Rails - Como gerenciar atributos aninhados sem usar accept_nested_attributes_for?

Meu problema é que me deparo com limitações de accept_nested_attributes_for, por isso preciso descobrir como replicar essa funcionalidade por conta própria para ter mais flexibilidade. (Veja abaixo exatamente o que está me desligando.)Então, minha pergunta é: como meu formulário, controlador e modelos devem ser se eu quiser imitar e aumentar accept_nested_attributes_for? O verdadeiro truque é que eu preciso atualizar os novos e novos modelos existentes com associações / atributos existentes.

Eu estou construindo um aplicativo que usa formulários aninhados. Inicialmente usei este RailsCast como um blueprint (aproveitando accept_nested_attributes_for):Railscast 196: Formulário de Modelo Aninhado.

Meu aplicativo é listas de verificação com tarefas (tarefas) e estou permitindo que o usuário atualize a lista de verificação (nome, descrição) e adicione / remova tarefas associadas em um único formulário. Isso funciona bem, mas me deparo com problemas quando incorporei isso em outro aspecto do meu aplicativo: histórico por meio de controle de versão.

Uma grande parte do meu aplicativo é que preciso registrar informações históricas para meus modelos e associações. Acabei rolando meu próprio versionamento (Aqui é a minha pergunta onde eu descrevo o meu processo de decisão / considerações), e uma grande parte disso é um fluxo de trabalho onde eu preciso criar uma nova versão de uma coisa antiga, fazer atualizações para a nova versão, arquivar a versão antiga. Isso é invisível para o usuário, que vê a experiência como simplesmente atualizando um modelo por meio da interface do usuário.

Código - modelos

#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

Código - formulário atual (OBSERVAÇÃO: @jobs é definido como trabalhos não-arquivados para esta lista de verificação na ação de edição do controlador das listas de verificação; o mesmo acontece com @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 %>

Código - snippet de 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])

E é aí que eu corro para o accept_nested_attributes_for limitação (está documentado muito bemAqui. Eu recebo a exceção "Não foi possível encontrar Model1 com ID = X para Model2 com ID = Y", que é basicamente como projetado.

Então, como posso criar vários modelos aninhados e adicioná-los / removê-los no formulário do modelo pai de forma semelhante ao que aceita_nested_attributes_for, mas por conta própria?

As opções que eu vi - é uma dessas melhores?O verdadeiro truque é que eu preciso atualizar os novos e novos modelos existentes com associações / atributos existentes. Eu não posso ligá-los, então eu vou nomeá-los.

Redtape (no github) Virtus (também github)

Obrigado pela ajuda!

questionAnswers(2)

yourAnswerToTheQuestion