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

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.

Related

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

can't create a record in a database

I am using rails version 4.2 and ruby version 2.2.0. I am trying to save a record to lollypops table. No exceptions indicating reasons.
TASK: As soon as a member is created and saved, I want to populate the lollypops table by calling the create_lollypop(#member.id) in members controller's create method like this:
# POST /members
# POST /members.json
def create
#member = Member.create(members_params)
return unless request.post?
#member.save!
self.current_user = #member
c = Country.find(#member.country_id)
#member.update_attributes(
:country_code=>c.code)
create_lollypop(#member.id) #From here I want to create lollypop
MemberMailer.signup_notification(#member).deliver_now
redirect_to(:controller => '/admin/members', :action => 'show',
:id=> #member.id)
flash[:notice] = "Thanks for signing up! Check your email now to
confirm that your email is correct!"
rescue ActiveRecord::RecordInvalid
load_data
render :action => 'new'
end
def create_lollypop(member_id)
#member = Member.find(member_id)
Lollypop.create(
:member_id=>#member.id,
:product_name=>'lollypop',
:product_price=>100,
:email=>#member.email,
:house_flat => #member.house_flat,
:street=>#member.street,
:city_town=>#member.city_town,
:country =>#member.country,
:postcode_index=>#member.postcode_index,
:name=>#member.name)
end
The 'member' is created but the 'lollypops' table is not populated. The associations are:
MEMBER model:
has_one :lollypop, :dependent=>:destroy
LOLLYPOP model
belongs_to :member
If I use generic SQL command then the lollypops table gets populated but I do not want to do that:
def self.create_lollypop(member_id)
member = Member.find(member_id)
ActiveRecord::Base.connection.execute("insert into lollypops (member_id,product_name,product_price,email,house_flat,street,city_town,country,postcode_index,name)
values(#{member.id},'lollypop',#{100},'#{member.email}','#{member.house_flat}','#{member.street}','#{member.city_town}','#{member.country_code}','#{member.postcode_index}','#{member.name}')")
end
Any advice would be welcomed. Thank you.
In your create_lollypop(), You are not defining #member.
def create_lollypop(member_id)
#member = Member.find member_id
Lollypop.create!(
:member_id=>#member.id,
:product_name=>'lollypop',
:product_price=>100,
:email=>#member.email,
:house_flat => #member.house_flat,
:street=>#member.street,
:city_town=>#member.city_town,
:country =>#member.country,
:postcode_index=>#member.postcode_index,
:name=>#member.name
)
end
Also use create! so in case any validation failed then it will raise exception. So it will help you sort out issue.
For the moment try to create lollypop using the association method create_lollypop directly in your controller. use this code in you create controller method, note that create_lollypop method will fill (member_id field automatically):
#member = Member.create(members_params)
return unless request.post?
#member.save!
self.current_user = #member
c = Country.find(#member.country_id)
#member.update_attributes(
:country_code=>c.code)
#From here I want to create lollypop
#member.create_lollypop(
:product_name=>'lollypop',
:product_price=>100,
:email=>#member.email,
:house_flat => #member.house_flat,
:street=>#member.street,
:city_town=>#member.city_town,
:country =>#member.country,
:postcode_index=>#member.postcode_index,
:name=>#member.name
)
MemberMailer.signup_notification(#member).deliver_now
redirect_to(:controller => '/admin/members', :action => 'show',
:id=> #member.id)
flash[:notice] = "Thanks for signing up! Check your email now to
confirm that your email is correct!"
rescue ActiveRecord::RecordInvalid
load_data
render :action => 'new'
This is not exactly an answer, more like tips and notes, it's a little long and I hope you don't mind.
return unless request.post?
This is more of a php thing not a rails thing, in rails already the routing is checking this, so you don't need to do this check inside the controller, if it isn't a post it will be routed elsewhere.
#member = Member.create(members_params)
return unless request.post?
#member.save!
Saving after creating is meaningless, because create already saves the data, if you are doing it for the sake of the bang save!, then you could use the create with bang create!, not to mention that you do the redirection check after the member's create, so if this did work, it would leave you with stray members.
c = Country.find(#member.country_id)
#member.update_attributes(:country_code=>c.code)
If you have your assocciations correctly, you don't need to save the code like this, because the member knows that this country_id belongs to a country.
So add this to the member model
class Member < ActiveRecord::Base
has_one :lollypop, dependent: :destroy
belongs_to :country
end
This way you could always call #member.country to return the country object, then the code could come from there, like #member.country.code, or you could just write a method to shorten that up
def country_code
country.code
end
this way will get the code through an extra query, but it has an advantage, if you for any reason change a country's code, you don't need to loop on all members who have that country and update their codes too, you could also shorten this up even more using #delegate
#member.save!
#member.update_attributes(:country_code=>c.code)
Here you are updating the attributes of member after saving the member, which is kinda a waste, because you are doing 2 queries for what could be done with 1 query, programmatically it is correct and it will work, but it's bad for scaling, when more users start using your app the database will be more busy and the responses will be slower.
Instead i would recommend to postpone the creation of member till you have all the data you want
#member = Member.new(members_params) # this won't save to the database yet
#memeber.code = Country.find(#member.country_id).code
#member.save
This will only do 1 query at the end when all data is ready to be saved.
redirect_to(:controller => '/admin/members', :action => 'show', :id=> #member.id)
This is ok, but you probably have a better shorter path name in your routes, something like members_admin_path, check your routes name by doing a bin/rake routes in your terminal.
redirect_to members_admin_path(id: #member)
redirect_to ...
flash[:notice] = "message"
I'm not sure this will work, because the redirection needs to be returned, but when you added the flash after it, either the redirection will happen without the flash, or the flash will be set and returned as it's the last statement, but the redirection won't happen, not sure which will happen, to fix it you can simply swap the two statements, create the flash first and then redirect, or use the more convenient way of setting the flash while redirecting, cause that's supported
redirect_to ....., notice: 'my message'
rescue ActiveRecord::RecordInvalid
load_data
render :action => 'new'
This will do the job, but it isn't conventional, people tend to use the soft save and then do an if condition on the return value, either true or false, here's a short layout
# prepare #member's data
if #member.save
# set flash and redirect
else
load_data
render :new
end
The lollypop creation
Now there's a few things about this, first you have the method in the controller, which is bad cause it shouldn't be the controller's concern, the second method the self.create_lollypop is better cause it's created on the model level, but it's a class method, then the better way is creating it as a member method, this way the member who creates the lollypop already knows the data because it's his own self, notice i don't need to call #member because i am already inside member, so simple calls like id, email will return the member's data
# inside member.rb
def create_lollypop
Lollypop.create!(
member_id: id,
product_name: 'lollypop',
product_price: 100,
email: email,
house_flat: house_flat,
street: street,
city_town: city_town,
country: country,
postcode_index: postcode_index,
name: name
)
end
if you want you can also add this as an after create callback
after_create :create_lollypop
ps: This method name will probably conflict with the ActiveRecords create_lollypop method, so maybe you should pick a different name for this method.
As Mohammad had suggested to me, I changed Lollypop.create to Lollypop.create! and
while running my code, one validation error popped up. After correcting it and
altering my code to:
Lollypop.create!(
:member_id=> #member.id,
:product_name=>'lollypop',
:product_price=>100,
:email=>#member.email,
:house_flat => #member.house_flat,
:street=>#member.street,
:city_town=>#member.city_town,
:country =>#member.country_code,
:postcode_index=>#member.postcode_index,
:name=>#member.name
)
The 'lollypops' table got populated.

Rspec received unexpected message :[] with

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

rails 2.3 convert hash into mysql query

I'm trying to find out how rails converts a hash such as (This is an example please do not take this literally I threw something together to get the concept by I know this query is the same as User.find(1)):
{
:select => "users.*",
:conditions => "users.id = 1",
:order => "username"
}
Into:
SELECT users.* FROM users where users.id = 1 ORDER BY username
The closest thing I can find is ActiveRecord::Base#find_every
def find_every(options)
begin
case from = options[:from]
when Symbol
instantiate_collection(get(from, options[:params]))
when String
path = "#{from}#{query_string(options[:params])}"
instantiate_collection(format.decode(connection.get(path, headers).body) || [])
else
prefix_options, query_options = split_options(options[:params])
path = collection_path(prefix_options, query_options)
instantiate_collection( (format.decode(connection.get(path, headers).body) || []), prefix_options )
end
rescue ActiveResource::ResourceNotFound
# Swallowing ResourceNotFound exceptions and return nil - as per
# ActiveRecord.
nil
end
end
I'm unsure as to how to modify this to just return what the raw mysql statement would be.
So after a few hours of digging I came up with an answer although its not great.
class ActiveRecord::Base
def self._get_finder_options options
_get_construct_finder_sql(options)
end
private
def self._get_construct_finder_sql(options)
return (construct_finder_sql(options).inspect)
end
end
adding this as an extension gives you a publicly accessible method _get_finder_options which returns the raw sql statement.
In my case this is for a complex query to be wrapped as so
SELECT COUNT(*) as count FROM (INSERT_QUERY) as count_table
So that I could still use this with the will_paginate gem. This has only been tested in my current project so if you are trying to replicate please keep that in mind.

Create an elsif statement in Rails API?

I am trying to receive a JSON post to my Rails 3 application. The JSON post is just an email with a subject which will be one of the following:
BACKUP_PASS/VERIFY_PASS
BACKUP_FAIL/VERIFY_FAIL
BACKUP_FAIL/VERIFY_PASS
etc..
I have the following code in my controller:
def backupnotification
email_payload = JSON.parse(params[:payload])
Activity.create(:action => 'failed to backup', :details => email_payload['recipient'], :user_id => '28')
end
I've also added the following to my routes file:
post '/api/activity/backupnotification' => 'activities#backupnotification'
Obviously, this would create a new Activity record regardless of the backup status. What I would like to do is create an activity with an action of failed to backup if the term FAIL appears anywhere in the subject, and successfully backed up if the term FAIL does not exist.
The JSON post (email_payload) includes an attribute called subject. I was wondering if I could do something like this:
if email_payload['subject'] => "FAIL"
...
else
...
end
What would be the best way of doing this?
Assuming you can access your subject in a similar way as your recipient, you can try something like this.
def backupnotification
email_payload = JSON.parse(params[:payload])
if email_payload['subject'].include?('FAIL')
action_message = 'failed to backup'
else
action_message = 'successfully backed up'
end
Activity.create(
:action => action_message,
:details => email_payload['recipient'],
:user_id => '28')
end