Problem with Factory_girl, association and after_initialize

生来就可爱ヽ(ⅴ<●) 提交于 2020-01-01 01:14:10

问题


I have a Family class so defined:

class Family < ActiveRecord::Base
  after_initialize :initialize_family
  belongs_to :user
  validates :user, 
       :presence => true

  validates :name,   
       :presence => true,          
       :length => { :maximum => 30 },
       :format => { :with => /\A[a-zA-Z0-9\-_\s\']+\z/i}

  def initialize_family
    if self.name.blank? && self.user
        self.name = "#{self.user.profile_full_name}'s Family"
    end
  end
end

In my factories.rb I have:

Factory.define :family do |f|
   f.association :user, :factory => :user
end

In my family_spec.rb I have

let(:family) { Factory(:family) }

But this fails with:

1) Family is valid with valid attributes
     Failure/Error: let(:family) { Factory(:family) }
     ActiveRecord::RecordInvalid:
       Validation failed: Name can't be blank, Name is invalid, Languages can't be blank, Languages is too short (minimum is 1 characters)
     # ./spec/models/family_spec.rb:8:in `block (2 levels) in <top (required)>'
     # ./spec/models/family_spec.rb:10:in `block (2 levels) in <top (required)>'

Using the debugger I can see that when after_initialize is called self.user is nil. Why is this happening? If I call the family with create or new everything works fine.

Thanks for any help.


回答1:


This is the answer I got from Joe Ferris:

factory_girl doesn't pass arguments to the constructor. It uses #user= on your model, and instantiates it without any arguments.

and this one from Ben Hughes:

To elaborate on what Joe is saying, after_initialize methods are called immediately upon object initialization, and that time indeed user has not been set.

So for example while this will work:

family = Family.create!(:user => @user) # or @user.families.create ...

This will not (which is what factory_girl is doing under the hood):

family = Family.new
family.user = @user
family.save!

Just in general you want to be real careful using after_initialize, as remember this is called on every object initialization. A Family.all call on 1,000 objects will cause that to get called 1,000 times.

Sounds like in this instance you might be better of using a before_validation instead of after_initialize.

The following syntax also works for testing in rspec:

let (:family) { Family.create(:user => @user) }



回答2:


Since after_initialize is triggered after new objects are instantiated and factory_girl builds instances by calling new without any arguments by default, you must use initialize_with to overwrite the default build.

FactoryGirl.define do
  factory :family do
    initialize_with { new(user: build(:user)) }
  end
end



回答3:


I believe that it's because the association is lazy, thus in the "after_initialize" there's no user yet.

http://rdoc.info/github/thoughtbot/factory_girl/v1.3.3/file/README.rdoc

Perhaps you can directly call one factory from another, but I didn't try this, e.g.

f.user Factory(:user)


来源:https://stackoverflow.com/questions/5916162/problem-with-factory-girl-association-and-after-initialize

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!