Rails 5: unable to retrieve hash values from parameter
Asked Answered
I

4

43

I'm running into a strange issue.

undefined method `values' for #<ActionController::Parameters:0x007fb06f6b2728>

is the error I get, when I assign a variable to a param hash, and try to get it's values.

attributes = params[:line_item][:line_item_attributes_attributes] || {}
attributes.values

the parameter looks like this a hash of hashes:

{"0"=>{"product_attribute_id"=>"4"}, "1"=>{"product_attribute_id"=>"7"}}

now when I do this in console and assign that to a variable attributes it works flawlessly. So I'm struggling to understand what isn't working here - and how to make it work.

Iambic answered 22/1, 2016 at 14:52 Comment(1)
this is indeed strange. Any object of class ActionController::Parameters should respond to values. What are your ruby and rails versions? Could you add a logger.warn attributes.inspect?Phelgon
A
88

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

Addia answered 22/1, 2016 at 16:16 Comment(7)
but this class, namely ActionController::Parameters, is a subclass of Hash, and the latter has a method values.Phelgon
Not actually, that's why there is a to_h method in that classAddia
try params = ActionController::Parameters.new; params.values to see that you do not need to_h here.Phelgon
mmm it starts to make sense... lol may be related to rails version?Addia
I'm running rails 5.0 beta1, tried adding .to_h on the param, but it gives me an empty hashIambic
it is because you need to whitelist the params, please read carefully the answer. But you can still use to_unsafe_h and avoid whitelistingAddia
Please check the answer again, now I found the root of your issue. Hope it helps youAddia
R
19

Since Rails 5, params are of class 'ActionController::Parameters'

If you do params.to_h you will get the following error.

*** ActionController::UnfilteredParameters Exception: unable to convert 
unpermitted parameters to hash

You can do as follows to permit all the params and get as Hash format:

parameters = params.permit(params.keys).to_h

"But beware of using this! You are permitting all the params which may include unknown params that can harm your code."

Rufina answered 24/8, 2017 at 11:28 Comment(0)
D
3

I think what's happening is the following:

In a console you are working with a simple hash called attributes. As a hash the attributes parameter in the console has a valid instance method called values.

In your rails app the params hash is not a simple hash any more. It is an instance of the ActionController::Parameters class. As an instance of that class it does not have an instance method called values, but it does have an instance method called to_h & to_unsafe_h, which would accomplish your goals. After calling to_h on your parameters you can call the values method.

Disassemble answered 22/1, 2016 at 16:12 Comment(8)
but the class ActionController::Parameters, is a subclass of Hash, and the latter has a method values.Phelgon
More specifically, try in a console params = ActionController::Parameters.new({line_item: ActionController::Parameters.new({line_item_attributes_attributes: ActionController::Parameters.new})}); attributes = params[:line_item][:line_item_attributes_attributes] || {}; puts attributes.class; attributes.valuesPhelgon
true... you can also simply run ActionController::Parameters.method_defined? :values and you'll see that values is a valid method... now I'm stumpedDisassemble
@Phelgon diving into the Ruby though you can try this with your params variable: params.kind_of? Hash #=> true, but then try params.instance_of? Hash #=> false So params is a kind of hash, but not an instance of hash... why would that be?Disassemble
Looking in Rails 5, Parameters no longer inherits from Hash (via HashWithIndifferentAccess), but they did add :values as a delegate method, so it should still work... Here is the Rails 5 code vs. the Rails 4 codeDisassemble
I'm indeed using Rails 5, however when I do attributes = params[:line_item][:line_item_attributes_attributes].to_h it becomes an empty hash.Iambic
You have to call to_h on the safe version of the params, i.e. my_params = params.permit(:line_item).require(:product_attribute_id), basically wherever you whitelisted your params in the controller... otherwise you have to call to_unsafe_h on params... or you can probably run this: params.permit!.to_h, but that will permanently de-whitelist your params hash, which you shouldn't want... better to work with a safe version of the params hashDisassemble
And... :values should still work since it was added as a delegate method in Rails 5.Disassemble
A
0

Word to the wise: if you're using link_to_sorted from the sorted gem it breaks views in Rails 5.

Anya answered 11/8, 2017 at 19:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.