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!
Related
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>]
I have a search/filter form where the user may select none to many parameters from which to filter records. The filters could include a specific user_id, or company_id, or project_id, etc. Any combination of these parameters could be submitted to filter records by.
What I'm trying to do is create as simple a query as possible and not need to re-query a subset.
I could pull this off by using more logic in the query...but, it seems there should be a rails way.
Thing.where( params[:user_id].present? ? user_id: params[:user_id] : "user_id IS NOT NULL" ).
where( params[:company_id].present? ? company_id: params[:company_id] : "company_id IS NOT NULL" )
What I'm striving for is something cleaner...like:
Thing.where( user_id: params.fetch(:user_id, '*') )
Then, I could chain all the available search params like this:
Thing.where(
user_id: params.fetch(:user_id, '*'),
company_id: params.fetch(:company_id, '*'),
project_id: params.fetch(:project_id, '*')
)
Another approach
Thing.where('') will return all Things. So, I could do something like:
Thing.where( params[:user_id].present? ? { user_id: params[:user_id] } : '' )
But, this doesn't seem like the rails way.
Is there a way to do this? Thanks!
Just create a model that takes the parameters as input and creates a scope:
class ThingFilter
include ActiveModel::Model
include ActiveModel::Attributes
attribute :user_id
attribute :company_id
attribute :project_id
def resolve(scope = Thing.all)
attributes.inject(scope) do |filtered, (attr_name, value)|
if !value.present?
scope.merge(Thing.where.not(attr_name => nil))
else
scope.merge(Thing.where(attr_name => value))
end
end
end
end
class Thing < ApplicationRecord
def self.filter(**kwargs)
ThingFilter.new(**kwargs).resolve(self.where)
end
end
#things = Thing.filter(
params.permit(:user_id, :company_id, :project_id)
)
This is extremely easy to test and lets you add features like validations if needed without making a mess of your controller. You can also bind it to forms.
There's a method called #merge which can be used in this case.
#things = Thing.all
#things = #things.merge(-> { where(user_id: params[:user_id]) }) if params[:user_id].present?
#things = #things.merge(-> { where(company_id: params[:company_id]) }) if params[:company_id].present?
#things = #things.merge(-> { where(project_id: params[:project_id]) }) if params[:project_id].present?
Although it's not the most concise way to do it, it's pretty readable in my opinion.
Found a concise way to do it, but use it according to your opinion on readability.
#things =
params.slice(:user_id, :company_id, :project_id).
reduce(Thing.all) do |relation, (column, value)|
relation.where(column => value)
end
It feels like you may be coding that which the Ransack gem already provides.
It accepts search parameters, such as user_id_eq and company_name_present, and sort parameters, and converts them to the required SQL.
https://github.com/activerecord-hackery/ransack
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.
How to handle tag_ids in post params to save it in related model? I would like to use for it only post_params method.
has_many :tags
def post_params
params.require(:post).permit(:title, :message, :tag_ids)
end
#Parameters: {"post"=>{"title"=>"asdf", "message"=>"asfd", "tag_ids"=>"543d727a4261729ecd000000,543d8a914261729ecd010000"}}
I've got:
Mongoid::Errors::InvalidValue -
Problem:
Value of type String cannot be written to a field of type Array
I found solution but I don't like it, in Post model I've added:
def tag_ids=str
str.split(',').each do |id|
t = Tag.find(id)
self.tags << t
end
end
I think that you have to modify the incoming data in tag_ids in create action in your controller.
So when you receive the data, before you are saving the data into DB by, for example: post.create! you should add parsing to your PostsController action create:
If you want array of string:
post.tag_ids = params[tag_ids]split(",")
or if you want array of integers:
post.tag_ids = params[tag_ids]split(",").map(&:to_i)
Something like this? (I'm assuming you want an array of ObjectId)
id_array = params['post']['tag_ids'].split(',').map { |id| BSON::ObjectId.from_string(id) }
params['post']['tag_ids'] = id_array
I have the following dynamic params depending on the line items i am trying to add to an order
{"line_item" => {"items"=>{"0"=>{"price"=>"5.75", "name"=>"Item name", "quantity"=>"5"}, "1"=>{"price"=>"3.35", "name"=>"Item name", "quantity"=>"1"}}}
In my controller:
def lineitems_params
params.require(:line_item).permit(:key1, :key2, :key3, :key4, :payment_type, :payment_provider).tap do |whitelisted|
whitelisted[:items] = params[:line_item][:items]
end
end
I still get the
Unpermitted parameters: items
in my logs, and it does not update the items.
How can i solve this?
NOTE: the items hash can have many elements inside.
EDIT:
In my model:
serialize :items, Hash
This should work
def lineitems_params
params.require(:line_item).permit(:key1, :key2, :key3, :key4, :payment_type, :payment_provider, {:items => {:price, :name, :quantity}})
end
Update
may be you should just give like this
def lineitems_params
params.require(:line_item).tap do |whitelisted|
whitelisted[:items] = params[:line_item][:items]
end
end
Source
Note: Don't give params.require(:line_items).permit! it permits all attributes.