Rails allows you to instantize a model object using parameters hash (attribute => value). The mechanism is called mass assignment and thanks to that you can easily pass request parameters (params
) and build an object without a need to assign attributes manually one by one. Here is an example:
# app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
def signup
params[:user] # => {"name"=>"Elvis Presley", "email"=>"elivis@example.com"}
@user = User.new(params[:user])
end
# ...
end
Mass-assignment vulnerability and protected attributes
Unfortunately, this can sometimes cause a harm. Without additional validation attackers can pass parameters that the developer never intended to be set. It is called mass-assignment vulnerability.
First Rails versions introduced “protected attributes” to protect model attributes from mass-assignment vulnerability. On the model level it was possible to declare a white or black list of attributes.
# app/models/user.rb
class User < ActiveRecord::Base
attr_accessible :name, :email
# ...
end
Strong Parameters
The solution was not very flexible and Rails 4 introduced Strong Parameters, which allows to filter attribute on the controller level. Now, you can decide depend on the context, which attributes to permit for mass update.
# app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
def signup
@user = User.new(signup_params)
end
# ...
private
def signup_params
params.require(:user).permit(:name, :email)
end
end
Permit only selected
Let’s have a closer look and check the available methods. permit
allows to declare which keys (attributes) are allowed. All other will be filtered out. As a result it returns ActionController::Parameters
with all available parameters.
# Create ActionController::Parameters object with some parameters for test purposes
params = ActionController::Parameters.new({
name: 'Elvis Presley',
email: 'elvis@example.com'
})
# The object includes all request parameters with the permitted flag is set to false
irb(main):001:0> params
=> <ActionController::Parameters {"name"=>"Elvis Presley", "email"=>"elvis@example.com"} permitted: false>
# Use permit to filter out all parameters except name
irb(main):002:0> params.permit(:name)
=> <ActionController::Parameters {"name"=>"Elvis Presley"} permitted: true>
# You can pass any number of parameter names you want to pass
irb(main):003:0> params.permit(:name, :email)
=> <ActionController::Parameters {"name"=>"Elvis Presley", "email"=>"elvis@example.com"} permitted: true>
# In case the key does not exists it returns an empty object
irb(main):004:0> params.permit(:non_existing)
=> <ActionController::Parameters {} permitted: true>
# Do not chain permit method if you want to permit more than one param
irb(main):005:0> params.permit(:name).permit(:email)
=> <ActionController::Parameters {} permitted: true>
# The orginal params object won't be changed
irb(main):006:0> params
=> <ActionController::Parameters {"name"=>"Elvis Presley", "email"=>"elvis@example.com"} permitted: false>
Require to make sure the param exists
The second method you might want to use is require
which allows you to make sure the param exists.
# Create ActionController::Parameters object with some parameters for test purposes
params = ActionController::Parameters.new({
name: 'Luke Skywalker',
email: 'luke@example.com'
})
# Check if param :name exists.
irb(main):001:0> params.require(:name)
=> "Luke Skywalker"
# Please note it returns the param value, not the ActionController::Parameters object
irb(main):002:0> params.require(:name).class
=> String
# You can check only one param at a time
irb(main):003:0> params.require(:name, :email)
Traceback (most recent call last):
1: from (irb):27
ArgumentError (wrong number of arguments (given 2, expected 1))
# When param is missing it raises an exception
irb(main):004:0> params.require(:age)
Traceback (most recent call last):
2: from (irb):27
1: from (irb):28:in `rescue in irb_binding'
ActionController::ParameterMissing (param is missing or the value is empty: age)
We can combine require
and permit
to make sure a hash of attributes is send.
# Create ActionController::Parameters object with some parameters for test purposes
params = ActionController::Parameters.new({
person: {
name: 'Elvis Presley',
email: 'elvis@example.com',
role: 'admin'
}
})
# Check if :person param exists. Please notice it returns a hash.
irb(main):001:0> params.require(:person)
=> <ActionController::Parameters {"name"=>"Elvis Presley", "email"=>"elvis@example.com", "role"=>"admin"} permitted: false>
# Permit :name from :person hash
irb(main):002:0> params.require(:person).permit(:name)
=> <ActionController::Parameters {"name"=>"Elvis Presley"} permitted: true>
# Permit :name and :email from :person hash
=> <ActionController::Parameters {"name"=>"Elvis Presley", "email"=>"elvis@example.com"} permitted: true>
# Sometimes you might want to permit all params but it is not considered as safe operation.
irb(main):003:0> params.require(:person).permit!
=> <ActionController::Parameters {"name"=>"Elvis Presley", "email"=>"elvis@example.com", "role"=>"admin"} permitted: true>
Summary
Strong Parameters are very flexible and allows to protect our internal code from unwanted params. For more details please check Rails documentation for Strong Parameters.