使用块调用create时,Rails 3 after_initialize未运行

时间:2011-03-05 03:24:44

标签: ruby-on-rails-3 activerecord

我正在尝试使用after_initialize为对象设置一些默认值。我遇到的问题是,无论对象是如何创建的,我都希望调用它。

我的课程:

class Foo < ActiveRecord::Base

  serialize :data

  after_initialize :init

  def init
    self.data ||= {}
    self.bar ||= "bar"
    self.baz ||= "baz"
  end

end

如果我致电Foo.newFoo.new(:bar => "things")Foo.create(:baz => 'stuff'),一切正常。但是,当我使用create的块时,after_initialize回调无法运行。

obj = Foo.create do |f|
  f.bar = "words"
  f.data = { :attr_1 => 1, :attr_2 => 2 }
end

这只会产生obj.baz =&gt; <{1}}代替nil,其他属性设置正确。

我是否因为执行回调的方式而遗漏了某些内容,与使用块调用create并且没有或者是否被块阻止的默认值存在差异?

更新

发现了这个问题。

事实证明,使用块而不使用块调用"baz"会略有不同。当您在没有块的情况下调用create并且仅传入参数哈希时,出于所有意图和目的,您正在调用create,而Foo.new({<hash of argument>}).save回调在您之前的保存之前执行期待。

当你用一个块来调用after_initialize时会发生一些不同的事情。使用您传入的任何参数调用事件的顺序create,然后调用Foo.new,然后运行该块。因此,如果您正在使用块(就像我一样)与哈希参数互换,只是为了让事情更具可读性,您可以得到一点,因为在您打算设置的所有参数之前运行after_initialize集。

我有点因为我在after_initialize设置了一些额外的工作,根据传递的值来设置一些额外的必需属性。由于调用after_initialize时实际上没有设置任何内容,因此未正确设置任何内容并且我的验证失败。

我最终不得不打电话给after_initialize。进入init后进入after_initialize。不是最干净的,但它解决了这个问题。

感谢布兰登指出我正确的方向。

1 个答案:

答案 0 :(得分:7)

我无法重现这一点。我碰巧有一个应用程序方便使用以下(简化)类:

class Service < ActiveRecord::Base
  serialize        :data, Hash
  after_initialize :create_default_data
  attr_accessible  :data, :token

  protected

    def create_default_data
      self.data ||= Hash.new
    end
end

这是一个IRB会议:

ruby-1.9.2-p136 :001 > obj = Service.create do |s|
ruby-1.9.2-p136 :002 >     s.token = "abc"
ruby-1.9.2-p136 :003?>   end
 => #<Service id: 22, user_id: nil, type: nil, data: {}, created_at: "2011-03-05 04:18:00", updated_at: "2011-03-05 04:18:00", token: "abc"> 
ruby-1.9.2-p136 :004 > obj.data
 => {}

如您所见,data方法中after_initialize初始化为空哈希。 Rails代码表明这也是有意义的;在create

def create(attributes = nil, &block)
  if attributes.is_a?(Array)
    attributes.collect { |attr| create(attr, &block) }
  else
    object = new(attributes)
    yield(object) if block_given?
    object.save
    object
  end
end

因此create调用new并将值object分配给它yield。以下是new中的相关部分:

def initialize(attributes = nil)
  # truncated for space
  result = yield self if block_given?
  run_callbacks :initialize
  result
end

正如您所看到的,new在返回之前无条件地调用initialize回调,因此在create甚至屈服于您传递的块之前。 当您的块获取对象时,after_initialize方法已经执行

仔细检查(1)您的Rails版本是最新的(截至目前我认为是3.0.5)并且(2)没有任何设置baz而没有您意识到这一点。