Factory_girl,关联和after_initialize问题

Mat*_*ani 21 ruby-on-rails-3 factory-bot

我有一个如此定义的Family类:

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
Run Code Online (Sandbox Code Playgroud)

在我的工厂里.我有:

Factory.define :family do |f|
   f.association :user, :factory => :user
end
Run Code Online (Sandbox Code Playgroud)

在我的family_spec.rb中,我有

let(:family) { Factory(:family) }
Run Code Online (Sandbox Code Playgroud)

但这失败了:

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)>'
Run Code Online (Sandbox Code Playgroud)

使用调试器我可以看到,当调用after_initialize时,self.user为nil.为什么会这样?如果我用创建或新的方式打电话给家人一切正常.

谢谢你的帮助.

Mat*_*ani 24

这是我从乔·费里斯那里得到的答案:

factory_girl不会将参数传递给构造函数.它在您的模型上使用#user =,并在没有任何参数的情况下实例化它.

而这一个来自Ben Hughes:

为了详细说明Joe所说的内容,在对象初始化时立即调用after_initialize方法,并且确实没有设置用户的时间.

例如,虽然这将工作:

family = Family.create!(:user => @user) # or @user.families.create ...
Run Code Online (Sandbox Code Playgroud)

这不会(这是factory_girl在幕后做的事情):

family = Family.new
family.user = @user
family.save!
Run Code Online (Sandbox Code Playgroud)

一般来说,你想要使用after_initialize时要小心谨慎,因为记住这是在每个对象初始化时调用的.对1,000个对象进行Family.all调用将导致被调用1,000次.

听起来像在这种情况下你可能更好地使用before_validation而不是after_initialize.

以下语法也适用于rspec中的测试:

let (:family) { Family.create(:user => @user) }
Run Code Online (Sandbox Code Playgroud)


Gui*_*iGS 18

由于after_initialize在实例化新对象后触发,并且factory_girl new默认情况下通过不带任何参数调用来构建实例,因此必须使用initialize_with覆盖默认构建.

FactoryGirl.define do
  factory :family do
    initialize_with { new(user: build(:user)) }
  end
end
Run Code Online (Sandbox Code Playgroud)

  • 也可以像这样使用“ initialize_with {new(attributes)}”来调用 (3认同)
  • 虽然Matteo回答了为什么,这是实际的如何. (2认同)