Structuring Multipart FormData with Rails Naming Conventions

As part of my studies at https://flatironschool.com/, I encountered several caveats when attempting to utilize multipart FormData with a Rails API backend. My use case involved images with accompanying descriptions and tags.

Strong Params

When using strong params in Rails, we permit.require(:controller_name), with :controller_name being the singular name of the controller that we are in.

When POSTing to a Rails API endpoint, a hash of :controller_name is automagically added to the root of the request by Rails’ router.

For example, consider the following route in routes.rb, as well as a corresponding fetch:

If we log params from within the users#create action, we find the body of our fetch automagically nested inside of a :user hash:

To utilize strong params and mass assignment, we require that :user hash and permit its desired attributes.

Strong Presumptions

Having always used similarly constructed fetches, I expected similar behavior from multipart FormData.

Let’s build a FormData, POST it and see what happens:

As it turns out, we aren’t transmitting JSON — we need to remove the header from our fetch request. Let’s try again.

Our file has transmitted, as has our name attribute, but params.require(:user) is refusing our request. What happened to our root level :user hash? It turns out Rails will not generate the root level hash without the JSON header — we have to build it into the FormData structure ourselves. For this, we must use the Rails parameter naming conventions: https://guides.rubyonrails.org/v3.2.13/form_helpers.html#understanding-parameter-naming-conventions

A final attempt:

Success! Our file and attribute has transmitted, we have a :user hash and params.require(:user) is happy.

Arrays

FormData is able to store arrays of values. To do so, we iteratively attach an item with the same name.

Let’s update our permitted params to allow an array of tag_ids, and attempt a POST.

That didn’t work. Despite our JS console log showing the array, Rails only kept the last tag_id and made it a key/value pair. Because .permit was expecting an array, it rejected the key/value pair.

The solution, again, is to use Rails parameter naming conventions while constructing the FormData. MDN notes this technique as “being compatible with PHP naming conventions”: https://developer.mozilla.org/en-US/docs/Web/API/FormData/append

Success!

Empty Arrays with Strong Params

Mass assignment with an empty array is a useful way to delete associations. For instance, to remove all associated tags from a user:

Let’s try a PATCH request with an empty tag_ids array:

That’s not quite what we wanted, but it did made it through the strong params filter. How would an ActiveRecord .update behave with [“null”]?

Rails fired a database query looking for a Tag with an id of 0 — interesting, but not at all helpful. The question remains: how can we get an empty array into our params?

Under the hood, forms are strings — the key/value pairs don’t have a concept of null. The solution is to assign a value of empty string, and let Rails do the rest:

How does ActiveRecord .update behave with [“”]? Exactly the same as [].

Success!

Full stack web developer with a passion for number theory and algorithms.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store