Rails 3 after_initialize не работает, когда create вызывается с блоком - PullRequest
11 голосов
/ 05 марта 2011

Я пытаюсь установить некоторые значения по умолчанию для объекта, используя after_initialize.У меня проблема в том, что я хотел бы, чтобы это вызывалось независимо от того, как создается объект.

Мой класс:

class Foo < ActiveRecord::Base

  serialize :data

  after_initialize :init

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

end

Все работает нормально, если я вызываю Foo.new,Foo.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 => nil вместо "baz" с другими атрибутамиустановлен правильно.

Я что-то упустил из-за того, как выполняются обратные вызовы, с различиями в вызове create с блоком и без, или значения по умолчанию блокируются блоком?

ОБНОВЛЕНИЕ

Обнаружена проблема.

Оказывается, что вызов create с блоком и без него несколько отличается.Когда вы вызываете create без блока и просто передаете хэш параметров, для всех намерений и целей вы вызываете Foo.new({<hash of argument>}).save, и обратный вызов after_initialize выполняется прямо перед сохранением, как вы ожидаете.

Когда вы звоните create с блоком, происходит что-то немного другое.Порядок событий Foo.new вызывается с любыми аргументами, которые вы передаете, затем вызывается after_initialize, затем запускается блок.Так что, если вы используете блок (как я) взаимозаменяемо с параметрами хеша, просто чтобы сделать вещи немного более читабельными, вы можете получить бит, потому что ваш after_initialize запускается до того, как все параметры, которые вы намереваетесь установить, фактически установлены.

Я получил немного, потому что я выполнял некоторую дополнительную работу в настройке after_initialize некоторых дополнительных обязательных атрибутов, основанных на значении того, что передавалось.Поскольку на самом деле ничего не было установлено, когда after_initialize был вызван, ничего не было установлено правильно, и мои проверки завершились неудачно.

В итоге мне пришлось делать вызовы на init.Один раз на after_initialize и один раз на before_validation.Не самый чистый, но это решило проблему.

Спасибо Брэндону за то, что он указал мне правильное направление.

1 Ответ

7 голосов
/ 05 марта 2011

Я не могу воспроизвести это. У меня есть приложение под рукой со следующим (упрощенным) классом:

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 без вас.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...