Rails - Wie verwalte ich verschachtelte Attribute ohne accept_nested_attributes_for?

Mein Problem ist, dass ich auf Einschränkungen von accept_nested_attributes_for gestoßen bin. Daher muss ich herausfinden, wie ich diese Funktionalität selbst replizieren kann, um mehr Flexibilität zu erreichen. (Siehe unten für genau das, was mich aufhängt.)Meine Frage lautet also: Wie sollten meine Form, mein Controller und meine Modelle aussehen, wenn ich accept_nested_attributes_for nachahmen und erweitern möchte? Der eigentliche Trick ist, dass ich in der Lage sein muss, sowohl vorhandene als auch neue Modelle mit vorhandenen Zuordnungen / Attributen zu aktualisieren.

Ich erstelle eine App, die verschachtelte Formulare verwendet. Ich habe diesen RailsCast ursprünglich als Vorlage verwendet (Nutzung von accept_nested_attributes_for):Railscast 196: Verschachtelte Modellform.

Meine App besteht aus Checklisten mit Jobs (Aufgaben), und ich lasse den Benutzer die Checkliste (Name, Beschreibung) aktualisieren und verknüpfte Jobs in einem einzigen Formular hinzufügen / entfernen. Das funktioniert gut, aber ich stoße auf Probleme, wenn ich dies in einen anderen Aspekt meiner App einbinde: den Verlauf über die Versionierung.

Ein großer Teil meiner App besteht darin, dass ich historische Informationen für meine Modelle und Assoziationen aufzeichnen muss. Am Ende habe ich meine eigene Versionierung gerollt (Hier ist meine Frage, in der ich meinen Entscheidungsprozess / meine Überlegungen beschreibe), und ein großer Teil davon ist ein Workflow, in dem ich eine neue Version eines alten Objekts erstellen, Aktualisierungen der neuen Version vornehmen und die alte Version archivieren muss. Dies ist für den Benutzer nicht sichtbar, der die Erfahrung als einfaches Aktualisieren eines Modells über die Benutzeroberfläche ansieht.

Code - Modelle

#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

Code - aktuelles Formular (HINWEIS: @jobs ist in der Bearbeitungsaktion des Checklisten-Controllers als nicht archivierte Jobs für diese Checkliste definiert, ebenso wie @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 %>

Code - Snippet von 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])

Und hier stoße ich zur Einschränkung auf accept_nested_attributes_ (es ist ziemlich gut dokumentiert)Hier. Ich erhalte die Ausnahme "Model1 mit ID = X für Model2 mit ID = Y konnte nicht gefunden werden", die im Grunde wie geplant ist.

Wie kann ich also mehrere verschachtelte Modelle erstellen und sie im Formular des übergeordneten Modells hinzufügen / entfernen, ähnlich wie dies accept_nested_attributes_for tut, aber auf eigene Faust?

Die Optionen, die ich gesehen habe - ist eine dieser besten?Der eigentliche Trick ist, dass ich in der Lage sein muss, sowohl vorhandene als auch neue Modelle mit vorhandenen Zuordnungen / Attributen zu aktualisieren. Ich kann sie nicht verlinken, also werde ich sie nur benennen.

Redtape (auf Github) Virtus (auch Github)

Danke für Ihre Hilfe!

Antworten auf die Frage(2)

Ihre Antwort auf die Frage