Rails fields_for дубликатов форм для существующих записей после проверки - PullRequest
3 голосов
/ 16 июня 2011

Я столкнулся с довольно странной ошибкой. У меня есть вложенная форма, которая работает, как и ожидалось, за исключением случаев, когда проверка не проходит на существующей записи. При сбое проверки существующей записи повторно обработанное представление редактирования содержит поля для недопустимой записи дважды. Первый набор полей заполняется в соответствии с тем, как объект хранится в данный момент. Второй набор полей заполняется информацией, которая была только что отправлена ​​и признана недействительной.

У меня есть базовая вложенная форма, в которой родитель (ShiftPeriod) имеет дочерних элементов (сдвиги) и каждый дочерний элемент принадлежит родительскому элементу. ShiftPeriod accept_nested_attributes для Shifts, где allow_destroy имеет значение true. Я использую гем nested_form, но я также пытался использовать обычный form_for с тем же результатом

Форма для ShiftPeriod (я удалил как можно больше, чтобы не усложнять, пока не выясню это):

<%= nested_form_for @shift_period do |f| %>
  <%= f.fields_for :shifts %>
  <%= f.link_to_add "Add shift", :shifts %>
  <%= f.submit %>
<% end %>

Парциальный с полями для смен:

<%= f.select :member_id, options_for_select(Member.crew_members.order('last_name').collect{|member| ["#{member.last_name}, #{member.first_name}", member.id]}, :selected => Member.where(:bars_num == 1).first.id) %>
<%= f.collection_select :start_time, @time_range, :dup, :hour, :selected => Time.parse(f.object.start_time.to_s) || @shift_period.start_time %>
<%= f.collection_select :end_time, @time_range, :dup, :hour, :selected => f.object.new_record? ? @time_range.last : Time.parse(f.object.end_time.to_s) %>
<%= f.select :repeat_month, options_for_select([['Never', false], ['Monthly', true]]) %>
<%= f.select :repeat, options_for_select([['Never', 0], ['Every Other Week', 1], ['Every Week', 2]]) %>
<%= f.link_to_remove "Remove" %>

Соответствующая часть объекта Shift:

class Shift < ActiveRecord::Base
  include Coverage::SetOperations

  belongs_to :member
  belongs_to :shift_period

  delegate :date, :to => :shift_period
  delegate :daynight, :to => :shift_period

  after_save :update_shift_period_open_slots
  after_destroy :update_shift_period_open_slots

  validates_presence_of :member, :start_time, :end_time, :shift_period

Соответствующая часть объекта ShiftPeriod:

class ShiftPeriod < ActiveRecord::Base
  has_many :shifts
  has_many :open_slots, :dependent => :destroy
  has_many :calls
  after_create :update_open_slots
  validates_presence_of :date
  validates :date, :uniqueness => {:scope => :daynight}

  accepts_nested_attributes_for :shifts, :reject_if => lambda {|a| a[:start_time].blank? || a[:end_time].blank? || a[:member_id].blank? || a[:repeat].blank? }, :allow_destroy => true

Контроллер: as_many children (Shifts) и каждый дочерний элемент принадлежит_ родителю. ShiftPeriod accept_nested_attributes для Shifts, где allow_destroy имеет значение true. Я использую гем nested_form, но я также пытался использовать обычный form_for с тем же результатом

Контроллер:

def edit
  @shift_period = ShiftPeriod.find(params[:id])
  set_time_range
end

def set_time_range
  @time_range = @shift_period.daynight ? (6..18).to_a : (18..23).to_a + (0..6).to_a
  @time_range.collect!{|val| @shift_period.start_time - @shift_period.start_time.hour.hours + val.hours }
end

def update
  @shift_period = ShiftPeriod.find(params[:id])
  respond_to do |format|
    if @shift_period.update_attributes(params[:shift_period])
      format.html { redirect_to(schedule_path(:date => @shift_period.date, :notice => 'Shift period was successfully updated')) }
      format.xml  { head :ok }
    else
      set_time_range
      format.html { render :action => "edit" }
      format.xml  { render :xml => @shift_period.errors, :status => :unprocessable_entity }
    end
  end
end

Форма для ShiftPeriod (я удалил как можно больше, чтобы не усложнять, пока не выясню это):

<%= nested_form_for @shift_period do |f| %>
  <%= f.fields_for :shifts %>
  <%= f.link_to_add "Add shift", :shifts %>
  <%= f.submit %>
<% end %>

Парциальный с полями для смен:

<%= f.select :member_id, options_for_select(Member.crew_members.order('last_name').collect{|member| ["#{member.last_name}, #{member.first_name}", member.id]}, :selected => Member.where(:bars_num == 1).first.id) %>
<%= f.collection_select :start_time, @time_range, :dup, :hour, :selected => Time.parse(f.object.start_time.to_s) || @shift_period.start_time %>
<%= f.collection_select :end_time, @time_range, :dup, :hour, :selected => f.object.new_record? ? @time_range.last : Time.parse(f.object.end_time.to_s) %>
<%= f.select :repeat_month, options_for_select([['Never', false], ['Monthly', true]]) %>
<%= f.select :repeat, options_for_select([['Never', 0], ['Every Other Week', 1], ['Every Week', 2]]) %>
<%= f.link_to_remove "Remove" %>

Соответствующая часть объекта Shift:

class Shift < ActiveRecord::Base
  include Coverage::SetOperations

  belongs_to :member
  belongs_to :shift_period

  delegate :date, :to => :shift_period
  delegate :daynight, :to => :shift_period

  after_save :update_shift_period_open_slots
  after_destroy :update_shift_period_open_slots

  validates_presence_of :member, :start_time, :end_time, :shift_period

Соответствующая часть объекта ShiftPeriod:

class ShiftPeriod < ActiveRecord::Base
  has_many :shifts
  has_many :open_slots, :dependent => :destroy
  has_many :calls
  after_create :update_open_slots
  validates_presence_of :date
  validates :date, :uniqueness => {:scope => :daynight}

  accepts_nested_attributes_for :shifts, :reject_if => lambda {|a| a[:start_time].blank? || a[:end_time].blank? || a[:member_id].blank? || a[:repeat].blank? }, :allow_destroy => true

Контроллер:

def edit
  @shift_period = ShiftPeriod.find(params[:id])
  set_time_range
end

def set_time_range
  @time_range = @shift_period.daynight ? (6..18).to_a : (18..23).to_a + (0..6).to_a
  @time_range.collect!{|val| @shift_period.start_time - @shift_period.start_time.hour.hours + val.hours }
end

def update
  @shift_period = ShiftPeriod.find(params[:id])
  respond_to do |format|
    if @shift_period.update_attributes(params[:shift_period])
      format.html { redirect_to(schedule_path(:date => @shift_period.date, :notice => 'Shift period was successfully updated')) }
      format.xml  { head :ok }
    else
      set_time_range
      format.html { render :action => "edit" }
      format.xml  { render :xml => @shift_period.errors, :status => :unprocessable_entity }
    end
  end
end

1 Ответ

1 голос
/ 17 июня 2011

Вот хак, который я сейчас использую, чтобы обойти проблему, более ценные идеи будут с благодарностью.

def update
  @shift_period = ShiftPeriod.find(params[:id])
  if @shift_period.update_attributes(params[:shift_period])
    redirect_to(schedule_path(:date => @shift_period.date, :notice => 'Shift period was successfully updated')) 
  else
    set_time_range

    new_records = []
    @shift_period.shifts.each{|shift| if shift.new_record? then new_records << shift end}
    @shift_period.shifts.slice!(0,@shift_period.shifts.length/2)
    @shift_period.shifts += new_records
    render :action => "edit"
  end

end

...