Paperclip and Nested Attributes
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 %>
<%= f.submit 'Submit' %> <% end %>
<% 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.