I have an app that has a model (called cases) that has_many documents associated with it. I have had no problems in creating the case and the associated documents. I had quite a bit of trouble when I was trying to replace one of the documents with and updated one. But I finally got that straightened out. Not sure if this is how a real programmer would do it, but it works for me.

cases_controller.rb

def index
    if permitted_to? :create
      @case = Case.new
      Document::DOC_CLASSES.each_with_index do |d, i|
      @case.documents.build(doc_class: Document::DOC_CLASSES[i])
    end
  end

  def edit
    @case = Case.find(params[:id])
  end

  def create
    @case = Case.new(case_params)
    if @case.save
      pid = fork do
        @case.generate_merged_document
      end
      redirect_to cases_path
    else
      redirect_to cases_path
    end
  end

  def update
    @case = Case.find(params[:id])
    if @case.update(case_params)
      pid = fork do
        @case.generate_merged_document
      end
    redirect_to cases_path
    else
      render action: 'edit'
    end
  end

A couple of things to note here. I’m making a new case from the index path. That’s just because it’s easier for this app. There’s more to the index method, but that’s the relevant part for this post. The Document::DOC_CLASSES.each goes through the list of documents that I’m allowed to have for each case. The only other different thing is my generate_merged_document method. Which does merge all the uploaded pdf files into one, because that’s something I needed to do.

case.rb

has_many :documents, dependent: :destroy
  accepts_nested_attributes_for :documents, allow_destroy: true

This is all I have in the model having to do with the document.

document.rb

belongs_to :case 
  has_attached_file :doc, path: "#{Rails.root}/storage/:attachment/:id/:filename"

This is all that’s in the document model having to do with the case.

To me, all the magic happened in the view. I have a partial that I use for the form.
_form.html.erb

<%= form_for(@case) do |f| %>
  Firstname: <%= f.text_field :firstname %>
Lastname: <%= f.text_field :lastname %>

<%= f.fields_for :documents do |g| %> <% if params[:action] == 'edit' %> <%= g.hidden_field :id %> <%= g.hidden_field :doc_class %> <% if g.object.doc_file_name %> <%= g.file_field :doc %> <%= g.object.doc_class %> (Current: <%= g.object.doc_file_name %>) <% else %> <%= g.file_field :doc %> <%= g.object.doc_class %> (Not uploaded) <% end %> <% else %> <%= g.file_field :doc %> <%= g.collection_select :doc_class, Document::DOC_CLASSES, :to_s, :titleize %> <% end %>
<% end %>

<%= f.submit 'Submit' %> <% end %>

So this is just making a regular form. But the fields for the nested document are different, depending on whether it’s and edit action or not. If it is an edit action, it’s putting the current id and doc_class in hidden fields (because those won’t change) and showing a file_field to enable the uploading of a new document. And just so I knew what I had, it’s showing the current document if one has already been uploaded. For a new case, it just shows a file_field for each possible document that could be uploaded.

Took me a while to get here, but I think this works pretty well. I know I’ll come across this again in the future, so I’m just documenting it for myself so I remember how to do it.