Rails 5. Strong params require not used if one permit key is set

Kramougna picture Kramougna · Feb 2, 2016 · Viewed 14.7k times · Source

I am using Rails 5 to create a JSON Api.

My controller uses strong parameters with one require attribute like this:

params.require(:require_attribute).permit(:permit_attribute1,:permit_attribute2)

Normally I have to send my JSON like this:

{
    "require_attribute":{
        "permit_attribute1": "data",
        "permit_attribute2": "data"
    }
}

And if the required attribute is missing,I have to get this message:

"ActionController::ParameterMissing: param is missing or the value is empty: require_attribute"

My problem is, if I remove the required attribute from JSON and I have one permit attribute in common with strong params it does work.

The JSON I send :

{
    "permit_attribute1": "data",
}

When I get params in log I have:

{"permit1"=>data, "controller"=>"mycontroller", "action"=>"create", "require_attribute"=>{"permit1"=>1}}

It seems Rails creates a hash with required key instead of raising an error. But I want to force the required attribute when I receive a JSON.

Anyone have an idea?

Answer

max picture max · Feb 2, 2016

The strong parameter API was designed with the most common use cases in mind. It is not meant as a silver bullet to handle all your whitelisting problems.

http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters

require(key)

Ensures that a parameter is present. If it's present, returns the parameter at the given key, otherwise raises an ActionController::ParameterMissing error.

http://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-require

As you can see from the above setting required parameters on a "flat" hash is not really what the strong parameters API is built for. Rather its built around the rails conventions where params are nested under a single key.

You could use ´.require´ to pull a single key at once but that would be rather clumsy.

Rather you can simulate what it does by raising an exception unless the key is present:

def something_params
  req = [:required_attribute1, :required_attribute2]
  req.each do |k|
    raise ActionController::ParameterMissing.new(k) unless params[k].present?
  end
  whitelisted = params.permit(:permit_attribute1, :permit_attribute2)
end

However a better method altogether may be to use model level validations - ActionController::ParameterMissing is supposed to indicate that the general formatting of the request is off - not that a required attribute is missing. For example for a JSONAPI.org formatted request you would do:

params.require(:data).require(:attributes).permit(:email, :username)

Which ensures that the request follows the standard. However enforcing that a User cannot be created without an email is a model level concern.