Ruby on Rails - Is params a method or a hash?
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.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
How to Remove All Elements That Satisfy a Condition in Array in Ruby
Should I Deploy My Ruby on Rails Application on Heroku
Automatically Precompile Assets Before Pushing to Heroku
Sending a Delete Request from Sinatra
Rails Change Routing of Submit in Form_For
Passing Binding or Arguments to Erb from the Command Line
C1 or C2 Coverage Tool for Ruby
Making a Soap Request by Using Xml in Rails
Automatic Logging of Datamapper Queries
Is Returning a Value from 'Next' a Bad Idea
Ruby on Rails Config.Secret_Token Error
Connecting to Oracle Db Using Ruby