Nested arrays in Strong Parameters in Rails - ruby-on-rails

Faced a strange problem or bug.
i have a route with strong parameters method:
def st_params
params.permit([:id, logs: [], metric_ids: []])
end
Then i call it with, for example, rspec:
post some_path(format:json, metric_ids:[ [1,0,1], [5,1,4] ])
And when i call a st_params method there is no metric_ids param and i have got message in logs: Unpermitted parameters: metric_ids
But if i change st_params method like this:
def st_params
p = params.permit([:id, logs: [], metric_ids: []])
p[:metric_ids] = params[:metric_ids]
# or just p = params.permit!
p
end
Now, everything works fine in browser, but it looks strange.
But in rspec i have received nil value for metric_ids in any case :( I have not found any information about the problem. Maybe someone here may help me.
Thanks in advance.

2 things that may be causing your trouble here:
1) The bang method permit! is for accepting an entire hash of parameters and it does not take any arguments. I also am unsure if permit! can be called on the entire params hash, the documentation is unclear on this.
2) Your use of array notation may be causeing the params hash some confusion. (It also may be acceptable, but looks a little unorthodox compared to what I'm used to seeing.) I would write your st_params as
def st_params
params.permit(:id, :log, :metric_ids)
end
Also, a permit argument like metric_ids: [] as you have written will whitelist any parameters nested inside of params[:metric_ids], based on the way you are passing data to this hash above, I do not think this is your intended usage. It appears that you are storing an array at this level of the hash and there is no further complexity, so no need to whitelist params beyond this scope.
Hope this helps!

I was facing same problem. Appending as: :json to post solved the problem for me.
Ref: https://github.com/rspec/rspec-rails/issues/1700#issuecomment-304411233

Related

Set Parameter if blank

I need to set the id parameter to a value if it is wasn't submitted with the form.
Is it ok to do something like this in Rails or does this violate any standards or cause possible issues?
if params[:cart][:cart_addresses_attributes]["0"][:id].blank?
params[:cart][:cart_addresses_attributes]["0"][:id] = 1234 #default id
end
My implementation works with this logic, but I am not sure if this is the proper way to handle the issue.
There's a chance [:record_type] is nil which will lead to an undefined method error when you attempt to call [:id] on nil. Additionally, I'd find it a bit weird to directly mutate params, even though you technically can do that. I'd consider using Strong Parameter processing methods like so (added a full action, which isn't in your sample, to give more context on how this would be used):
def create
#record_type = RecordType.new(record_type_params)
if record_type.save
redirect_to #record_type
else
render :new
end
end
def record_type_params
params.require(:record_type).permit(:id).reverse_merge(id: 1234)
end
The reverse_merge call is a way to merge the user-supplied parameters into your defaults. This accomplishes what you're after in what I would consider a more conventional way and doesn't mutate params.
def cart_params
params.require(:cart).permit(:cart_addresses_attributes => [:id]).tap do |p|
p[:cart_addresses_attributes]["0"][:id] ||= 1234
end
end
if params[:record_type][:id].nil? # or replace ".nil?" with "== nil"
params[:record_type][:id] = 1234
end
personally, this is the way I prefer to do it. Some ways are more efficient than others, but if that works for you I'd roll with it.

Updating objects in RoR with API call

I am trying to configure my ruby on rails application in such a manner that I can update values with http Patch calls from for example a Angular app. Currently I have the following method of which I expect it to work:
users_controller.rb
def safe_params
params.require(:id).permit(:email)
end
def update
user = User.find(params[:id])
user.update_attributes(safe_params)
render nothing: true, status: 204
end
However, I get the following error when I pass some simple JSON:
undefined method `permit' for "500":String
Passed JSON:
{"email":"newadres#live.com", "id":500}
Do you guys know what I am doing wrong?
I believe you are misunderstanding the purpose of require and permit.
require is generally used in combination with a Hash and a form, to make sure the controller receives an Hash that exists and contains some expected attributes. Note that require will either raise, or extract the value associated with the required key, and return that value.
permit works as a filter, it explicitly allows only certain fields. The returned value is the original params Hash, whitelisted.
In your case, require does not make any sense at all, unless you pass a nested JSON like this one
{"user": {"email":"newadres#live.com", "id":500}}
but even in that case, it would be
params.require(:user).permit(:email)
In your current scenario, the correct code is
params.permit(:email)
One way to fix this, keeping with the spirit of the Rails docs:
def safe_params
params.require(:user).permit(:email)
end
And update the json:
{"user": {"email":"newadres#live.com"}, "id": 500}
You should change the order between require and permit, like that
params.permit(:email).require(:id)
because permit returns the entire hash, while require returns the specific parameter
Reference here
UPDATE
However, as others pointed out, you shouldn't use require with a single attribute, as it is most commonly used for hashes instead

In Rails, how can a mass-assignment be made to an existing model object without warnings?

In a Rails application it's possible to assign some values from params to an existing model object, like this:
model.attributes = params
This would obviously return a ForbiddenAttributesError but that can be avoided like this:
model.attributes = params.permit(:a, :b, :c)
However, although that works, it still outputs a message to the console if params contains keys that are not mentioned in the call to permit:
Unpermitted parameters: d, e, f
The warning is pointless because it's already known that params contains additional keys and permit is being used to select the required subset. It can be avoided by doing
model.attributes = params.slice(:a, :b, :c).permit!
Is there a more appropriate way to do this assignment that does not require having to slice the hash first?
The warning is pointless because it's already known that params contains additional keys and permit is being used to select the required subset.
You mistake the purpose of this warning. It is very valuable when you debug why your form doesn't update this new field you added just today. With the warning, looking at the server log you can quickly find out that you forgot to update strong_params filtering.
Also, the warning will only be shown in dev/test environments. It won't appear in production.
But suppose you really hate that warning. In which case you can do this in your app config:
# possible values: :raise, :log
config.action_controller.action_on_unpermitted_parameters = false
BTW, your code is a bit unorthodox. The common way is to keep the filtering code in one method and call it from both create/update (and whereever else you need it)
def update
#product = ...
if #product.update_attributes(product_params)
...
else
...
end
end
private
def product_params
params.require(:product).permit(:a, :b, :c)
end

Ruby 2 Keyword Arguments and ActionController::Parameters

I have a rails 4 application that is running on ruby 2.1. I have a User model that looks something like
class User < ActiveModel::Base
def self.search(query: false, active: true, **extra)
# ...
end
end
As you can see in the search method I am attempting to use the new keyword arguments feature of ruby 2.
The problem is that when I call this code from in my controller all values get dumped into query.
params
{"action"=>"search", "controller"=>"users", query: "foobar" }
Please note that this is a ActionController::Parameters object and not a hash as it looks
UsersController
def search
#users = User.search(params)
end
I feel that this is because params is a ActionController::Parameters object and not a hash. However even calling to_h on params when passing it in dumps everything into query instead of the expected behavior. I think this is because the keys are now strings instead of symbols.
I know that I could build a new hash w/ symbols as the keys but this seems to be more trouble than it's worth. Ideas? Suggestions?
Keywords arguments must be passed as hash with symbols, not strings:
class Something
def initialize(one: nil)
end
end
irb(main):019:0> Something.new("one" => 1)
ArgumentError: wrong number of arguments (1 for 0)
ActionController::Parameters inherits from ActiveSupport::HashWithIndifferentAccess which defaults to string keys:
a = HashWithIndifferentAccess.new(one: 1)
=> {"one"=>1}
To make it symbols you can call symbolize_keys method. In your case: User.search(params.symbolize_keys)
I agree with Morgoth, however, with rails ~5 you will get a Deprecation Warning because ActionController::Parameters no longer inherits from hash. So instead you can do:
params.to_unsafe_hash.symbolize_keys
or if you have nested params as is often the case when building api endpoints:
params.to_unsafe_hash.deep_symbolize_keys
You might add a method to ApplicationController that looks something like this:
def unsafe_keyworded_params
#_unsafe_keyworded_params ||= params.to_unsafe_hash.deep_symbolized_keys
end
You most likely do need them to be symbols. Try this:
def search
#users = User.search(params.inject({}){|para,(k,v)| para[k.to_sym] = v; para}
end
I know it's not the ideal solution, but it is a one liner.
In this particular instance I think you're better off passing the params object and treating it as such rather than trying to be clever with the new functionality in Ruby 2.
For one thing, reading this is a lot clearer about where the variables are coming from and why they might be missing/incorrect/whatever:
def search(params)
raise ArgumentError, 'Required arguments are missing' unless params[:query].present?
# ... do stuff ...
end
What you're trying to do (in my opinion) only clouds the issue and confuses things when trying to debug problems:
def self.search(query: false, active: true, **extra)
# ...
end
# Method explicitly asks for particular arguments, but then you call it like this:
User.search(params)
Personally, I think that code is a bit smelly.
However ... personal opinion aside, how I would fix it would be to monkey-patch the ActionController::Parameters class and add a #to_h method which structured the data as you need it to pass to a method like this.
Using to_unsafe_hash is unsafe because it includes params that are not permitted. (See ActionController::Parameters#permit) A better approach is to use to_hash:
params.to_hash.symbolize_keys
or if you have nested params:
params.to_hash.deep_symbolize_keys
Reference: https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-to_hash

Why won't my params pass to my model?

I am trying to pass a simple variable in my params to a class method, however it doesnt seem to work. This seems elementary, but I'm still learning. Can someone explain why this doesn't work and offer an alternative? My code is below.
controller
#profile = current_user.profile
#dogs = Dog.by_profile(params[#profile])
model
def self.by_profile(profile)
Dog.where(kids_under_10: profile.kids_under_10 )
end
*note: profile.kids_under_10 is a boolean. When I manually replace it with true or false, everything works fine.
params is a special rails hash that contains url parameters. So your code is looking for a url parameter passed with the request containing the string version of your user profile. This is definitely not what you want to be doing.
When you're calling a rails model method, you call it with arguments like any other method: Dog.by_profile(#profile)
You don't want the params part, or you're trying to do something crazy that should be refactored :)
Your key value for params should look like params[:profile].
So try #dogs = Dog.by_profile(params[:profile]).
Because params is a hash that comes from a request. What you are doing is trying to search the hash with an instance variable which is wrong.
I think what you meant is to do params[:profile] or just #profile

Resources