rails fields_for検証後の既存のレコード用の重複フォーム
-
28-10-2019 - |
質問
かなり奇妙なエラーに出くわしました。既存のレコードで検証が失敗した場合を除き、期待どおりに機能するネストフォームがあります。既存のレコードで検証が失敗すると、再レンダリングされた編集ビューには、無効なレコードのフィールドが2回含まれています。フィールドの最初のセットは、オブジェクトの現在保存方法に従って記入されます。フィールドの2番目のセットには、提出され、無効であることがわかった情報が記入されています。
私は、親(Shiftperiod)has_many子供(Shifts)と各子供が親に属している基本的なネストされた形を持っています。 ShiftPeriodは、Shiftsの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" %>
シフトオブジェクトの関連部分:
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子供(シフト)と各子供は親に属します。 ShiftPeriodは、Shiftsの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" %>
シフトオブジェクトの関連部分:
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
解決
これが私が今のところ問題を回避するために使用しているハックです。より良いアイデアは大歓迎です。
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
終わり