Factory_girl、关联和 after_initialize 的问题

发布于 11-05 06:33 字数 1341 浏览 4 评论 0 原文

我有一个这样定义的 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

在我的 Factory.rb 中,我有:

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

在我的 family_spec.rb 中,我有

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

但这失败了:

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)>'

使用调试器,我可以看到当 after_initialize 被调用时 self.user 为零。为什么会发生这种情况? 如果我用 create 或 new 给家人打电话,一切都会正常。

感谢您的任何帮助。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

温柔女人霸气范 2024-11-12 06:33:11

这是我从 Joe Ferris 那里得到的答案:

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

Ben Hughes 的这个:

为了详细说明 Joe 所说的内容,调用了 after_initialize 方法
对象初始化后立即执行,而此时用户确实还没有
已设置。

例如,虽然这会起作用:

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

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

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

一般来说,你要非常小心地使用after_initialize,因为
请记住,这是在每个对象初始化时调用的。一家人的呼唤
on 1,000 个对象将导致调用 1,000 次。

听起来在这种情况下你最好使用
before_validation 而不是 after_initialize。

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

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

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) }
时光清浅 2024-11-12 06:33:11

由于 after_initialize 是在实例化新对象后触发的,并且factory_girl默认是通过调用new而不带任何参数来构建实例的,因此您必须使用""="" }="" com="" thoughtbot="" factory_girl="" blob="" v4.5.0="" getting_started.md#custom-construction"="">initialize_with 覆盖默认构建。

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

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
不喜欢何必死缠烂打 2024-11-12 06:33:11

我相信这是因为关联是惰性的,因此在“after_initialize”中还没有用户。

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

也许你可以直接从另一个工厂调用一个工厂,但我没有尝试这个,例如

f.user Factory(:user)

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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文