私の問題は、accepts_nested_attributes_forの制限に遭遇したことです。そのため、柔軟性を高めるために、その機能を自分で複製する方法を理解する必要があります。(正確に私を悩ませているものについては、以下を参照してください。)したがって、私の質問は、accepts_nested_attributes_forを模倣および拡張したい場合、フォーム、コントローラー、およびモデルはどのように見えるべきですか?本当の秘訣は、既存のモデルと新しいモデルの両方を既存の関連付け/属性で更新できるようにする必要があることです。
ネストされたフォームを使用するアプリを作成しています。私は当初、このRailsCastを青写真として使用しました(accepts_nested_attributes_forを活用):Railscast 196:ネストされたモデルフォーム。
私のアプリはジョブ(タスク)を含むチェックリストであり、ユーザーがチェックリスト(名前、説明)を更新し、関連するジョブを1つのフォームで追加/削除できるようにします。これはうまく機能しますが、これをアプリの別の側面、つまりバージョン管理による履歴に組み込むと、問題が発生します。
私のアプリの大きな部分は、モデルと関連付けの履歴情報を記録する必要があることです。私は自分のバージョン管理をロールバックすることになりました(これが私の決定プロセス/考慮事項を説明する私の質問です)、そしてその大部分は古いものの新しいバージョンを作成し、新しいバージョンを更新する必要があるワークフローです、古いバージョンをアーカイブします。これは、UIを介してモデルを更新するだけのエクスペリエンスであると見なすユーザーには見えません。
コード-モデル
#checklist.rb
class Checklist < ActiveRecord::Base
has_many :jobs, :through => :checklists_jobs
accepts_nested_attributes_for :jobs, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => true
end
#job.rb
class Job < ActiveRecord::Base
has_many :checklists, :through => :checklists_jobs
end
コード-現在のフォーム(注:@jobsは、チェックリストコントローラーの編集アクションでこのチェックリストのアーカイブされていないジョブとして定義されます。@ checklistも同様です)
<%= simple_form_for @checklist, :html => { :class => 'form-inline' } do |f| %>
<fieldset>
<legend><%= controller.action_name.capitalize %> Checklist</legend><br>
<%= f.input :name, :input_html => { :rows => 1 }, :placeholder => 'Name the Checklist...', :class => 'autoresizer' %>
<%= f.input :description, :input_html => { :rows => 3 }, :placeholder => 'Optional description...', :class => 'autoresizer' %>
<legend>Jobs on this Checklist - [Name] [Description]</legend>
<%= f.fields_for :jobs, @jobs, :html => { :class => 'form-inline' } do |j| %>
<%= render "job_fields_disabled", :j => j %>
<% end %>
</br>
<p><%= link_to_add_fields "+", f, :jobs %></p>
<div class="form-actions">
<%= f.submit nil, :class => 'btn btn-primary' %>
<%= link_to 'Cancel', checklists_path, :class => 'btn' %>
</div>
</fieldset>
<% end %>
コード-checklists_controller.rb#Updateからのスニペット
def update
@oldChecklist = Checklist.find(params[:id])
# Do some checks to determine if we need to do the new copy/archive stuff
@newChecklist = @oldChecklist.dup
@newChecklist.parent_id = (@oldChecklist.parent_id == 0) ? @oldChecklist.id : @oldChecklist.parent_id
@newChecklist.predecessor_id = @oldChecklist.id
@newChecklist.version = (@oldChecklist.version + 1)
@newChecklist.save
# Now I've got a new checklist that looks like the old one (with some updated versioning info).
# For the jobs associated with the old checklist, do some similar archiving and creating new versions IN THE JOIN TABLE
@oldChecklist.checklists_jobs.archived_state(:false).each do |u|
x = u.dup
x.checklist_id = @newChecklist.id
x.save
u.archive
u.save
end
# Now the new checklist's join table entries look like the old checklist's entries did
# BEFORE the form was submitted; but I want to update the NEW Checklist so it reflects
# the updates made in the form that was submitted.
# Part of the params[:checklist] has is "jobs_attributes", which is handled by
# accepts_nested_attributes_for. The problem is I can't really manipulate that hash very
# well, and I can't do a direct update with those attributes on my NEW model (as I'm
# trying in the next line) due to a built-in limitation.
@newChecklist.update_attributes(params[:checklist])
ここで、accepts_nested_attributes_forの制限に遭遇します(ここでかなり詳しく説明されています。基本的に設計どおりの「ID=YのModel2のID=XのModel1が見つかりませんでした」という例外が発生します。
では、複数のネストされたモデルを作成し、accepts_nested_attributes_forと同様に、親モデルのフォームでそれらを追加/削除するにはどうすればよいですか?
私が見たオプション-これらの最高の1つですか?本当の秘訣は、既存のモデルと新しいモデルの両方を既存の関連付け/属性で更新できるようにする必要があることです。リンクできないので、名前を付けます。
Redtape(github上)Virtus(またgithub)
ご協力いただきありがとうございます!