Search code examples
ruby-on-railsruby-on-rails-5

Rails 5: unable to retrieve hash values from parameter


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.


Solution

  • 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