Testing a model spec that uses an after_create callback - unit-testing

Here is a model that I'm using, I've simplified it a bit down to the simplest form that still fails my example:
class User < ActiveRecord::Base
after_create :setup_lists
def setup_lists
List.create(:user_id => self.id, :name => "current")
List.create(:user_id => self.id, :name => "master")
end
end
And I'd like to spec the example as follows:
require 'spec_helper'
describe User do
before(:each) do
#user = Factory(:user)
end
describe "#setup_lists" do
before(:each) do
List.stub(:create).with(:name => "current")
List.stub(:create).with(:name => "master")
it "creates a new master list" do
List.should_receive(:create).with(:name => "master")
end
it "creates a new current list" do
List.should_receive(:create).with(:name => "current")
end
end
end
Which I expected would work just fine, but I am left with the following error:
Failures:
1) User#setup_lists creates a new master list
Failure/Error: List.should_receive(:create).with(:name => "current")
(<List(id: integer, name: string, created_at: datetime, updated_at: datetime, user_id: integer) (class)>).create({:name=>"current"})
expected: 1 time
received: 0 times
# ./spec/models/user_spec.rb:44
2) User#setup_lists creates a new current list
Failure/Error: List.should_receive(:create).with(:name => "master")
(<List(id: integer, name: string, created_at: datetime, updated_at: datetime, user_id: integer) (class)>).create({:name=>"master"})
expected: 1 time
received: 0 times
# ./spec/models/user_spec.rb:48
Can anybody help me understand why this is happening?

Three issues:
1) The User object is created before setting the message expectation, so should_receive never gets to see the message;
2) You're stubbing out methods for which you're also setting expectations. You want to stub out methods for which you don't set expectations, but which are needed for the test to pass
3) You need to pass in all the parameters
To fix, create the User object after setting the expectaion, and stub out each method in turn (because your model calls List.create twice):
describe User do
describe "#setup_lists" do
it "creates a new master list" do
List.stub(:create).with(:user_id=>1,:name => "current")
List.should_receive(:create).with(:user_id=>1,:name => "master")
#user = User.create
end
it "creates a new current list" do
List.stub(:create).with(:user_id=>1,:name => "master")
List.should_receive(:create).with(:user_id=>1,:name => "current")
#user = User.create
end
end
end
While it's really an issue of style, it makes more sense to use a real User object here rather than a factory, since you're testing the model itself.

zetetic's answer is awesome, but if you want something a bit quicker (and still works), I'd recommend using the shoulda-callback-matchers gem. It's a complete set of matchers that make testing callbacks easier. I'm all about easy & reducing boilerplate. You can see some examples in my RSpec model testing skeleton if you care to look.
Either way gets the job done!

Related

How do I write a regex in my Rails model validation?

I'm using Rails 5. I want one of the attributes in my model to fail validation if it consists of only letters or if it contains the pattern "\d:\d" anywhere in its value. I tried this
validates_format_of :my_attr, numericality: { greater_than: 0, :only_integer => true }, :allow_blank => true, :without => /(\d:\d|^\p{L}+$)/
But when I create a new object
2.4.0 :018 > ab = MyObjectTime.new({:my_attr => "ab"})
It is not indicating an error when I query "ab.errors" for the field in question. What's the correct way to write the regular expression above?
First and Foremost new method does not trigger any kind of validation on the object.
class Person < ApplicationRecord
validates :name, presence: true
end
>> p = Person.new
# => #<Person id: nil, name: nil>
>> p.errors.messages
# => {}
new method does not trigger any validation on an object as it is not hitting the database to save the record.
>> p.save
# => false
save method will try to create the record to the database and triggers the respective validation on the object.
>> p.valid?
# => false
When you hit the .valid? the method, it validates the object against the mentioned validation.
>> p.errors.messages
# => {name:["can't be blank"]}
>> p = Person.create
# => #<Person id: nil, name: nil>
Creating and saving a new record will send an SQL INSERT operation to the database.
Internal functionality of Person.create is Person.new and Person.save
When you are creating an object, it tries to create the valid record to the database and triggers the respective validation.
>> p.errors.messages
# => {name:["can't be blank"]}
>> p.save
# => false
>> p.save!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
>> Person.create!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
Secondly, validates_numericality_of and validates_format_of are the different set of validation helpers which you have mixed.
validates_numericality_of :my_attr, :only_integer => true, :allow_blank => true,
:greater_than => 0
validates_format_of :my_attr, :without => /(\d:\d|^\p{L}+$)/
This validation won't accept any such object :
MyObjectTime.new({:my_attr => "ab"})
MyObjectTime.new({:my_attr => "1:1"})
For more information you can take help from these validation helpers => http://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html
Try something like this
validates :email, presence: true, format: { with: /\A([^\}\{\]\[#\s\,]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }

Rails 4 - Thinking Sphinx filter using field on related model

In my Rails 4 app I have models like:
'AgencyGroup'
has_many :group_agencies
has_many :agencies, :through => :group_agencies
Where I keep 'token' field
'Agency' model
has_many :group_agencies
has_many :agency_groups, :through => :group_agencies
has_many :advertisements
'Advertisement' model
belongs_to :agency
I use Thinking Sphinx and it works really greate but now I got new requirement to filter 'Advertisements' by AgencyGroup token fields.
Basically I need to find advertisment with some parameters but only for agencies that are in agency group with posted token.
if params[:search]
#results = Advertisement.search Riddle::Query.escape(params[:search]), :star => true, :page => params[:page], :per_page => 6
end
To get results I run http query like this:
http://api.localhost.local:3000/v1/advertisements?token=JHW_tdXn5g-vQY1f_ZzLuw&search=Nissim
What I'm missing? How to use relation between models in TS?
I think the best approach here involves a few steps:
Step 1: Add AgencyGroup IDs as an attribute in your Advertisement index. If you're using SQL-backed indices (:with => :active_record), it's a one-liner:
has agency.group_agencies.agency_group.id, :as => :agency_group_ids
If you're using real-time indices, then you'll want a method in Advertisement that returns all of those IDs:
def agency_group_ids
agency.agency_group_ids
end
And your attribute definition will look like this:
has agency_group_ids, :type => :integer, :multi => true
Step 2: Because you've changed your index structure, don't forget to rebuild your indices:
# for SQL-backed indices:
rake ts:rebuild
# or, for real-time indices
rake ts:regenerate
Step 3: In your controller, find the agency group for the given token:
agency_group = AgencyGroup.find_by :token => params[:token]
Step 4: Finally, use that agency group's id in your search call:
#results = Advertisement.search Riddle::Query.escape(params[:search]),
:star => true,
:page => params[:page],
:per_page => 6,
:with => {:agency_group_ids => agency_group.id}

Rails create multiple interrelated objects in one form

I have a Vehicles model where each vehicle has a make and model. When a user creates a vehicle, they can either select from currently available makes and models, or they can create a new make and model. Both make and model contain no extra data, so they are stored in the CommonLookup model I use for dynamic enumerations.
I want to limit model choices through ajax based on the currently selected make. To do this, I've created a blongs_to relationship on the CommonLookup model to itself; in other words, any record of that type can optionally reference a parent record of the same type in a many-to-one relationship.
The problem I'm running into is actually saving the relationship. My model code, which works for creating non-related make and model records, is as follows:
class Vehicle < ActiveRecord::Base
belongs_to :make, :class_name => "CommonLookup", :foreign_key => "make_id"
belongs_to :model, :class_name => "CommonLookup", :foreign_key => "model_id"
attr_accessor :new_make_name
attr_accessor :new_model_name
before_save :create_make_from_name, :create_model_from_name
def create_make_from_name
create_make(
:value => new_make_name
)
end
def create_model_from_name
create_model(
:value => new_model_name
)
end
end
This code successfully creates a vehicle with the associated new make and model, but the new make and model are not associated with each other as I need them to be. I need a many-models to one-make relationship that I can use to easily limit choices. To be clear, this question has nothing to do with the ajax part that is necessary for limiting choices; I'm focusing on the creation of the model instances themselves so that they are related, all from a single form.
I've attempted to set up code in the create_model_from_name callback but to no avail; there is no accessible reference to the object created in the first callback that could be used to set up the relationship. What I tried:
def create_model_from_name
create_model(
:value => new_model_name,
:parent => :make
)
end
But this didn't work. Any help would be greatly appreciated.
I solved this by combining the before_save callbacks and including a little more logic:
class Vehicle < ActiveRecord::Base
belongs_to :make, :class_name => "CommonLookup", :foreign_key => "make_id"
belongs_to :model, :class_name => "CommonLookup", :foreign_key => "model_id"
attr_accessor :new_make_name
attr_accessor :new_model_name
before_save :create_make_and_model_from_names
def create_make_and_model_from_names
if not new_model_name.blank?
if not new_make_name.blank?
create_model(
:value => new_model_name,
:parent => create_parent(
:value => new_make_name
)
)
else
create_model(
:value => new_model_name,
:parent => model
)
end
end
end
end
This met my requirements by setting up a way of creating new makes and creating and associating new models with both new makes and existing makes. The end result is that my ajax request is much easier to set up because I can easily locate the models associated with each make and place these in the options list.

Rails tutorial: undefined method

I'm stuck (again!) at Chapter 9 (this time in section 9.2.2) of the Rails tutorial. I am getting
bundle exec rspec spec/
................................FFF........................
Failures:
1) Authentication authorization as wrong user submitting a GET request to the Users#edit action
Failure/Error: before {sign_in user, no_capybara: true}
NoMethodError:
undefined method `new_remember_token' for #<User:0x007f8181815448>
# ./spec/support/utilities.rb:13:in `sign_in'
# ./spec/requests/authentication_pages_spec.rb:71:in `block (4 levels) in <top (required)>'
The other 2 errors are of the same type.
Here is spec causing the errors:
describe "as wrong user" do
let(:user) {FactoryGirl.create(:user)}
let(:wrong_user) {FactoryGirl.create(:user, email: "wrong#example.com")}
before {sign_in user, no_capybara: true}
describe "submitting a GET request to the Users#edit action" do
before {get edit_user_path(wrong_user)}
specify { expect(response.body).not_to match(full_title('Edit user'))}
specify { expect(response).to redirect_to(root_url)}
end
describe "submitting a PATCH request to the Users#update action" do
before { patch user_path(wrong_user)}
specify { expect(response).to redirect_to(root_url)}
end
end
And here is the method (utilities.rb) the error message is complaining about:
def sign_in (user, options={})
if options[:no_capybara]
# Sign in when not using Capybara
remember_token = User.new_remember_token
cookies[:remember_token]
user.update_attribute(:remember_token, User.digest(remember_token))
else
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
end
The code for the model (User.rb) is here:
class User < ActiveRecord::Base
before_save { self.email = email.downcase}
before_create :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
validates :password, length: {minimum: 6}
has_secure_password
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.digest(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = User.digest(User.new_remember_token)
end
end
I had previously trouble with the sign_in method but it miraculously disappeared. What am I doing wrong?
I finally found the culprit for the erratic test results that I have been observing in this case and, quite likely, on previous occasions (Failure/Error: sign_in user undefined method `sign_in', Rails named route not recognized). The problem seems to be that rails does not clear by default the cache between tests. Which is, actually, downright scary. It seems you cannot really trust the test results. I realised this by commenting out the method that rails was complaining about and re-running the test. The error persisted which meant one thing - rspec was simply working with some cached versions of the files and thus disregarding the changes which I am making. So even if the tests pass you can't be sure that they really do. This is really bizarre. After realising the problem with a bit of googling I found how to force rails to clean the cache - check jaustin's answer here: is Rails.cache purged between tests?

Rails 4 testing controller spec ,devise helper sign_in doesn't work

I have Rails 4.2.6 and rspec 3.3.0 , devise version 3.5.8 and i have trouble to test my controller specs always getting error saying ("expected the response to have a success status code (2xx) but it was 401")
Later when i try different spec I am always getting failure message ("Your account is not enabled yet !"), any ideas ?, any help much appreciated ..
my spec:
require 'rails_helper'
describe MyController, :type => :controller
let(:user) { create(:user) }
let(:campaign) { create(:campaign, user: user) }
let!(:placement) { create(:placement, user: user, campaign: campaign, end_date: Date.today) }
before(:each) do
sign_in user
end
context "when we have no data" do
before do
get :graph_data, format: :json
#json = JSON.parse(response.body)
end
it "should be a success" do
expect(response).to have_http_status(:success)
end
end
end
I have following spec_helper:
config.include Devise::TestHelpers, :type => :controller
config.before(:each) do
DatabaseCleaner.start
end
factory defined:
FactoryGirl.define do
factory :unconfirmed_user, class: User do
sequence(:email) { |n| "john#{n}#email.com" }
sequence(:name) { |n| "John Nice #{n}" }
password 'password'
password_confirmation 'password'
factory :user do
confirmed_at Time.now.utc
factory :admin do
organisation { create(:organisation, :some_organisation) }
end
end
end
controller:
MyController < ApplicationController
def graph_data
items = current_user.items //outputs [1,2,3,45,5]
render json: items, status: :ok
end
end
routes:
GET /mycontroller/graph_data(.:format)
i found what was the issue,on User model had a method 'active_for_authentication?'
def active_for_authentication?
super && (group.include?('company-group) || is_admin? )
end
Method 'active_for_authentication?' is a Devise public method
and when it is defined in your model like (User), you change behaviour of it, and in my case it was overwritten with different behaviour,
checking for belonging user to the 'company-group' or not ..
That's why i had failure message ("Your account is not enabled yet !")