Permitting Nested Arrays using Strong Params in Rails
TLDR: Strong Params must permit nested arrays last!
Strong Parameters, aka Strong Params, are used in many Rails applications to increase the security of data sent through forms. Strong Params allow developers to specify in the controller which parameters are accepted and used. By permitting only the expected params, any unneeded or potentially dangerous params will be ignored and effectively filtered out. This is especially important during Active Model mass assignments where multiple params can be passed at once.
The purpose of this post is not to provide an in-depth explanation of how to use Strong Parameters but rather to offer insight into a very specific problem which can result when whitelisting nested arrays. To illustrate this scenario, how it might arise, and how to solve it, I’ll use a contrived example.
Let’s say we start out building a simple side-project app which starts out as a social network for home cooks. A user form might ask for information like first name, last name, email, birthday, and favorite recipes to make. Within the User controller, the Strong Params specified for this form might look something like this:
def user_parameters
parameters.require(:profile).permit(
:first_name,
:last_name,
:email,
:birthday,
favorite_recipes: [ ]
)
end
However, as development continues on your side-project, so does its scope. Now the app is a social network where chefs can connect with other chefs, and listing all their favorite recipes in a single list is too broad. Instead, they want to specify favorite recipes of various categories like breakfast, lunch, dinner and dessert.
In the form, these specific fields would look like any other form field, perhaps just under a “Favorite Recipes” section header. However, behind the scenes, these fields are grouped together within a parent favorite_recipes
parameter array. As a result, each of these form fields will return a nested array of favorite recipes for that specific category.
The Strong Params would become more complex as they now include a nested array:
def user_parameters
parameters.require(:profile).permit(
:first_name,
:last_name,
:birthday,
:favorite_recipes: [
breakfast: [ ],
lunch: [ ],
dinner: [ ]
]
)
end
Great, everything seems to be working fine. But, chefs are more than just the foods they prepare — they’re also the tools they use. So, let’s add a “favorite tools” section to the form where they can select from a list of kitchen tools like knives, blenders, microwave, etc. Based on user feedback, you also want to throw in another field about what culinary school they attended. The Strong Params might then be modified to be something like this:
def user_parameters
parameters.require(:profile).permit(
:first_name,
:last_name,
:birthday,
:favorite_recipes: [
breakfast: [ ],
lunch: [ ],
dinner: [ ]
],
favorite_tools: [ ],
:culinary_school
)
end
Perfect, now users can display which elite culinary school they attended for all other users to see. But wait, something seems to be wrong! Suddenly the User form doesn’t seem to be saving any information about favorite recipes. Why is that?
The reason this is happening is because the order in which Strong Params are permitted matters. Unless a conscious decision is made to organize Strong Params in a specific way, e.g. alphabetically, they tend to be added only as new fields are added to a form; the list of Strong Params often mirrors the structure of the form.
However, the order in which the fields appear in the form does not need to match the order in which params are permitted in the Strong Params. For example, if the field for last_name
was moved before first_name
in the form, the order of the Strong Params could still remain as it is with :first_name being permitted before :last_name. This is crucial to solving this specific issue with nested Strong Params.
Back to our problem; the reason our user_parameters is no longer whitelisting the nested params of favorite_recipes is because nested parameters must be permitted last! Changing the params in this way will fix the issue, and will allow users to successfully save all their super cool chef info.
def user_parameters
parameters.require(:profile).permit(
:first_name,
:last_name,
:birthday,
favorite_tools: [ ],
:culinary_school,
:favorite_recipes: [
breakfast: [ ],
lunch: [ ],
dinner: [ ]
]
)
end
This issue with Strong Parameters is very specific and can often be avoided by redefining how data is organized and avoiding nested arrays. However, for times when nested Strong Params seems to be the best solution, it is important to remember that nested parameters will be ignored within the Strong Parameters unless they are permitted last.
Header photo by Alberto Triano on Unsplash