Replaces attr_accessible
and attr_protected
in Rails models, moving the responsibility for mass-assignment to the controller. Part of Rails 4 and available for Rails 3.2 as a gem.
class PeopleController < ActionController::Base
def create
Person.create(params[:person])
end
end
This raises an ActiveModel::ForbiddenAttributes
exception because it's using mass assignment without an explicit permit step.
class PeopleController < ActionController::Base
def update
person = current_account.people.find(params[:id])
person.update_attributes!(person_params)
redirect_to person
end
private
def person_params
params.require(:person).permit(:name, :age)
end
end
As long as there's a person key in the parameters this will be fine, otherwise it'll raise a ActionController::MissingParameter
exception
Using a private method to encapsulate the permissible parameters is just a good pattern since you'll be able to reuse the same permit list between create and update. Also, makes it easier to specialise this method with per-user checking of permissible attributes.
Demo upgrade of rails-api-identity
Live Coding Demo
The permitted scalar types are String, Symbol, NilClass, Numeric, TrueClass, FalseClass, Date, Time, DateTime, StringIO, IO, ActionDispatch::Http::UploadedFile
and Rack::Test::UploadedFile
.
Nested Parameters
params.permit(:name, { emails: [] },
friends: [ :name,
{ family: [ :name ], hobbies: [] }])
Using accepts_nested_attributes_for
# permit :id and :_destroy
params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy])
e.g. has_many associations (Hashes with integer keys can be declared directly)
# To whitelist the following data:
# {"book" => {"title" => "Some Book",
# "chapters_attributes" => { "1" => {"title" => "First Chapter"},
# "2" => {"title" => "Second Chapter"}}}}
params.require(:book).permit(:title, chapters_attributes: [:title])
- Self Documenting, controllers are quite explicit about the data they are about
- More flexible (easier to refactor into other objects)
- Railscast Example
Model
class Topic < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts
attr_accessible :name, as: :user
attr_accessible :name, :sticky, as: :admin
end
Controller
def create
@topic = Topic.new(params[:topic], as: current_user.try(:admin?) ? :admin : :user)
if @topic.save
redirect_to @topic, notice: "Created topic."
else
render :new
end
end
Using Strong Parameters it becomes
private
def topic_params
if current_user && current_user.admin?
# allows all parameters if admin
params.require(:topic).permit!
else
params.require(:topic).permit(:name)
end
end
Moving it for re-use
# /app/models/permitted_params.rb
class PermittedParams < Struct.new(:params, :user)
def topic
if user && user.admin?
params.require(:topic).permit!
else
params.require(:topic).permit(:name)
end
end
end
In application controller
# /app/controllers/application_controller.rb
def permitted_params
@permitted_params ||= PermittedParams.new(params, current_user)
end
In TopicsController
# /app/controllers/topics_controller.rb
def update
@topic = Topic.find(params[:id])
if @topic.update_attributes(permitted_params.topic)
redirect_to topics_url, notice: "Updated topic."
else
render :edit
end
end
- It doesn't handle all situations (but it's ruby so coding around it isn't too hard)
- Example of whitelisting unknown attributes
params = ActionController::Parameters.new(user: { username: "john", data: { foo: "bar" } })
# let's assume we can't do this because the data hash can contain any kind of data
params.require(:user).permit(:username, data: [ :foo ])
# we need to use the power of ruby to do this "by hand"
params.require(:user).permit(:username).tap do |whitelisted|
whitelisted[:data] = params[:user][:data]
end
# Unpermitted parameters: data
# => {"username"=>"john", "data"=>{"foo"=>"bar"}}
class UsersController < ApplicationController
def update
user = User.find(params[:id])
user.update_attributes(UserParams.permit(params))
respond_with user
end
class UserParams
def self.permit params
params.
require(:user).
permit(:first_name, :last_name)
end
end
end
Can be tested like so:
describe UsersController::UserParams do
describe ".permit" do
it "returns the cleaned params" do
user_params = { first_name: "Kanye", last_name: "West" }
params = ActionController::Parameters.new(user: { foo: "foo" }.merge(user_params))
permitted_params = UsersController::UserParams.permit(params)
expect(permitted_params).to eq(user_params.with_indifferent_access)
end
end
end
- Form fields are signed, so no alteration of the fields are allowed
- Form actions are signed. That means a form with an action of
/admin/users/3
will not work when submitted to/users/3
- Form views are digested. So if you remove a field from your form, old forms will not be accepted despite a valid signature
<%= form_for @user, signed: true do |f| %>
<% f.add_signed_fields :zipcode, :state # Optionally add additional fields to sign %>
<%= f.text_field :name %>
<%= f.text_field :address %>
<%= f.submit %>
<% end %>
Pundit provides a set of helpers which guide you in leveraging regular Ruby classes and object oriented design patterns to build a simple, robust and scaleable authorisation system.
class TopicPolicy
attr_reader :user, :topic
def initialize(user, topic)
@user = user
@topic = topic
end
def create?
user.admin? or not topic.published?
end
end
In your topics controller
def create
@topic = Topic.new(params[:topic])
authorize @topic
if @topic.save
redirect_to @topic
else
render :new
end
end
Trivial to wrap up permitted parameters into the same objects used for authorisation
class TopicPolicy < Struct.new(:user, :topic)
def permitted_attributes
if user.admin? || user.owner_of?(topic)
[:name, :sticky]
else
[:name]
end
end
end
class TopicsController < ApplicationController
def update
# ...
if @topic.update_attributes(topic_attributes)
# ...
end
private
def topic_attributes
params.require(:topic).permit(policy(@topic).permitted_attributes)
end
end
- https://github.com/rails/strong_parameters
- http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters
- http://blog.sensible.io/2013/08/17/strong-parameters-by-example.html
- Railscasts Pro #371 (http://railscasts.com/episodes/371-strong-parameters)
- rails/rails#9454 (see comment thread about things it's not designed to handle)
- http://robots.thoughtbot.com/post/49374573556/strong-parameters-as-documentation
- http://pivotallabs.com/rails-4-testing-strong-parameters/
- https://github.com/erichmenge/signed_form
- https://github.com/elabs/pundit