I am using factory_girl_rails and rspec and run into trouble,
raises the folloing error
1) WebsiteLink when link is external
Failure/Error: website_link.external should be false
expected #<FalseClass:0> => false
got #<WebsiteLink:100584240> => #<WebsiteLink id: nil, website_id: nil, link: nil, external: nil, checked: nil, click_count: nil, transition_count: nil, created_at: nil, updated_at: nil, link_description: nil>
Compared using equal?, which compares object identity,
but expected and actual are not the same object. Use
`expect(actual).to eq(expected)` if you don't care about
object identity in this example.
It is my code in spec.rb file
it "when link is external" do
website = FactoryGirl.create(:website,site_address: "www.socpost.ru")
website_link = FactoryGirl.create(:website_link, link: "www.google.com", website: website)
website_link.external should be true
end
The factory_girl factories
FactoryGirl.define do
factory :website do
sequence(:site_name){ |i| "Facebook#{i}" }
sequence(:site_address){ |i| "www.facebook_#{i}.com" }
sequence(:website_key){ |i| (1234567 + i).to_s }
end
factory :website_link do
sequence(:link){ |i| "www.facebook.com/test_#{i}" }
external false
checked false
click_count 1
transition_count 1
link_description "Hello"
website
end
end
Since I think it's helpful to understand why you received the error you received, here's an explanation:
You're statement had four expressions separated by spaces: website_link.external, should, be and false
Ruby evaluates these from right to left
false is trivial
be is interpreted as a method call with false as an argument
should is interpreted as a method with the result of be as an argument.
should is interpreted relative to subject, since the method wasn't sent to a specific object
Given the error you received, subject was either explicitly set to be WebsiteLink or that was the argument to describe for a parent of the example and thus the implicit subject
website_link.external never got evaluated because the error occurred prior to that point
You forgot to use the dot.
it "when link is external" do
website = FactoryGirl.create(:website,site_address: "www.socpost.ru")
website_link = FactoryGirl.create(:website_link, link: "www.google.com", website: website)
website_link.external.should be true (note the dot between external and should)
end
Give it a try.
Related
I have the following code:
## teams_controller.rb
def destroy
ActiveRecord::Base.transaction do
team_admins = team.team_admins
binding.pry
team.destroy!
team_admins.each(&:update_team_admin_role_if_needed!)
end
respond_with_200(team, serializer: V1::User::Management::TeamSerializer)
end
And the corresponding spec to ensure the last line of the above code fires:
## teams_controller_spec.rb
it 'demotes team admins to employees when needed' do
team_admin_account = create(:account)
admin_team_membership = create(:team_membership, team: #team, admin: true, account: team_admin_account)
team_admin_account.update!(role: Role.team_admin)
expect { process_destroy(team_id: #team.slug) }
.to change { team_admin_account.reload.role }
.from(Role.team_admin)
.to(Role.employee)
end
When I use the above code in my application it works as expected, however the spec fails as the account apparently never has their role updated:
expected `team_admin_account.reload.role` to have changed from #<Role id: 4, add_to_first_user_in_organisation: false, title: "Team admin", created_at: "2020-01-03 09:04:28", updated_at: "2020-01-03 09:04:28", management: false, cms_access: false> to #<Role id: 3, add_to_first_user_in_organisation: false, title: "Employee", created_at: "2020-01-03 09:04:28", updated_at: "2020-01-03 09:04:28", management: false, cms_access: false>, but did not change
When I hit the pry point in my spec and quit out straight away, the spec fails. Likewise when there is no pry point.
However when I enter team_admins at the pry point (which returns the one team_admin I create in my spec) and then quit out of the spec, the spec passes and the account has their role updated.
Anyone have any idea why manually calling team_admins makes my spec pass?
Thanks in advance
EDIT:
The following change to the code also makes the spec pass:
def destroy
ActiveRecord::Base.transaction do
team_admins = team.team_admins
puts team_admins ## <---- Adding this makes the spec pass
team.destroy!
team_admins.each(&:update_team_admin_role_if_needed!)
end
respond_with_200(team, serializer: V1::User::Management::TeamSerializer)
end
team_admins is an AssociationRelation from team. This does not execute a query until referenced, such as with each or puts.
team.destroy! is called before team_admins.each is called. So when team_admins.each executes there is no more team and thus no team_admins. You should be able to verify this by watching logs/test.log and looking at the queries and when they are executed.
Congratulations, you've found a bug. Execute team_admins.each before destroying team.
In Ruby 1.9.2 on Rails 3.0.3, I'm attempting to test for object equality between two Friend (class inherits from ActiveRecord::Base) objects.
The objects are equal, but the test fails:
Failure/Error: Friend.new(name: 'Bob').should eql(Friend.new(name: 'Bob'))
expected #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil>
got #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil>
(compared using eql?)
Just for grins, I also test for object identity, which fails as I'd expect:
Failure/Error: Friend.new(name: 'Bob').should equal(Friend.new(name: 'Bob'))
expected #<Friend:2190028040> => #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil>
got #<Friend:2190195380> => #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil>
Compared using equal?, which compares object identity,
but expected and actual are not the same object. Use
'actual.should == expected' if you don't care about
object identity in this example.
Can someone explain to me why the first test for object equality fails, and how I can successfully assert those two objects are equal?
Rails deliberately delegates equality checks to the identity column. If you want to know if two AR objects contain the same stuff, compare the result of calling #attributes on both.
Take a look at the API docs on the == (alias eql?) operation for ActiveRecord::Base
Returns true if comparison_object is the same exact object, or comparison_object is of the same type and self has an ID and it is equal to comparison_object.id.
Note that new records are different from any other record by definition, unless the other record is the receiver itself. Besides, if you fetch existing records with select and leave the ID out, you’re on your own, this predicate will return false.
Note also that destroying a record preserves its ID in the model instance, so deleted models are still comparable.
If you want to compare two model instances based on their attributes, you will probably want to exclude certain irrelevant attributes from your comparison, such as: id, created_at, and updated_at. (I would consider those to be more metadata about the record than part of the record's data itself.)
This might not matter when you are comparing two new (unsaved) records (since id, created_at, and updated_at will all be nil until saved), but I sometimes find it necessary to compare a saved object with an unsaved one (in which case == would give you false since nil != 5). Or I want to compare two saved objects to find out if they contain the same data (so the ActiveRecord == operator doesn't work, because it returns false if they have different id's, even if they are otherwise identical).
My solution to this problem is to add something like this in the models that you want to be comparable using attributes:
def self.attributes_to_ignore_when_comparing
[:id, :created_at, :updated_at]
end
def identical?(other)
self. attributes.except(*self.class.attributes_to_ignore_when_comparing.map(&:to_s)) ==
other.attributes.except(*self.class.attributes_to_ignore_when_comparing.map(&:to_s))
end
Then in my specs I can write such readable and succinct things as this:
Address.last.should be_identical(Address.new({city: 'City', country: 'USA'}))
I'm planning on forking the active_record_attributes_equality gem and changing it to use this behavior so that this can be more easily reused.
Some questions I have, though, include:
Does such a gem already exist??
What should the method be called? I don't think overriding the existing == operator is a good idea, so for now I'm calling it identical?. But maybe something like practically_identical? or attributes_eql? would be more accurate, since it's not checking if they're strictly identical (some of the attributes are allowed to be different.)...
attributes_to_ignore_when_comparing is too verbose. Not that this will need to be explicitly added to each model if they want to use the gem's defaults. Maybe allow the default to be overridden with a class macro like ignore_for_attributes_eql :last_signed_in_at, :updated_at
Comments are welcome...
Update: Instead of forking the active_record_attributes_equality, I wrote a brand-new gem, active_record_ignored_attributes, available at http://github.com/TylerRick/active_record_ignored_attributes and http://rubygems.org/gems/active_record_ignored_attributes
META = [:id, :created_at, :updated_at, :interacted_at, :confirmed_at]
def eql_attributes?(original,new)
original = original.attributes.with_indifferent_access.except(*META)
new = new.attributes.symbolize_keys.with_indifferent_access.except(*META)
original == new
end
eql_attributes? attrs, attrs2
I created a matcher on RSpec just for this type of comparison, very simple, but effective.
Inside this file:
spec/support/matchers.rb
You can implement this matcher...
RSpec::Matchers.define :be_a_clone_of do |model1|
match do |model2|
ignored_columns = %w[id created_at updated_at]
model1.attributes.except(*ignored_columns) == model2.attributes.except(*ignored_columns)
end
end
After that, you can use it when writing a spec, by the following way...
item = create(:item) # FactoryBot gem
item2 = item.dup
expect(item).to be_a_clone_of(item2)
# True
Useful links:
https://relishapp.com/rspec/rspec-expectations/v/2-4/docs/custom-matchers/define-matcher
https://github.com/thoughtbot/factory_bot
If like me you're looking for a Minitest answer to this question then here's a custom method that asserts that the attributes of two objects are equal.
It assumes that you always want to exclude the id, created_at, and updated_at attributes, but you can override that behaviour if you wish.
I like to keep my test_helper.rb clean so created a test/shared/custom_assertions.rb file with the following content.
module CustomAssertions
def assert_attributes_equal(original, new, except: %i[id created_at updated_at])
extractor = proc { |record| record.attributes.with_indifferent_access.except(*except) }
assert_equal extractor.call(original), extractor.call(new)
end
end
Then alter your test_helper.rb to include it so you can access it within your tests.
require 'shared/custom_assertions'
class ActiveSupport::TestCase
include CustomAssertions
end
Basic usage:
test 'comments should be equal' do
assert_attributes_equal(Comment.first, Comment.second)
end
If you want to override the attributes it ignores then pass an array of strings or symbols with the except arg:
test 'comments should be equal' do
assert_attributes_equal(
Comment.first,
Comment.second,
except: %i[id created_at updated_at edited_at]
)
end
I have these factories setup:
FactoryGirl.define do
factory :product do
name { Faker::Commerce.product_name }
price { Faker::Commerce.price }
image { Faker::Internet.url }
end
factory :new_product, parent: :product do
name nil
price nil
image nil
end
factory :string_product, parent: :product do
price { Faker::Commerce.price.to_s }
end
end
Why do I want to use :string_product? Well, although the price attribute is of datatype float at the database level, occasionally I want to build a Factory with all of the attributes as strings.
This is so I can build the factory and then run expectations against its attributes when they are passed into the params hash. (All params from the URL are strings)
However, in the rails console:
> FactoryGirl.build :string_product
=> #<Product:0x00000007279780 id: nil, name: "Sleek Plastic Hat", price: 43.54, image: "http://blick.name/moie", created_at: nil, updated_at: nil>
As you can see, price is still being saved as a string.
An experiment to attempt to see what's going on:
...
factory :string_product, parent: :product do
price { "WHY ARE YOU NOT A STRING?" }
end
...
results in:
=> #<Product:0x000000077ddfa0 id: nil, name: "Awesome Steel Pants", price: 0.0, image: "http://rogahn.com/kavon", created_at: nil, updated_at: nil>
My string is now converted to the float 0.0
How do I prevent this behavior? If I want to have one of my attributes as a string, especially when I'm only building it I should be allowed to. Is there a FactoryGirl configuration where I can stop this happening? Exactly the same thing happens with the Fabrication gem, so I'm guessing this is something to do with the model? My Product model is literally empty right now...no validations or anything, so how can that be? The only way FactoryGirl knows price is a float is because it has that datatype on the database level.
Anyway, this is really annoying, if someone could show me how to let me write strings to my Factory's attributes I would be very appreciative. I could use .to_s in the spec itself but I want to keep my specs clean as possible and thought factories would be a great place to keep this configuration...
Is there a fabrication library that would let me do this?
Just some more experimentation:
> "WHY ARE YOU NOT A STRING".to_f
=> 0.0
Okay, so somewhere, in rails or in factorygirl, to_f is being called on my beloved string. Where? And how do I stop it?
With fabrication you need to use attributes_for to generate a hash representation of your object. It will bypass the ActiveRecord model entirely so nothing should be coerced.
Fabricate.attributes_for(:string_product)
In Ruby 1.9.2 on Rails 3.0.3, I'm attempting to test for object equality between two Friend (class inherits from ActiveRecord::Base) objects.
The objects are equal, but the test fails:
Failure/Error: Friend.new(name: 'Bob').should eql(Friend.new(name: 'Bob'))
expected #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil>
got #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil>
(compared using eql?)
Just for grins, I also test for object identity, which fails as I'd expect:
Failure/Error: Friend.new(name: 'Bob').should equal(Friend.new(name: 'Bob'))
expected #<Friend:2190028040> => #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil>
got #<Friend:2190195380> => #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil>
Compared using equal?, which compares object identity,
but expected and actual are not the same object. Use
'actual.should == expected' if you don't care about
object identity in this example.
Can someone explain to me why the first test for object equality fails, and how I can successfully assert those two objects are equal?
Rails deliberately delegates equality checks to the identity column. If you want to know if two AR objects contain the same stuff, compare the result of calling #attributes on both.
Take a look at the API docs on the == (alias eql?) operation for ActiveRecord::Base
Returns true if comparison_object is the same exact object, or comparison_object is of the same type and self has an ID and it is equal to comparison_object.id.
Note that new records are different from any other record by definition, unless the other record is the receiver itself. Besides, if you fetch existing records with select and leave the ID out, you’re on your own, this predicate will return false.
Note also that destroying a record preserves its ID in the model instance, so deleted models are still comparable.
If you want to compare two model instances based on their attributes, you will probably want to exclude certain irrelevant attributes from your comparison, such as: id, created_at, and updated_at. (I would consider those to be more metadata about the record than part of the record's data itself.)
This might not matter when you are comparing two new (unsaved) records (since id, created_at, and updated_at will all be nil until saved), but I sometimes find it necessary to compare a saved object with an unsaved one (in which case == would give you false since nil != 5). Or I want to compare two saved objects to find out if they contain the same data (so the ActiveRecord == operator doesn't work, because it returns false if they have different id's, even if they are otherwise identical).
My solution to this problem is to add something like this in the models that you want to be comparable using attributes:
def self.attributes_to_ignore_when_comparing
[:id, :created_at, :updated_at]
end
def identical?(other)
self. attributes.except(*self.class.attributes_to_ignore_when_comparing.map(&:to_s)) ==
other.attributes.except(*self.class.attributes_to_ignore_when_comparing.map(&:to_s))
end
Then in my specs I can write such readable and succinct things as this:
Address.last.should be_identical(Address.new({city: 'City', country: 'USA'}))
I'm planning on forking the active_record_attributes_equality gem and changing it to use this behavior so that this can be more easily reused.
Some questions I have, though, include:
Does such a gem already exist??
What should the method be called? I don't think overriding the existing == operator is a good idea, so for now I'm calling it identical?. But maybe something like practically_identical? or attributes_eql? would be more accurate, since it's not checking if they're strictly identical (some of the attributes are allowed to be different.)...
attributes_to_ignore_when_comparing is too verbose. Not that this will need to be explicitly added to each model if they want to use the gem's defaults. Maybe allow the default to be overridden with a class macro like ignore_for_attributes_eql :last_signed_in_at, :updated_at
Comments are welcome...
Update: Instead of forking the active_record_attributes_equality, I wrote a brand-new gem, active_record_ignored_attributes, available at http://github.com/TylerRick/active_record_ignored_attributes and http://rubygems.org/gems/active_record_ignored_attributes
META = [:id, :created_at, :updated_at, :interacted_at, :confirmed_at]
def eql_attributes?(original,new)
original = original.attributes.with_indifferent_access.except(*META)
new = new.attributes.symbolize_keys.with_indifferent_access.except(*META)
original == new
end
eql_attributes? attrs, attrs2
I created a matcher on RSpec just for this type of comparison, very simple, but effective.
Inside this file:
spec/support/matchers.rb
You can implement this matcher...
RSpec::Matchers.define :be_a_clone_of do |model1|
match do |model2|
ignored_columns = %w[id created_at updated_at]
model1.attributes.except(*ignored_columns) == model2.attributes.except(*ignored_columns)
end
end
After that, you can use it when writing a spec, by the following way...
item = create(:item) # FactoryBot gem
item2 = item.dup
expect(item).to be_a_clone_of(item2)
# True
Useful links:
https://relishapp.com/rspec/rspec-expectations/v/2-4/docs/custom-matchers/define-matcher
https://github.com/thoughtbot/factory_bot
If like me you're looking for a Minitest answer to this question then here's a custom method that asserts that the attributes of two objects are equal.
It assumes that you always want to exclude the id, created_at, and updated_at attributes, but you can override that behaviour if you wish.
I like to keep my test_helper.rb clean so created a test/shared/custom_assertions.rb file with the following content.
module CustomAssertions
def assert_attributes_equal(original, new, except: %i[id created_at updated_at])
extractor = proc { |record| record.attributes.with_indifferent_access.except(*except) }
assert_equal extractor.call(original), extractor.call(new)
end
end
Then alter your test_helper.rb to include it so you can access it within your tests.
require 'shared/custom_assertions'
class ActiveSupport::TestCase
include CustomAssertions
end
Basic usage:
test 'comments should be equal' do
assert_attributes_equal(Comment.first, Comment.second)
end
If you want to override the attributes it ignores then pass an array of strings or symbols with the except arg:
test 'comments should be equal' do
assert_attributes_equal(
Comment.first,
Comment.second,
except: %i[id created_at updated_at edited_at]
)
end
Can anyone explain why this happens?
mybox:$ ruby script/console
Loading development environment (Rails 2.3.5)
>> foo = Foo.new
=> #<Foo id: nil, customer_id: nil, created_at: nil, updated_at: nil>
>> bar = Bar.new
=> #<Bar id: nil, bundle_id: nil, alias: nil, real: nil, active: true, list_type: 0, body_record_active: false, created_at: nil, updated_at: nil>
>> bar.save
=> false
>> bar.errors.each_full { |msg| puts msg }
Real can't be blank
Real You must supply a valid email
=> ["Real can't be blank", "Real You must supply a valid email"]
So far that is perfect, that is what i want the error message to read. Now for more:
>> foo.bars << bar
=> [#<Bar id: nil, bundle_id: nil, alias: nil, real: nil, active: true, list_type: 0, body_record_active: false, created_at: nil, updated_at: nil>]
>> foo.save
=> false
>> foo.errors.to_xml
=> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<errors>\n <error>Bars is invalid</error>\n</errors>\n"
That is what I can't figure out. Why am I getting Bars is invalid versus the error messages displayed above, ["Real can't be blank", "Real you must supply a valid email"] etc.
My controller simply has a respond_to method with the following in it:
format.xml { render :xml => #foo.errors, :status => :unprocessable_entity }
How do I have this output the real error messages so the user has some insight into what they did wrong? How do I write my render method in my controller to show all of the appropriate error messages?
I think you are using
validates_associated :bar in your foo.rb MODEL
so it only giving "Bars is invalid"
to check the error messages for bars either you have to do following in your
VIEW
<%= error_messages_for :foo, :bar %>
Controller
foo.bar.errors.to_xml
& to skip "bar is invalid" message put following method in foo.rb
def after_validation
# Skip errors that won't be useful to the end user
filtered_errors = self.errors.reject{ |err| %w{ bar }.include?(err.first) }
self.errors.clear
filtered_errors.each { |err| self.errors.add(*err) }
end
It's because the errors for bar are stored in the bar object. To get these errors you have to do something like this:
foo.bar.each do |bar|
bar.errors.each_full { |msg| puts msg }
end
It's all convoluted to me, but I haven't figured out the best way to get all the errors in one list (besides handling it my self). I understand the reasoning behind it (as each object should only know about it's own errors). What I usually do is extent ActiveRecord::Errors and create a new each_full_with_associations function that returns them all.
It all makes sense when you see it on a form with nested fields. In that case the errors are shown properly and all is good.
We used to overwrite an errors method in particular model, if we needed errors of child objects too, smth like that
class Foo < ActiveRecord::Base
alias :errors_without_children :errors
def errors
self.bars.each do |i|
i.errors.each_full do |msg|
errors_without_children.add_to_base msg
end
end
errors_without_children
end
end
You can still optimise it more. But this one already adds all bars objects' error messages to foo.