Kazi Noshin

Nested file uploads in Rails

This post is about how I solved a form submission with multiple files from a nested fields_for by explicitly adding multipart: true on the parent form_with.

I have been building a secondhand marketplace for Bangladesh. Although I am a front-end developer, I needed a full-stack framework that would let me deploy as quickly as possible. I picked Ruby on Rails for building. It seemed like the fastest way to release something.

Recently, I was working on a nested form for a one-to-one relationship:

class Product < ApplicationRecord
  has_one :book
  has_many_attached :images
end


class Book < ApplicationRecord
  belongs_to :product
end

I wanted to render a form for multiple images for the Post model. According to the guide, this is how you do it:

<%= form_with model: book do |form| %>
  <%= form.label :pages %>
  <%= form.number_field :pages %>

  <div>
    <%= fields_for :product, book.product do |product_form| %>
      <%= product_form.label :images %>
      <%= product_form.file_field :images, multiple: true, accept: "image/*" %>

      <%= product_form.label :name %>
      <%= product_form.text_field :name %>
     <% end %>
  </div>

  <%= form.submit %>
<% end %>

It wouldn't work.

After a while, I wondered if the encoding attribute, enctype, was set correctly. For uploading files through a form, the enctype must be set to multipart/form-data.

According to the Rails API documentation, using file_field within a form_with automatically sets the encoding of the form to multipart/form-data.

I inspected the source for the form. There was no enctype set on the generated form!

The fix for this issue was adding multipart: true to the form.

+<%= form_with model: book, multipart: true do |form| %>
-<%= form_with model: book, do |form| %>
   <%= form.label :pages %>
   <%= form.number_field :pages %>

   <div>
     <%= fields_for :product, book.product do |product_form| %>
       <%= product_form.label :images %>
       <%= product_form.file_field :images, multiple: true, accept: "image/*" %>

       <%= product_form.label :name %>
       <%= product_form.text_field :name %>
      <% end %>
   </div>

   <%= form.submit %>
 <% end %>

The fields_for around the file_field must have prevented the default behaviour of setting the encoding for the form.

I might not have figured this out if I didn't know forms require the correct encoding type to be set. Goes to show how important it is to understand the foundational web technologies if you are building for the web.