How to merge nested attributes in strong params with has_many association - ruby-on-rails

In my controller of a rails 6 app I have the following strong parameter:
params.require(:item).permit(:summary, tasks_attributes: [:id, :name])
I want to merge the following into the tasks attributes:
user_account_id: user_account.id
account_id: current_account.id
I've tried the following with no success:
params.require(:item).permit(:summary, tasks_attributes: [:id, :name])
.reverse_merge(tasks_attributes: [user_account_id: user_account.id, account_id: current_account.id]
If I try
.reverse_merge(account_id: current_account.id)
It successfully merges into the item, but no such luck trying to get it into the tasks attributes. Other posts mentioned the reverse_merge but assuming they work in a has_one/belong to relationship.
If not possible in the strong params, I would have to do something like the following after pulling in the params:
#item.tasks.each { |task| task.user_account_id = user_account.id }

You need to loop through the nested attributes and merge each attributes hash:
def item_params
params.require(:item)
.permit(:summary, tasks_attributes: [:id, :name])
.tap do |wl|
wl.tasks_attributes.each do |hash|
hash.reverse_merge!(
user_account_id: user_account.id,
account_id: current_account.id
)
end
end
end
AFAIK ActionController::Parameters does not really have a built in utility for doing what you want and its really outside the scope of what strong parameters is designed for which is to whitelist parameters for mass assignment.

Related

Permit array of strong params without using nested attributes

I have an array of users I want to save in my database. An example when shown in the console after a POST is made:
"users"=>[{"name"=>"Job", "email"=>"*****"}, {"name"=>"Ed", "email"=>"****"}]
No nested attributes going on here. How to format strong params for this?
def user_params
params.fetch(:user, {}).permit(:name, :email, ...)
end
This may work for saving a single user but I'm passing an array of users. I'm currently storing the user in a "dirty" manner:
users = params[:users]
users.each do |user|
User.create(name: user[:name], email: user[:email])
end
I'd like to refactor and do something like:
User.create(user_params)
To permit a param key containing an array of hashes you pass the name as the key and an array of permitted attributes:
def user_params
params.permit(users: [:name, :email, ...])
.fetch(:users, [])
end
The output will be an array of ActionController::Parameters instances or an empty array if the key is not present:
[#<ActionController::Parameters {"name"=>"Job", "email"=>"*****"} permitted: true>,
#<ActionController::Parameters {"name"=>"Ed", "email"=>"****"} permitted: true>]

Submitted array param that is permitted in strong params disappears

Having trouble with strong params in a Rails 5.0.2 api. I can't post the exact code, but the params gathered in a method and then used in the create action in the controller.
def create
render_json Model.create(create_params_method)
end
def create_params_method
create_params = params.require(:model).permit(:name, array_of_ids: [])
create_params
end
When I submit the request, params contains the array_of_params parameter, but when it runs through the params method, it disappears and I just have the name. Trying to track down where/why array_of_ids is getting rejected, but no luck so far. I use stack all the time for help and I can provide more information if needed, but this is my first time actually asking a question so bear with me 😄.
Because params.require(:model).permit(:name, array_of_ids: []) does is filter out the mentioned params from received params, so if you have anything else it will be gone. e.g if you recieve
user: {id: 1, name: 'sahil', last_name: 'grover', roles: [1,2,3], is_admin: true}
and your params filter is
params.require(:user).permit(:id, :name, :last_name, roles: [])
then the resultant you will get is
{id: 1, name: 'sahil', last_name: 'grover', roles: [1,2,3]}
is_admin will be removed since it is not listed in permitted params.

Undefined method `with_indifferent_access' for #<Array: for nested model in rails

I have a nested form with three models vehicle, vehicle_key_feature and vehicle_detail where vehicle_key_feature and vehicle_detail has one to one relation with vehicle. It is working fine when I use strong params following way -
params.require(:vehicle).permit(:title, vehicle_key_feature_attributes: [:android_auto], vehicle_detail_attributes: [:tech_specs])
since I have lots of strong params for all three models, I would like to keep nested attributes params in a separate method and merge them with vehicle_params. But it's showing me following error
undefined method with_indifferent_access' for #Array
I have written the following codebase, I checked console and params.inspect which is in expected form.
def vehicle_params
params.require(:vehicle).permit(
:title, :category_id, :make, :model, :model_number, :mileage, :exterior, :interior, :transmission, :engine_type, :drivetrain, :fuel_efficiency, :engine, :condition, :description, :dealer_id
)
.merge(vehicle_key_feature_attributes)
.merge(vehicle_detail_attributes)
end
def vehicle_key_feature_attributes
{
vehicle_key_feature_attributes: [
:android_auto, :apple_carplay, :backup_camera, :blind_spot_monitor, :bluetooth,
:forward_collision_warning, :interior_accents, :keyless_entry, :side_impact_air_bags
]
}
end
def vehicle_detail_attributes
{
vehicle_detail_attributes: [
:exterior, :interior, :entertainment, :mechanical, :safety, :tech_specs, :details
]
}
end
What is the best solution to extract these two nested attributes in two separate methods?
Your second snippet is doing something else. To replicate what the first one does, add your hashes to permit's argument list.
params.require(:vehicle).permit(:title, ..., vehicle_key_feature_attributes.merge(vehicle_detail_attributes))

Rails mass create from array of hash

I get params like this: Parameters: {"utf8"=>"✓", "events"=>{"363"=>{"name"=>"3"}}, "new_events"=>[{"name"=>"1"}, {"name"=>"2"}], "date"=>"2016-11-01"}
What I would like to do is create Events from new_events array of hashes, but it doesn's let me because it neets to be sanitized for mass assignment.
This is how event_params looks like:
def event_params
params.require(:events).permit(:id, :name, :start_time, :user_id)
end
This setup is because I want to update and/or create new records when I press submit.
The current way I do this is
params[:new_events].each_with_object({}) do |e|
Event.create(name: e[:name], user_id: current_user.id, start_time: params[:date])
end
But Im not sure it's the correct way to do it
You should be able to sanitize the array of parameters:
params.require(:events).permit(new_events: [:name])
Event.create(params[:new_events] do |new_event|
new_event.user = current_user
end
But note that you'll have to loop over the objects anyway, so it doesn't make much of a difference. You may want to just do the loop with Event.new and validate them as a group:
events = Event.new(params[:new_events]) do |new_event|
new_event.user = current_user
...
end
if events.any?(&:invalid?)
[error handling]
else
events.save!

strong_params removing id of accepts_nested_attributes_for models

I have the follow strong_params statement:
def product_grid_params
params.require(:product_grid).permit(:name,
product_grid_locations_attributes: [:id, :grid_index, :item_id, :item_type, :short_name, :long_name]
).merge({ venue_id: params[:venue_id] })
end
But my params and product_grid_params look like this:
(byebug) params
{"product_grid"=>{"product_grid_locations_attributes"=>[{"id"=>"5560d1f7a15a416719000007", "short_name"=>"shrt", "long_name"=>"Whiskey Ginger", "grid_index"=>73, "item_type"=>"product", "item_id"=>"9b97aa28-1349-4f60-a359-3907c8ac9a74"}]}, "id"=>"5560d1f7a15a416719000006", "venue_id"=>"5560d1f7a15a416719000005", "format"=>"json", "controller"=>"api/v2/manager/product_grids", "action"=>"update"}
(byebug) product_grid_params
{"product_grid_locations_attributes"=>[{"grid_index"=>73, "item_id"=>"9b97aa28-1349-4f60-a359-3907c8ac9a74", "item_type"=>"product", "short_name"=>"shrt", "long_name"=>"Whiskey Ginger"}], "venue_id"=>"5560d1f7a15a416719000005"}
You'll notice that in the params, the product_grid_location's id is present, but it gets filtered out in product_grid_params. What gives? I need that id there to update nested attributes.
Looks like this was because of an issue with Mongoid. The id I was passing in was a Moped::BSON::ObjectId, which strong_params refused to parse. I converted it to a string and everything was fine after that:
params[:product_grid][:product_grid_locations_attributes].each { |location| location[:id] = location[:id].to_str }

Resources