I want to use FactoryGirl.attributes_for in controller testing, as in:
it \"raise error creating a new PremiseGroup for this user\" do
expect {
post :c
I think this is a slight improvement over fearless_fool's answer, although it depends on your desired result.
Easiest to explain with an example. Say you have lat and long attributes in your model. On your form, you don't have lat and long fields, but rather lat degree, lat minute, lat second, etc. These later can converted to the decimal lat long form.
Say your factory is like so:
factory :something
lat_d 12
lat_m 32
..
long_d 23
long_m 23.2
end
fearless's build_attributes
would return { lat: nil, long: nil}
. While the build_attributes
below will return { lat_d: 12, lat_m: 32..., lat: nil...}
def build_attributes
ba = FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "created_at", "updated_at"].member?(k)
end
af = FactoryGirl.attributes_for(*args)
ba.symbolize_keys.merge(af)
end
The accepted answer seems outdated as it did not work for me, after digging through the web & especially this Github issue, I present you:
This creates :belongs_to
associations and adds their id
(and type
if :polymorphic
) to the attributes. It also includes the code through FactoryBot::Syntax::Methods
instead of an own module limited to controllers.
spec/support/factory_bot_macros.rb
module FactoryBot::Syntax::Methods
def nested_attributes_for(*args)
attributes = attributes_for(*args)
klass = args.first.to_s.camelize.constantize
klass.reflect_on_all_associations(:belongs_to).each do |r|
association = FactoryBot.create(r.class_name.underscore)
attributes["#{r.name}_id"] = association.id
attributes["#{r.name}_type"] = association.class.name if r.options[:polymorphic]
end
attributes
end
end
this is an adapted version of jamesst20 on the github issue - kudos to him
Here is another way:
FactoryGirl.build(:car).attributes.except('id', 'created_at', 'updated_at').symbolize_keys
Limitations:
create
, as in association :user, strategy: :create
. This strategy can make your factory very slow if you don't use it wisely.To further elaborate on the given build_attributes
solution, I modified it to only add the accessible associations:
def build_attributes(*args)
obj = FactoryGirl.build(*args)
associations = obj.class.reflect_on_all_associations(:belongs_to).map { |a| "#{a.name}_id" }
accessible = obj.class.accessible_attributes
accessible_associations = obj.attributes.delete_if do |k, v|
!associations.member?(k) or !accessible.include?(k)
end
FactoryGirl.attributes_for(*args).merge(accessible_associations.symbolize_keys)
end
By design, FactoryGirl's attribues_for
intentionally omits things that would trigger a database transaction so tests will run fast. But you can can write a build_attributes
method (below) to model all the attributes, if you're willing to take the time hit.
Digging deep into the FactoryGirl documentation, e.g. this wiki page, you will find mentions that attributes_for
ignores associations -- see update below. As a workaround, I've wrapped a helper method around FactoryGirl.build(...).attributes
that strips id
, created_at
, and updated_at
:
def build_attributes(*args)
FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "created_at", "updated_at"].member?(k)
end
end
So now:
>> build_attributes(:premise_group)
=> {"name"=>"PremiseGroup_21", "user_id"=>29, "is_visible"=>false, "is_open"=>false}
... which is exactly what's expected.
Having absorbed the comments from the creators of FactoryGirl, I understand why attributes_for
ignores associations: referencing an association generates a call to the db which can greatly slow down tests in some cases. But if you need associations, the build_attributes
approach shown above should work.