Rails fields_for 验证后现有记录的重复表单

发布于 2024-11-15 22:22:52 字数 6152 浏览 4 评论 0原文

我遇到了一个相当奇怪的错误。我有一个嵌套表单,它可以按预期工作,除非对现有记录进行验证失败。当现有记录验证失败时,重新呈现的编辑视图将两次包含无效记录的字段。第一组字段根据对象当前存储的方式填写。第二组字段填写的是刚刚提交并发现无效的信息。

我有一个基本的嵌套形式,其中父级(ShiftPeriod)有很多子级(Shifts),并且每个子级都属于父级。 ShiftPeriod 接受 Shift 的_nested_attributes,并将allow_destroy 设置为true。我正在使用nested_form gem,但我也尝试使用常规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 子级(Shifts)并且每个子级都属于父级。 ShiftPeriod 接受 Shift 的_nested_attributes,并将allow_destroy 设置为true。我正在使用nested_form gem,但我也尝试使用常规的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

I've come across a rather bizarre error. I have a nested form that works as expected except when a validation fails on an existing record. When a validation fails on an existing record, the re-rendered edit view contains fields for the invalid record twice. The first set of fields is filled out according to the way the object is currently stored. The second set of fields is filled out with the information that was just submitted and found to be invalid.

I have a basic nested form where a parent (ShiftPeriod) has_many children (Shifts) and each child belongs_to the parent. ShiftPeriod accepts_nested_attributes for Shifts, with allow_destroy set to true. I'm using the nested_form gem, but I also tried using a regular form_for with the same result

The form for ShiftPeriod (I removed as much as possible to try to keep it simple until i figure this out):

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

The partial with the fields for shifts:

<%= 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" %>

The relevant part of the Shift object:

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

The relevant part of the ShiftPeriod object:

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

The controller:
as_many children (Shifts) and each child belongs_to the parent. ShiftPeriod accepts_nested_attributes for Shifts, with allow_destroy set to true. I'm using the nested_form gem, but I also tried using a regular form_for with the same result

The controller:

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

The form for ShiftPeriod (I removed as much as possible to try to keep it simple until i figure this out):

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

The partial with the fields for shifts:

<%= 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" %>

The relevant part of the Shift object:

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

The relevant part of the ShiftPeriod object:

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

The controller:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

書生途 2024-11-22 22:22:52

这是我现在用来解决这个问题的技巧,更好的想法将不胜感激。

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

结尾

Here is the hack I'm using for now to get around the problem, better ideas would be greatly appreciated.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文