FactoryGirl: 'has_and_belongs_to_many' associations and the 'NoMethodError'
We ran into a somewhat frustrating problem while using Factory Girl to create an object which had a 'has_and_belongs_to_many' association with another object.
The relevant code in the two classes was like this..
class Bar < ActiveRecord::Base
has_and_belongs_to_many :foos, :class_name => "Foo", :join-table => "bar_foos"
end
class Foo < ActiveRecord::Base
has_many :bars
end
…and we originally defined our 'Bar' factory like so:
Factory.define :bar do |f|
f.association(:foos, :factory => :foo)
end
Factory.define :foo do |f|
...
end
On calling the following in our test
Factory(:bar)
we ended up with this stack trace:
NoMethodError in 'SomeController GET for some action'
undefined method `each' for #<Bar:0x102faabd8>
/Users/mneedham/.rvm/gems/jruby-1.5.1/gems/activerecord-2.3.5/lib/active_record/attribute_methods.rb:260:in `method_missing'
/Users/mneedham/.rvm/gems/jruby-1.5.1/gems/activerecord-2.3.5/lib/active_record/associations/association_collection.rb:320:in `replace'
/Users/mneedham/.rvm/gems/jruby-1.5.1/gems/activerecord-2.3.5/lib/active_record/associations.rb:1325:in `foos='
/Users/mneedham/.rvm/gems/jruby-1.5.1/gems/factory_girl-1.3.2/lib/factory_girl/proxy/build.rb:13:in `send'
/Users/mneedham/.rvm/gems/jruby-1.5.1/gems/factory_girl-1.3.2/lib/factory_girl/proxy/build.rb:13:in `set'
/Users/mneedham/.rvm/gems/jruby-1.5.1/gems/factory_girl-1.3.2/lib/factory_girl/proxy/build.rb:17:in `associate'
/Users/mneedham/.rvm/gems/jruby-1.5.1/gems/factory_girl-1.3.2/lib/factory_girl/attribute/association.rb:15:in `add_to'
/Users/mneedham/.rvm/gems/jruby-1.5.1/gems/factory_girl-1.3.2/lib/factory_girl/factory.rb:324:in `run'
/Users/mneedham/.rvm/gems/jruby-1.5.1/gems/factory_girl-1.3.2/lib/factory_girl/factory.rb:322:in `each'
/Users/mneedham/.rvm/gems/jruby-1.5.1/gems/factory_girl-1.3.2/lib/factory_girl/factory.rb:322:in `run'
/Users/mneedham/.rvm/gems/jruby-1.5.1/gems/factory_girl-1.3.2/lib/factory_girl/factory.rb:250:in `build'
/Users/mneedham/SandBox/ruby/some_controller_spec.rb:7:
The problem is that the Active Record code assumes that it will be passed an array when the 'foo=' method is called on the proxy 'Bar' object it creates. Unfortunately we’re passing a single 'Foo'.
Instead we need to use the more verbose syntax and wrap the call to association in an array:
Factory.define :bar do |f|
f.foos { |a| [a.association(:bar)] }
end
Dante Regis has written about this before but I found it sufficiently sufficiently frustrating that I thought I’d document it as well.
About the author
I'm currently working on short form content at ClickHouse. I publish short 5 minute videos showing how to solve data problems on YouTube @LearnDataWithMark. I previously worked on graph analytics at Neo4j, where I also co-authored the O'Reilly Graph Algorithms Book with Amy Hodler.