Ruby on Rails - Is Params a Method or a Hash

Ruby on Rails - Is params a method or a hash?

  1. The description is a little bit truncated. To be precise, it should be read as:

    The return value of the params method is [an] object ...

    A method is not an object, but when you execute a method, it always returns an object. In this case, params is a method, not an object, but has a return value, which is an object.

  2. In older versions of Rails, the return value of params used to be a hash, but now, it isn't a hash.

Rails params method: Why can it be accessed like a hash?

You are correct that params is a method, but here the params method returns an instance of ActionController::Parameters and we call hash accessor method #[] on it.

This is a common pattern in ruby to call methods on the returned object. Let's see it by a simple example:

def params
{
id: 101,
key: 'value',
foo: 'bar'
}
end

params[:id] # => 101
params[:foo] # => 'bar'

As you can see in the example, method params returns a hash object and we call hash accessor method #[] on the returned object.

Reference to rails params method: https://github.com/rails/rails/blob/5e1a039a1dd63ab70300a1340226eab690444cea/actionpack/lib/action_controller/metal/strong_parameters.rb#L1215-L1225

def params
@_params ||= begin
context = {
controller: self.class.name,
action: action_name,
request: request,
params: request.filtered_parameters
}
Parameters.new(request.parameters, context)
end
end

Note for ruby beginners: In ruby, we can call methods without parenthesis. So, above call is equivalent to params()[:id].

Rails 6 change params to hash of hashes

The params get his structure from the input names.

So you could add an hidden field for question, and then specify a name for both of your fields.

<%= simple_form_for :test_results, url: test_results_path  do |f| %>
<% @randomize_questions.map do |q| %>
<%= q[:question] %>
<%= f.input "question_#{q[:id]}", as: :hidden, input_html: { name: "test_results[#{q[:id]}][question]", value: q[:question] } %>
<%= f.input "question_#{q[:id]}", collection: q[:answers], as: :radio_buttons, input_html: { name: "test_results[#{q[:id]}][answer]" } %>
<% end %>
<%= f.button :submit %>
<% end %>

Params should looks like this:

params => {
test_result: {
1 => {
question: "...",
answer: "..."
},
2 => {
question: "...",
answer: "..."
}
}
}

Not tested. Could you tell if that's works for you?

Rails 5: unable to retrieve hash values from parameter

take a look to this. Very weird since ActionController::Parameters is a subclass of Hash, you can convert it directly to a hash using the to_h method on the params hash.

However to_h only will work with whitelisted params, so you can do something like:

permitted = params.require(:line_item).permit(: line_item_attributes_attributes)
attributes = permitted.to_h || {}
attributes.values

But if instead you do not want to whitelist then you just need to use the to_unsafe_h method.

Update

I was very curious about this issue, so I started researching, and now that you clarified that you are using Rails 5, well that's the cause of this issue, as @tillmo said in stable releases of Rails like 4.x, ActionController::Parameters is a subclass of Hash, so it should indeed respond to the values method, however in Rails 5 ActionController::Parameters now returns an Object instead of a Hash

Note: this doesn’t affect accessing the keys in the params hash like params[:id]. You can view the Pull Request that implemented this change.

To access the parameters in the object you can add to_h to the parameters:

params.to_h

If we look at the to_h method in ActionController::Parameters we can see it checks if the parameters are permitted before converting them to a hash.

# actionpack/lib/action_controller/metal/strong_parameters.rb
def to_h
if permitted?
@parameters.to_h
else
slice(*self.class.always_permitted_parameters).permit!.to_h
end
end

for example:

def do_something_with_params
params.slice(:param_1, :param_2)
end

Which would return:

{ :param_1 => "a", :param_2 => "2" }

But now that will return an ActionController::Parameters object.

Calling to_h on this would return an empty hash because param_1 and param_2 aren’t permitted.

To get access to the params from ActionController::Parameters, you need to first permit the params and then call to_h on the object

def do_something_with_params
params.permit([:param_1, :param_2]).to_h
end

The above would return a hash with the params you just permitted, but if you do not want to permit the params and want to skip that step there is another way using to_unsafe_hash method:

def do_something_with_params
params.to_unsafe_h.slice(:param_1, :param_2)
end

There is a way of always permit the params from a configuration from application.rb, if you want to always allow certain parameters you can set a configuration option. Note: this will return the hash with string keys, not symbol keys.

#controller and action are parameters that are always permitter by default, but you need to add it in this config.
config.always_permitted_parameters = %w( controller action param_1 param_2)

Now you can access the params like:

def do_something_with_params
params.slice("param_1", "param_2").to_h
end

Note that now the keys are strings and not symbols.

Hope this helps you to understand the root of your issue.

Source: eileen.codes

accessing params in actioncontroller hash but getting undefined method

The content in uppderResult is a string and not array. That is why you get only [.

I believe the content is JSON encoded so you could just parse it to get an array.

JSON.parse(params["uppyResult"])[0]["successful"][0]["id"],

Please note that this code can still throw errors in production e.g. when malformed or nil. So you should implement make sure each element exist by e.g. using dig, first, parsing or & safe navigator.

A slightly more safe way to do it

def id
successful_result[0].to_h['id']
end

def successful_result
upper_result[0].to_h['successful'].to_a
end

def upper_result
JSON.parse(params["uppyResult"])
rescue
[] # rescue malformed JSON
end

How to get just GET and POST params as hash in Rails?

You should have permitted params (Strong parameters).

In your controller have permitted params method.

def your_params
params.permit(:query1, :query2)
end

If you wish to have a hash of those you can do

your_params.to_h #This will return hash of permitted params

Update:

Incase you have multiple query* type of parameters you can select them and permit!. Here is a command line explanation

# passing the list of parameters
> params = ActionController::Parameters.new({query1: 'aa', query2: 'bb', query3: 'cc', controller: 'users', action: 'index'})
=> <ActionController::Parameters {"query1"=>"aa", "query2"=>"bb", "query3"=>"cc", "controller"=>"users", "action"=>"index"} permitted: false>

# Select all the parameters of type query<integer>
> all_queries = params.select {|i| i.match?(/query\d/) }
=> <ActionController::Parameters {"query1"=>"aa", "query2"=>"bb", "query3"=>"cc"} permitted: false>

# mark the selected as permitted
> all_queries.permit!
=> <ActionController::Parameters {"query1"=>"aa", "query2"=>"bb", "query3"=>"cc"} permitted: true>

# get them as hash
> all_queries.to_h
=> {"query1"=>"aa", "query2"=>"bb", "query3"=>"cc"}

Controller method will look like

# getting all query<integer> like params
def your_params
params.select {|param| param.match?(/query\d/}.permit!
end

Rails strong parameters, user require and get an hash

require is only intended to ensure that the params have the correct general structure.

For example if you have:

params.require(:foo).permit(:bar, :baz)

require lets us bail early if the :foo key is missing since we cant do anything with the request.

require is not intended to validate the presence of individual params - that is handled with model level validations in Rails.

If you really had to you could do:

def create_billing_address!(data)
keys = [:first_name, :last_name, :phone, :address_1, :city, :postcode, :country]

keys.each do |k|
raise ActionController::ParameterMissing and return unless data[k].present?
end

service_customer.create_address(customer_id, data.permit(*keys))
end

But thats just bad application design as you're letting the model level business logic creep into the controller.

Reduce AbcSize metric when sanitizing params hash

As given by the documentation:

Branch -- an explicit forward program branch out of scope -- a function call, class method call, or new operator

So, the "Branch" in the ABC is calculated as "anything that jumps out of the current method".

In the method you described,

def sanitize_params
params[:purchase][:invoice] = nil if params[:purchase][:invoice] == ''
params[:purchase][:note] = nil if params[:purchase][:note] == ''
params[:purchase][:product_id] = nil if params[:purchase][:product_id].blank?
end

these are the 19 calls that need to leave the current method:

01. params (on line 1, before the assignment)
02. params[:purchase] (on line 1, before the assignment)
03. params[:purchase][:invoice] (on line 1, before the assignment)
04. params (on line 1, after the assignment)
05. params[:purchase] (on line 1, after the assignment)
06. params[:purchase][:invoice] (on line 1, after the assignment)

07. params (on line 2, before the assignment)
08. params[:purchase] (on line 2, before the assignment)
09. params[:purchase][:note] (on line 2, before the assignment)
10. params (on line 2, after the assignment)
11. params[:purchase] (on line 2, after the assignment)
12. params[:purchase][:note] (on line 2, after the assignment)

13. params (on line 3, before the assignment)
14. params[:purchase] (on line 3, before the assignment)
15. params[:purchase][:product_id] (on line 3, before the assignment)
16. params (on line 3, after the assignment)
17. params[:purchase] (on line 3, after the assignment)
18. params[:purchase][:product_id] (on line 3, after the assignment)
19. params[:purchase][:product_id].blank? (on line 3, after the assignment)

Important ponts: whenever you call params, you're accessing a different method, and everytime you access an index on the hash, you are calling a method over this hash to access the desired index.

About if you should care: the ABC size is a standard to measure code quality, so it's usually a good call to stick with it, whenever it makes sense, of course.

But there are some ways how you could refator the code to decrease the ABC complexity. One suggestion would be the following:

def sanitize_params
params.tap do |params|
params[:purchase][:invoice] = nil if params.dig(:purchase, :invoice) == ''
params[:purchase][:note] = nil if params.dig(:purchase, :note) == ''
params[:purchase][:product_id] = nil if params.dig(:purchase, :product_id).blank?
end
end

In this example, the Branch complexity decreases a lot because we only call the external params once and apply all the changes inside the tap block. Also, by using the dig method over the params, we call only one method to go as deep as we want to go.

Another idea would be to break this method into more specific methods to sanitize each attribute, for instance.

I hope this can be helpful.



Related Topics



Leave a reply



Submit