Rails nested_attributes_for не работает при обновлении

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

Я пытаюсь сохранить вложенные атрибуты для модели в rails 3.2.11, но операция работает только при создании вложенного объекта, но не при его обновлении.

Вот код для двух моделей, с которыми я работаю

class Property < ActiveRecord::Base

  has_many :opening_times
  accepts_nested_attributes_for :opening_times, :allow_destroy => true
  attr_accessible :opening_times_attributes

...

class OpeningTime < ActiveRecord::Base
  belongs_to :property
  attr_accessible :start_date, :end_date

  attr_accessible :day, :start_time, :end_time
  attr_writer :day, :start_time, :end_time

  before_save :set_dates

...

  def set_dates
    day = Date.parse(@day)
    start_time = Time.parse(@start_time)
    end_time = Time.parse(@end_time)

    start_date = "#{day.day}/#{day.month}/#{day.year} #{start_time.hour}:#{start_time.min}"
    self.start_date = DateTime.parse(start_date)

    end_date = "#{day.day}/#{day.month}/#{day.year} #{end_time.hour}:#{end_time.min}"
    self.end_date = DateTime.parse(end_date)
  end

Поэтому, когда я пытаюсь создать время открытия через свойство с помощью консоли rails, это работает:

1.9.3p125 :006 > p = Property.find(9)
1.9.3p125 :006 > p.opening_times_attributes = [{"day"=>"27/02/2013", "start_time"=>"11:30",     "end_time"=>"12:30", "_destroy"=>"false"}]
 => [{"day"=>"27/02/2013", "start_time"=>"11:30", "end_time"=>"12:30", "_destroy"=>"false"}] 
1.9.3p125 :007 > p.save!
 (0.1ms)  begin transaction
 Suburb Load (0.2ms)  SELECT "suburbs".* FROM "suburbs" WHERE "suburbs"."name" = 'BARREN GROUNDS' LIMIT 1
 SQL (0.6ms)  INSERT INTO "opening_times" ("end_date", "property_id", "start_date") VALUES (?, ?, ?)  [["end_date", Wed, 27 Feb 2013 12:30:00 UTC +00:00], ["property_id", 9], ["start_date", Wed, 27 Feb 2013 11:30:00 UTC +00:00]]
 (2.8ms)  commit transaction
 => true 

Но когда я пытаюсь обновить существующий вложенный объект (передавая идентификатор в хеше), он ничего не делает.

1.9.3p125 :037 > p.opening_times
 => [#<OpeningTime id: 12, property_id: 9, start_date: "2013-02-27 11:00:00", end_date: "2013-02-27 13:00:00">] 
1.9.3p125 :038 > p.opening_times_attributes = [{"day"=>"27/02/2013", "start_time"=>"11:30", "end_time"=>"12:30", "_destroy"=>"false", "id"=>12}]
 => [{"day"=>"27/02/2013", "start_time"=>"11:30", "end_time"=>"12:30", "_destroy"=>"false", "id"=>12}] 
1.9.3p125 :039 > p.save!
(0.1ms)  begin transaction
Suburb Load (0.2ms)  SELECT "suburbs".* FROM "suburbs" WHERE "suburbs"."name" = 'BARREN GROUNDS' LIMIT 1
(0.1ms)  commit transaction
 => true 
1.9.3p125 :040 > exit

Основываясь на том, что я прочитал до сих пор (например, http://archives.ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes), это должно работать для обоих операции.

Любая идея о том, что я делаю неправильно?

Спасибо!

[РЕДАКТИРОВАТЬ]

Основываясь на предложении @jvnill, кажется, что обратный вызов before_save не вызывается при обновлении. Я добавил обходной путь для явного вызова set_dates при обновлении любого из полей, и тогда он работает.

def day=(day)
  @day = day
  set_dates unless (@day.blank? || @start_time.blank? || @end_time.blank?)
end

def start_time=(start_time)
  @start_time = start_time
  set_dates unless (@day.blank? || @start_time.blank? || @end_time.blank?)
end

def end_time=(end_time)
  @end_time = end_time
  set_dates unless (@day.blank? || @start_time.blank? || @end_time.blank?)
end

Это не решает проблему полностью, поскольку теперь проверка не работает гладко, и кажется, что мне приходится вручную выполнять работу, которую должен выполнять AR.


person frankmt    schedule 18.02.2013    source источник
comment
я предполагаю, что вы строите время начала и окончания обратного вызова set_dates. Вы можете проверить этот метод и убедиться, что обновление проходит без проблем?   -  person jvnill    schedule 18.02.2013
comment
Я проверил, и этот метод не вызывается при обновлении. Однако при создании (как в примере) это происходит нормально.   -  person frankmt    schedule 18.02.2013
comment
так вот где твоя проблема. Вы можете включить этот метод в свой вопрос?   -  person jvnill    schedule 18.02.2013
comment
не уверен, что я следую, но я добавил метод к вопросу. Спасибо   -  person frankmt    schedule 18.02.2013


Ответы (3)


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

Итак, это работает?

> o = p.opening_times.first
> o.update_attributes({"day"=>"27/02/2013", "start_time"=>"11:30", "end_time"=>"12:30"})

Если нет, отключите свои проверки и обратные вызовы и посмотрите, правильно ли проходят вложенные_атрибуты.

ИЗМЕНИТЬ:

Итак, похоже, проблема связана с вызовом вложенных атрибутов. Что произойдет, если вы попробуете это?

> p.update_attributes(opening_times_attributes: [{"day"=>"27/02/2013", "start_time"=>"11:30", "end_time"=>"12:30", "_destroy"=>"false", "id"=>12} ])

Обратите внимание, что я звоню update_attributes, а не устанавливаю opening_times_attributes

РЕДАКТИРОВАТЬ 2:

Если вы можете уничтожить запись с помощью вложенных атрибутов, я подозреваю, что что-то отклоняет ваши изменения в дочерней модели. Вы проверили на наличие ошибок проверки? Вы пытались обновить только 1 атрибут?

person Dane O'Connor    schedule 18.02.2013
comment
(0,1 мс) начать транзакцию (0,3 мс) UPDATE opens_times SET start_date = '2013-02-27 11:30:00.000000', end_date = '2013-02-27 12:30:00.000000' ГДЕ open_times.id = 12 (2.4 мс) зафиксировать транзакцию - person frankmt; 18.02.2013
comment
Ничего снова..(0,1 мс) начать транзакцию Загрузить пригород (0,2 мс) ВЫБЕРИТЕ пригород.* ИЗ пригородов ГДЕ пригороды.имя = 'БЕСПОДНЫЕ ЗЕМЛИ' LIMIT 1 (0,1 мс) зафиксировать транзакцию - person frankmt; 18.02.2013
comment
Ты можешь уничтожить запись? - person Dane O'Connor; 18.02.2013
comment
Чешу затылок... Если вы отключите обратный вызов set_date, сможете ли вы обновлять через вложенные атрибуты? - person Dane O'Connor; 18.02.2013
comment
Только что обновил вопрос. Если я заставляю атрибуты обновляться при изменении виртуальных, то сохранение работает, поэтому кажется, что это связано с выполнением обратного вызова before_save. - person frankmt; 18.02.2013

Вдохновленный приведенными выше ответами, я понял, что вы должны явно указать AR, что поле изменяется, когда установлен какой-то виртуальный атрибут, иначе обратный вызов before_save никогда не вызывается.

Это не очень хорошо, но обходной путь, который я нашел для этой проблемы, заключается в вызове {attr}_will_change! всякий раз, когда был установлен виртуальный атрибут:

def day=(val)
  unless val == self.day
    start_date_will_change!
    end_date_will_change!
  end
  @day = val
end

def start_time=(val)
  start_date_will_change! unless val == self.start_time
  @start_time = val
end

def end_time=(val)
  end_date_will_change! unless val == self.end_time
  @end_time = val
end

Спасибо всем за ваши ответы!

person frankmt    schedule 18.02.2013

Кажется, теперь я знаю вашу проблему. обратные вызовы _save не запускаются, если в записи ничего не изменилось. поскольку вы вызываете before_save, ничего не меняя в родительской модели, обратный вызов не запускается. попробуйте использовать before_validation вместо before_save.

person jvnill    schedule 18.02.2013
comment
Если это так, это может быть проблемой, но изменение его на before_validation также не сработало. Я пытался обновить его вместе с чем-то на модели, и это все еще не работает. - person frankmt; 18.02.2013
comment
Только что обновил вопрос. Если я заставляю атрибуты обновляться при изменении виртуальных, то сохранение работает, поэтому кажется, что это связано с выполнением обратного вызова before_save. - person frankmt; 18.02.2013
comment
Вы можете попробовать первый ответ в этом вопросе? stackoverflow.com/ вопросы/11460667/ - person jvnill; 18.02.2013
comment
Спасибо! Это точное решение не сработало (time_entry не существует?), но решило проблему, вызвав {attr}_will_change для полей, затронутых в каждом установщике виртуальных атрибутов. Опубликую ответ на него, но, к сожалению, придется ждать его 5 часов (недостаточно репутации! :P) Большое спасибо за вашу помощь. - person frankmt; 18.02.2013