loading...

Rails Strong Params and Accepting Nested Parameters

christiankastner profile image Christian ・2 min read

Recently, I came across a very interesting macro in rails that gave me the undeniable "Rails is Magic" feeling that is amazing for any rails web dev. The macro in question is putting "accepts_nested_attributes_for" in your rails model.

I came across this when confronted with the problem of how to send nested data from a frontend react app where users could create events that chained together individual activities from a google places API search. Once the user had picked the activities they wanted to chain, I needed to send the data to the backend for model creation. Once the info was locked and loaded, I needed the rails backend to create the event with its associated date and user_id columns IN ADDITION TO however many activities the user had chained into this event. And each event had its associated photo, name, address, etc. columns that the backend had to populate and persist.

The head scratcher here is: what controller receives the post? Do we send a post to events and hard read the post params of each activity? Or do we send two posts one after the other using callbacks to appropriately queue the data? The first is out due to data security. Strong params exist fro a reason and to stray from them seems unwise since they protect against any data breaches in the code by limiting the information a user can pass in. The second is out because it just seems too unwieldy and repetitive.

The Answer, as you've probably guessed, is accepts_nested_params_for. I put this bad-boy in my events model and set it to accept params for ":activities" which looked like this:

class Event < ApplicationRecord
    has_many :activities, dependent: :destroy

    accepts_nested_attributes_for :activities
end

Having this allows the strong params for event to accept params for activities. After this, all that is required is to modify your strong params method in the controller of your controller. For me this was the event controller.

private

def event_params
    params.require(:event).permit(:date, :user_id, activities_attributes: [:name, :formatted_address, :icon, :types])
end

Calling the method "event_params" will now use render a hash with the permitted properties (date, user_id in my case) in addition to an array of our activities with their permitted properties (name, formatted_address, icon, and types).

Here it becomes very important that your post requests have a specific body. Rails threw errors when I tried to submit the body as:

body: {
   event: {
      date: ...,
      user_id: ...,
      activities: [...]
   }
}

After trial, error, and google perusing I found that the activities array must be named "activities_attributes" for the strong params to read the array.

body: {
   event: {
      date: ...,
      user_id: ...,
      activities_attributes: [...]
   }
}

Thus, my glorious solution to the post request. Two birds, one controller. You'll find that Rails is creating the object that corresponds to the controller its in in addition to all the objects that this object accepts attributes for. And the beauty is that this is done all with mass assignment, by just calling

Event.create(event_params)

And suddenly... you're feeling that rails magic wash over you. The rails app you thought you were working on is now a gleaming red mustang with the top down mounted on a deserted two lane road where space is only possibility.

Posted on by:

christiankastner profile

Christian

@christiankastner

Software engineer particularly interested in creative coding and machine learning.

Discussion

markdown guide
 

Hey Christian,
Here is my case:

params.require(:geo_datum)
.permit(:type, properties: [ :name, :amenity, :popupContent ],
geometry: [ :type, coordinates: [ :lat, :lng ] ]) 

I want to just change the last part to coordinates: [[[]]]
It didn't raised any error but still seems not working.
The data structure of the part is:

coordinates: [
[
[lat:Float, lng:Float],[lat:Float, lng:Float],[lat:Float, lng:Float],[lat:Float, lng:Float] ...
]
]

How can I figure this out?