Rspec received unexpected message :[] with - json

I write a mapper which matches attributes received from facebook and set them to my model.
class Mapper::FacebookUser
def initialize (user, facebook_user)
#user = user
#facebook_user = facebook_user
end
def name
#user.name = #facebook_user['name'] if #facebook_user['name'] rescue false
end
end
The response from Facebook is something like this: (JSON)
{
"name": "Jo Doe"
}
And my user_spec is that:
require 'spec_helper.rb'
describe Mapper::FacebookUser do
before(:each) do
#facebook_user = double("FacebookUser")
#user = User.new
end
it 'should map name to name' do
#facebook_user.stub(:name).and_return("User Name")
Mapper::FacebookUser.new(#user, #facebook_user).name
#user.name.should eq("User Name")
end
end
RSpec::Mocks::MockExpectationError: Double "Facebook" received
unexpected message :[] with ("name")
I tried the following:
#facebook_user.stub(:name).and_return("User Name")
#facebook_user.stub("name").and_return("User Name")
#facebook_user.stub(:[]).and_return({name: "User Name"})
#facebook_user.stub(:[]).and_return({name: "User Name"}.to_json)
only this one works, but I am quite sure, that's not the way:
#facebook_user.stub(:[]).and_return("User Name")
What is wrong with my approach?
How can I get my test working?
many thanks
I use rspec:
rspec (2.14.1)
rspec-core (2.14.7)
rspec-expectations (2.14.4)
rspec-mocks (2.14.4)
rspec-rails (2.14.0)

First, you're not obviously using #facebook_event in your model, so I'm not sure you're stubbing it. If you want to stub the object/method you are using in your name method, you'd need to stub the :[] method on the #facebook_user object you're passing in on your new call. The reason you're getting the error you're getting is because your not stubbing that method.
I assume the one approach you tried with the #facebook_event stub is working because the real Mapper::FacebookUser#[] method calls that object in that way.

My approach is now
I hope this helps someone
facebook_user = { "name": "Jo Doe" }
class Mapper::FacebookUser
def initialize (user, facebook_user={})
#user = user # AR model
# allows ['key'] or [:keys]
#facebook_user = facebook_user.with_indifferent_access
end
def name
#user.name = #facebook_user.dig('name') # sets to nil if not present (works also with chains) >= Ruby 2.3
end
end
require 'spec_helper.rb'
describe Mapper::FacebookUser do
before(:each) do
#user = User.new
#facebook_user = {}
end
it 'should map name to name' do
#facebook_user['name']
Mapper::FacebookUser.new(#user, #facebook_user).name
#user.name.should eq("User Name")
end
end

Related

Rails pass user object to another controller

I am building a Rails project where I have a user, and that user has many Tests (it's like a Trivia game). I have a UsersController where I query the user (going to implement login later).
In my view I have a button which "starts" the test. I need to associate the user with a test, because my User has_many Tests (user_id is a foreign key on tests). My question is, how do I pass my #user object to my TestsController so I can associate the created test with the logged in user?
Here is my UsersController:
class UsersController < ApplicationController
def show
#user = User.find(1)
end
def start_test
redirect_to tests_path
end
end
In my users show view I have:
<p>Welcome <%= #user.name %>!</p>
<%= button_to "Start Test", users_start_test_path %>
When the button is clicked I redirect to tests_path which is in TestsController:
class TestsController < ApplicationController
def index
# here I need to create the Test belonging to the user
end
end
I am new to Rails and don't know how to pass that #user to TestsController so I can create the test belonging to the user. Theoretically I don't even need the whole #user, just the id. Any help would be greatly appreciated.
I think you should improve this by setting the route properly, Instead linking a controller action directly to another controller. I would create the following route.
resources :users do
resources :tests
end
This will force you to always have a user_id. For your new tests path you get this. new_user_test_path(#user)
This will use basics CRUD actions. So to make a new test for the correct user, use the the def new action in the tests controller.
def new
#user = User.find(params[:user_id]
#new_test = #user.tests.new
end
The controllers are standalone. They cann't share variables. You could try to send user id intro the request to controller TestsController (or better use an authentication system). Or directly in the TestsController calls User.find(1) again.
The only variable you can pass between controllers is a parameter through the routing (or the session).
So you can have a HTML Form that passes a body to an action inside a different controller OR for what I see in your case by visiting a route and pass a friendly query string parameter, like an ID.
So you could:
<%= button_to "Start Test", users_start_test_path(user_id: #user.id) %>
And then in your TestController:
class TestsController < ApplicationController
def index
#user = User.find params[:user_id]
end
end
I don't like to pass the current_user variable because it can then be hijacked.
This is how I would set the TestsController
class TestsController < ApplicationController
def index
#tests = current_user.tests
end
def new
#test = current_user.tests.new
end
def create
current_user.tests.create(test_params)
end
def show
#test = current_user.tests.find(params[:id]) # this will only find tests scoped to that user
end
def edit
#test = current_user.tests.find(params[:id]) # this will only find tests scoped to that user
end
def update
#test = current_user.tests.find(params[:id]) # this will only find tests scoped to that user
#test.update(test_params)
end
def test_params
params.require(:test).permit()
end
end
This way now you have the test scoped to the user.
I am assuming that you have device for user management

Removing password_digest from API json?

I'm using Sinatra to make a simple little API. I have not been able to figure out a way to remove the 'password_digest' field from the JSON I'm outputting. Well, I know of a long way that I can do it, but I have a feeling there is a much simpler way.
get "/users/all" do
content_type :json
#users = User.all
response = #users.map do |user|
user = user.to_h
user.delete("password_digest")
user
end
response.to_json
end
All I'm trying to do is remove the password_digest field from the output. Is there a simple way to do this? I've tried searching with no luck.
get "/users/all" do
content_type :json
#users = User.all
#users.to_json(except: [:password_digest])
end
You can also overide #as_json on the model to remove the attribute completely from serialization:
class User < ActiveRecord::Base
def as_json(**options)
# this coerces the option into an array and merges the passed
# values with defaults
excluding = [options[:exclude]].flatten
.compact
.union([:password_digest])
super(options.merge(exclude: excluding))
end
end
You should be able to do this:
get "/users/all" do
content_type :json
#users = User.all
response = #users.map do |user|
user = user.to_h # If your data is already a hash, you don't need this line.
user.delete(:password_digest) # <-- If your keys are symbolized
user.delete("password_digest") # <-- If your keys are strings
user
end
response.to_json
end

User ID is NULL when saving record for logged in user

I am starting to learn rails and have run into a problem. I am writing a simple application (similar to the twitter tutorials I have seen) where a user logs in and creates a new post.
When a user logs in, I am setting the session information as follows
session[:id] = authorized_user.id
session[:email] = authorized_user.email
So now I have the ID of the user logged in. Upon login, the user is brought to a form where they can submit a new post (3 fields.) When user clicks submit, I want to create a new record with the data they entered, and associate the record to that user (User ID). I am not exactly sure how to do this.
Below is the code on the controller:
def create
#Used for creating new status posts
#Need to get the ID of the user logged in
#user = AdminUser.find(session[:id])
#Instantiate new object using form parameters
#post = Post.new(post_params)
#post.AdminUser = #user # THIS IS THE LINE NOT WORKING
#Save the object
if #post.save
#If save succeeds, redirect to the index action
flash[:notice] = "Status has been saved"
redirect_to(:action => 'index')
else
#If the save fails, redisplay the form so user can fix problems
render('new')
end
end
Here is the Private method for post_params
def post_params
#Defining the params that are allowed to be passed with forms.
params.require(:post).permit(:post_status, :post_title, :post_content)
end
The record is saved but the UserID for the record is NULL.
My first instict was to try to pass UserID as a post parameter, but i think this is a potential security risk, so I am trying to figure out an alternate way. I am sure it is something simple and I am just missing it.
Attributes
Firstly,
#post.AdminUser = #user # THIS IS THE LINE NOT WORKING
You should use snake_case for your attribute names (you're using CamelCase). Calling an attribute AdminUser has all sorts of potential issues which will arise down the line.
Call it admin_user or admin_id or something similar
--
Params
Secondly,
I want to create a new record with the data they entered, and
associate the record to that user (User ID)
If you're trying to save a "dependent" record for an object (for example, saving a post for a user), you'll have to assign the user_id record yourself, and pass it through the params, like so:
#app/controllers/posts_controller.rb
Class PostsController < ApplicationController
def create
#post = Post.new(post_params)
#post.save
end
private
def post_params
params.require(:post).permit(:title, :body).merge({user_id: authorized_user.id})
end
end
When you create an element in your app, you're basically just taking data from the params hash & sending to the model to save. This is done using the strong_params functionality introduced in Rails 4:
def post_params
params.require(:post).permit(:title, :body).merge({user_id: authorized_user.id})
end
As you can see from my example above, you basically need to be able to send through the user_id / admin_id / AdminUser value through to the model (so it can save)
You can also do this by setting the attribute as the example below:
#app/controllers/posts_controller.rb
def create
#post = Post.new(post_params)
#post.user_id = authorized_user.id
#post.save
end
private
def post_params
params.require(:post).permit(:title, :body, :user_id)
end
--
You should also look at the difference between authentication & autorhization for better definition of your logged-in user object :)
Rewrite the line like this, taking UserID as the column name in posts table
#post.UserID = #user.id

How do I create an rspec test that validates a JSON response?

I have a Groups Controller with a method def inbox.
If the user is a group member then inbox returns a JSON object.
If the user is not a member, then inbox should redirect thanks to CanCan permissions.
How do I write an rspec to test these two use cases?
Current spec:
require 'spec_helper'
describe GroupsController do
include Devise::TestHelpers
before (:each) do
#user1 = Factory.create(:user)
#user1.confirm!
sign_in #user1
#group = Factory(:group)
#permission_user_1 = Factory.create(:permission, :user => #user1, :creator_id => #user1.id, :group => #group)
end
describe "GET inbox" do
it "should be successful" do
get inbox_group_path(#group.id), :format => :json
response.should be_success
end
end
end
Routes:
inbox_group GET /groups/:id/inbox(.:format) {:controller=>"groups", :action=>"inbox"}
Routes File:
resources :groups do
member do
get 'vcard', 'inbox'
end
....
end
This is how I do this:
describe "GET index" do
it "returns correct JSON" do
# #groups.should have(2).items
get :index, :format => :json
response.should be_success
body = JSON.parse(response.body)
body.should include('group')
groups = body['group']
groups.should have(2).items
groups.all? {|group| group.key?('customers_count')}.should be_true
groups.any? {|group| group.key?('customer_ids')}.should be_false
end
end
I'm not using cancan, therefore I cannot help with this part.
Sometimes it might be good enough to verify if response contains valid JSON and to show actual response otherwise, here is an example:
it 'responds with JSON' do
expect {
JSON.parse(response.body)
}.to_not raise_error, response.body
end
Try this:
_expected = {:order => order.details}.to_json
response.body.should == _expected
I think the first thing you want to do is to check that the response is of the correct type, i.e. that it has the Content-Type header set to application/json, something along the lines of:
it 'returns JSON' do
expect(response.content_type).to eq(Mime::JSON)
end
Then, depending on your case, you might want to check whether the response can be parsed as JSON, like wik suggested:
it 'responds with JSON' do
expect {
JSON.parse(response.body)
}.to_not raise_error
end
And you could merge the above two into a single test if you feel like two tests for checking JSON response validity are too much.
To assert JSON you can do this too:
ActiveSupport::JSON.decode(response.body).should == ActiveSupport::JSON.decode(
{"error" => " An email address is required "}.to_json
)
This blog gives some more ideas.

Ruby libxml parsing and inserting to database

I am currently trying to read from an xml file which records the jobs on a PBS. I have succesfullly managed to parse the code, but am unable to insert the objtects into my database, i receive this error:
"You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.delete"
This is my Model:
require 'xml/libxml'
class Job < ActiveRecord::Base
JOB_DIR = File.join('data', 'jobs')
attr_reader :jobid, :user, :group, :jobname, :queue, :ctime
attr_reader :qtime, :etime, :start, :owner
def initialize(jobid, user, group, jobname, queue, ctime, qtime, etime, start, owner)
#jobid, #user, #group, #jobname, #queue = jobid, user, group, jobname, queue
#ctime, #qtime, #etime, #start, #owner = ctime, qtime, etime, start, owner
end
def self.find_all()
jobs = []
input_file = "#{JOB_DIR}/1.xml"
doc = XML::Document.file(input_file)
doc.find('//execution_record').each do |node|
jobs << Job.new(
node.find('jobid').to_a.first.content,
node.find('user').to_a.first.content,
node.find('group').to_a.first.content,
node.find('jobname').to_a.first.content,
node.find('queue').to_a.first.content,
node.find('ctime').to_a.first.content,
node.find('qtime').to_a.first.content,
node.find('etime').to_a.first.content,
node.find('start').to_a.first.content,
node.find('owner').to_a.first.content
)
end
jobs
end
end
An my Model Controller:
class JobController < ApplicationController
def index
#jobs = Job.find_all()
end
def create
#jobs = Job.find_all()
for job in #jobs
job.save
end
end
end
I would appreciate any help...Thank you!
I'm not sure on the causes of the error message you're seeing because I can't see anywhere that you're trying to invoke a delete method, however this does seem like a slightly confused use of ActiveRecord.
If you have a jobs database table with fields jobid, user, group, jobname etc. then ActiveRecord will create accessor methods for these and you should not be using attr_reader or overriding initialize. You should also not be setting values value instance variables (#jobid etc.) If you don't have such fields on your table then there is nothing in your current code to map the values from the XML the database fields.
Your def self.find_all method should probably be along the lines of:
def self.build_from_xml
jobs = []
input_file = "#{JOB_DIR}/1.xml"
doc = XML::Document.file(input_file)
doc.find('//execution_record').each do |node|
jobs << Job.new(
:jobid => node.find('jobid').to_a.first.content,
:user => node.find('user').to_a.first.content,
...
Rails used to have a method of its own find_all to retrieve all existing records from the database so your method name is probably a bit misleading. Rails tends to use the build verb to mean create a new model object but don't save it yet so that's why I've gone with a name like build_from_xml.