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.
-
David Zhang