Rails 3 after_initialize не работает, когда create вызывается с блоком
Я пытаюсь установить некоторые значения по умолчанию для объекта с помощью 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 с блоком и без или по умолчанию значения, сбитые блоком?
UPDATE
Обнаружена проблема.
Оказывается, что вызов 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
Я не могу воспроизвести это. У меня есть приложение, удобное со следующим (упрощенным) классом:
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
s. Здесь соответствующая часть в 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
, если вы не осознаете это.