Stop FactoryGirl converting my strings to floats - ruby-on-rails

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)

Related

The `==` method in Ruby on Rails with `has_and_belongs_to_many` [duplicate]

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

Rails 4.2.5, simple_form, hstore, nested forms - Hstore Hash not persisting on save

I'm struggling to understand what's going on when I save my large-ish form - none of the Hstore parameters are saved to the database. Can someone point out what I presume to be the obvious error? (I've quickly trimmed a lot of the output to keep it concise as possible.
There's no console output, and it's not complaining about unpermitted params or anything. equipment field on the discipline model is the hstore column.
Given my strong params settings:
params.require(:profile).permit(
:id,
:user_id,
:gender,
:dob,
..snip..
disciplines_attributes: [
:id,
:profile_id,
:discipline_type,
:distance_in_meters_per_week,
..snip..
:equipment => [
:time_trial_bike,
:road_bike,
:turbo_trainer,
:watt_bike
]
]
My params:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"XX", "profile"=>
{"gender"=>"male", "dob(1i)"=>"1927", "dob(2i)"=>"3", "dob(3i)"=>"14",
"disciplines_attributes"=>{"0"=>
{"discipline_type"=>"swimming", "distance_in_meters_per_week"=>"",
"equipment"=>
{
"pull_buoy"=>"true",
"paddles"=>"false",
"wetsuit"=>"true",
"fins"=>"false",
"tempo_trainer"=>"false"
}}},
"commit"=>"Create Profile"}
Then my inspected resultant profile_params from the controller:
{"gender"=>"male", "dob(1i)"=>"1927", "dob(2i)"=>"3", "dob(3i)"=>"14", "height_in_meters"=>"",
"disciplines_attributes"=>{"0"=>{"discipline_type"=>"swimming", "distance_in_meters_per_week"=>"",
"equipment"=>{
"pull_buoy"=>"true",
"paddles"=>"false",
"wetsuit"=>"true",
"fins"=>"false",
"tempo_trainer"=>"false"}}
}
Can someone help me understand why the equipment hash is not committed to the database?
Discipline id: 148, profile_id: 50, discipline_type: "running", distance_in_meters_per_week: "", sessions_per_week: nil, time_per_session_in_minutes: nil, created_at: "2017-03-14 12:02:15", updated_at: "2017-03-14 12:02:15", equipment: nil>]
What is it about writing your own question, that always then leads you to finally understanding that, just maybe, you screwed up elsewhere?
Like overloading the Disciplines::initialize method to 'seed' the equipment hstore when it's a new record. But doing it on every instance, not just new records.
So in answer to my own question. The above does work, and if I'd bothered to check the DB directly, rather than relying on the console, I might have realised that:
after_initialize :setup_equipment
def initialize(search, options = {})
self.options = options
super
end
private
def setup_equipment
self.equipment = self.options
end
Was my problem all along. :(

Unable to set Rails model attribute from console or controller

I'm new to Rails and am working on getting an application set up in Rails 4.2.4. I have a model called List that looks like the following in the database (PostgresQL):
List(id: integer, user_id: integer, name: string, created_at: datetime, updated_at: datetime, friendly_name: string)
and in List.rb:
class List < ActiveRecord::Base
attr_accessor :name, :friendly_name
belongs_to :user
has_many :items
end
I am trying to modify the name attribute from a controller action:
def save_name
...
list_to_edit = List.find(params[:id].to_i)
list_to_edit.name = params[:name]
list_to_edit.save!
...
end
But the changes are not being persisted. I have confirmed that params[:name] and list_to_edit are not nil. When I try to change the attribute in the Rails console like this:
> l = List.last
> l.name = 'TestName'
> l.save!
I don't see any errors. After executing the above commands and executing l.name I do see TestName. When I type l or List.last, however I still see
#<List id: 29, user_id: 17, name: nil, created_at: "2015-11-07 18:55:04", updated_at: "2015-11-07 18:55:04", friendly_name: nil>
What do I need to do to set the name attribute of a List? I can post any additional file content if it is helpful.
After trying a few more things it looks like all I needed to do was remove name from the array being passed to attr_accessor in List.rb. I believe when I was trying to change the list name with my_list.name = 'something' I was modifying the instance variable, not the attribute stored in the database.

Rails: List Required Attributes For Create

I am manually creating objects in the rails console using Model.new(<attributes here>). Is there an easy way to list out which attributes a model will require me to include in order for the .save call to succeed?
I am running rails 4.2.3
You can get an array of validators using Model.validators. You'll have to parse this in some way to extract those validations for presence, something like:
presence_validated_attributes = Model.validators.map do |validator|
validator.attributes if validator.is_a?(ActiveRecord::Validations::PresenceValidator)
end.compact.flatten
I found a simpler way to accomplish the same thing:
When you do a failed create you can check the error message on the object.
# app/models/price.rb
class Price < ActiveRecord::Base
validates_presence_of :value
end
# in console
p = Price.new()
=> #<Price id: nil, created_at: nil, updated_at: nil, value: nil>
p.save
=> false
p.errors.messages
=> {:value=>["can't be blank"]}
In case you the mandatory attributes with error messages
book = Book.new
book.valid?
book.errors.messages
In case you just want the name of attributes without an error message
book = Book.new
book.valid?
book.errors.messages.keys

How to test for (ActiveRecord) object equality

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

Resources