Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factory Girl with multiple associations with the same parent

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

like image 819
Joseph Freivald Avatar asked Nov 20 '15 17:11

Joseph Freivald


1 Answers

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.

like image 53
Markus Avatar answered Nov 15 '22 05:11

Markus