How do I create a Factory that has multiple associations that rely on the same parent?
The Parent model:
class Parent < ActiveRecord::Base
  has_many :codes
  has_many :parent_filters
  validates :parent, :presence => true, :uniqueness => true
end
The Fitler model:
class Filter < ActiveRecord::base
  has_many :parent_filters
  validates :filter, :presence => true, :uniqueness => true
end
The ParentFilter join model:
class ParentFilter < ActiveRecord
  belongs_to :parent
  belongs_to :filter
  validates :filter, :presence => true
  validates :parent, :filter, :presence => true, :uniqueness => [ :scope => filter ]
end
The AdhocAttribute model:
class AdhocAttribute < ActiveRecord::Base
  has_many :adhoc_mappings
  has_many :codes, :through => :adhoc_mappings
  has_many :parent_filters, :through => adhoc_mappings
  validates :adhoc_attribute, :presence => true, :uniqueness => true
end
The code model:
class Code < ActiveRecord::Base
  belongs_to :parent
  has_many :adhoc_mappings
  has_many :adhoc_attributes, :through => :adhoc_mappings
  validates :code, :parent, presence: true
  validates :code, uniqueness: {case_sensitive: false}
end
And last but not least, an ad-hoc mapping model. This model allows for each code to be assigned one adhoc attribute per ParentFilter, which is the factory that I'm trying to create. This factory should require that the Parent for both the ParentFilter and the Code be the same (I'll add a custom validation to enforce that once I get a functional factory).
class AdHocMapping < ActiveRecord::Base
  belongs_to :code
  belongs_to :parent_filter
  belongs_to :adhoc_attribute
  has_one :company, :through => :parent_filter
  validates :code, :parent_filter, presence: true
  validates :code, :uniqueness => { :scope => :parent_filter }
end
If I were to create an AdhocMapping using straight ActiveRecord, I would build it up something like this:
p = Parent.where(:parent => "Papa").first_or_create
aa = AdhocAttribute(:adhoc_attribute => "Doodle").first_or_create
f = Filter.where(:filter => "Z..W").first_or_create
c = Code.where(:code => "ZYXW", :parent => p).first_or_create
pf = ParentFilter.where(:parent => p, :filter => f).first_or_create
am = AdhocMapping(:adhoc_attribute => aa, :parent_filter => pf, :code => :c).first_or_create
so that the Code and ParentFilter that is assigned to the AdhocMapping have the same Parent. I've tried a number of different methods to try to get this to work in FactoryGirl, but I can't seem to get it to create the object.
Here is the factory I thought was the closest to what I want:
require 'faker'
FactoryGirl.define do
  factory :adhoc_mapping do |f|
    transient do
     p Parent.where(:parent => Faker::Lorem.word).first_or_create
    end
    association :parent_filter, :parent => p
    association :code, :parent => p
    association :adhoc_attribute
  end
end
It gives an error of Trait not registered: p
For associations I usually use after(:build) in FactoryGirl, then I can exactly tell what should happen. (association xxx often did not work like I wanted it to, shame on me.)
So in your case I think you can solve it with this:
require 'faker'
FactoryGirl.define do
  factory :adhoc_mapping do |f|
    transient do
      default_parent { Parent.where(parent: Faker::Lorem.word).first_or_create }
      parent_filter {  FactoryGirl.build(:parent_filter, parent: default_parent) }
      code { FactoryGild.build(:code, parent: default_parent) }
      adhoc_attribute { FactoryGirl.build(:adhoc_attribute) }
    end
    after(:build) do |adhoc_mapping, evaluator|
      adhoc_mapping.parent_filter = evaluator.parent_filter
      adhoc_mapping.code = evaluator.code
      adhoc_mapping.adhoc_attribute = evaluator.adhoc_attribute
    end
  end
end
By using after(:build) it works with either FactoryGirl.create or FactoryGirl.build. In addition with the transient default_parent, you can even explicitly set the parent you want to have when you build your adhoc_mapping. And now after edit you can also pass nil to the attributes and it will not get overridden.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With