m   Back to Blog

Magic Behind Rails Query Params

Magic Behind Rails Query Params

I’ve been using Rails for a long time and never paid attention to one of its cool aspects. You probably noticed that sometimes Rails will automatically include params under the model name. But have ever wondered when and how it happens? I didn’t until I was forced to understand the whole stack the request goes through when I was investigating memory problems (something I’ll write about in another post).

Here’s a simple example:

Processing by ConfigurationsController#update as JSON
  Parameters: {"enabled_projects"=>[10300], "configuration"=>{"enabled_projects"=>[10300]}}

The request is (copied from a spec):

put configuration_path(format: :json),
  as: :json,
  params: {
    enabled_projects: [10300]
  }

First, as you see, we don’t wrap params in configuration. Also notice that enabled_projects is repeated twice.

It ain’t magic. It’s ActionController::ParamsWrapper at work ❤ .

It’s easy to forget, but when you scaffold your project, Rails creates config/initializers/wrap_parameters.rb with a default configuration like:

# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
  wrap_parameters format: [:json]
end

That means when ActionController is loaded, it should call wrap_parameters format: [:json] by default.

Yes, that’s correct - params are wrapped only for JSON requests by default. (In case you’re scratching your head as to why Rails sometimes wraps params and sometimes doesn’t.)

So if you submitted the same request, but for HTML, variant the wrapping would not kick in:

put configuration_path,
  params: {
    enabled_projects: [10300]
  }

It's handled as:

Processing by ConfigurationsController#update as HTML
  Parameters: {"enabled_projects"=>[10300]}

So why wrap params in the first place?

It’s mostly for ease of use. You can then have code as simple as:

def update
  unless @configuration.update(params.require(:configuration).permit!)
    render json: @configuration.errors, status: :unprocessable_entity
  end
end

And you don’t need to worry that some random params will be put inside configuration:

put configuration_path(format: :json),
  as: :json,
  params: {
    enabled_projects: [10300],
    attribute_that_is_not_in_the_model: true
  }

The code above still produces:

Processing by ConfigurationsController#update as JSON
  Parameters: {"enabled_projects"=>[10300], "attribute_that_is_not_in_the_model"=>true, "configuration"=>{"enabled_projects"=>[10300]}}

ActionController::ParamsWrapper tries to guess which model the controller is responsible for, and what attributes are permitted.

You can control that with additional params, for example (in configurations_controller.rb):

wrap_parameters include: [:attribute_that_is_not_in_the_model, :enabled_projects]

Will include attribute_that_is_not_in_the_model into configuration:

Processing by ConfigurationsController#update as JSON
  Parameters: {"enabled_projects"=>[10300], "attribute_that_is_not_in_the_model"=>true, "configuration"=>{"attribute_that_is_not_in_the_model"=>true, "enabled_projects"=>[10300]}}

Unfortunately, when using include you need to list all attributes that are allowed. You don’t extend the list as the name include would suggest.

Is that a good pattern (using params.require(:configuration).permit!)?

It has its benefits, but we currently don’t use it. We err on the safe side and have an additional security layer (filtering out unwanted params before we pass them to update):

def update
  unless @configuration.update(update_params)
    render json: @configuration.errors, status: :unprocessable_entity
  end
end

def update_params
  params.require(:configuration).permit(enabled_projects: [])
end

Maybe it’s overly redundant, but it’s our current pattern. Which pattern is better for you?

You can also disable ActionController::ParamsWrapper for a controller with:

wrap_parameters false

Sometimes the tools we use everyday have more features than we stop to appreciate. What’s your favorite thing about Rails?

Pawel Niewiadomski
Newsletter Subscription Notification

Want to be more agile?

Learn more about Jira, Agile and much more by joining our newsletter!

Thanks for joining our newsletter.
Oops! Something went wrong.