Strong Parameters The Right Way
StrongParameters is a great gem and it comes with Rails 4 by default. Currently, there are two patterns to work with your attributes.
1. Creating a private method on your controller which returns the whitelisted attributes. #
# app/controllers/people_controller.rb
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
Pros #
- If your person_params logic is simple as the example shows. That’s pretty much, just call the method and it will do the job.
Cons #
If your person_params logic grows because, let say you want to permit certain params attributes if your user is an admin and another params attributes if it’s just a regular user:
- You’ll be adding more complexity to your controller.
- You wont be able to reuse these logic.
- Your controller will be harder to test.
2. Extracting your params logic into a class. #
# app/models/permitted_params.rb
class PermittedParams < Struct.new(:params)
def person
valid_keys = [:name, :email, :message]
params.slice(*valid_keys).require(:person).permit(*valid_keys)
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
private
def permitted_params
@permitted_params ||= PermittedParams.new(params)
end
end
# app/controllers/people_controller.rb
class PeopleController < ActionController::Base
def update
person = current_account.people.find(params[:id])
person.update_attributes!(permitted_params.person)
redirect_to person
end
end
These approach, in my opinion is better than the first one. You should checkout the StrongParameters’ Railscasts for more deatils if you liked these approach.
Pros #
- You’ll decouple your controller’s logic from your params logic, in which case, you’ll keep your controllers dry.
- Your controllers will be easier to test.
- You’ll avoid code duplication.
Cons #
If you have a lot a resources, because applications usually grows and not the other way:
- Your PermittedParams class will end up being a huge and will be hard to maintain.
- You’ll be breaking the Single Responsability Principle.
So, which is exactly the best way to handle my parameters? #
The best way to handle your resource params would be to have a PermittedParams class type for each resource. Wouldn’t be better to have your resources params’ logic in separate classes like this?:
# app/parameters/user_parameters.rb
class UserParameters < Struct.new(:params)
def permit
params.require(:user).permit(:name, :age)
end
end
class ApplicationController < ActionController::Base
protect_from_forgery
# Returns an instance of the params class related to the Controller from which was called
def permitted_params
@permitted_params ||= permitted_params_class.new(params)
end
# Returns the params class related to the Controller from which was called
def permitted_params_class
class_name = params[:controller].to_s.singularize
formatted_class_name = class_name.camelcase
@permitted_params_class ||= "#{formatted_class_name}Parameters".constantize
end
end
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
User.create(permitted_params.permit)
end
end
Pros #
- You’ll decouple your controller’s logic from your params logic, in which case, you’ll keep your controllers dry.
- Your controllers will be easier to test.
- You’ll avoid code duplication.
- Your params classes will be smaller, easier to test and maintain.
- Your params classes will not violate the Single Responsability Principle.
Cons #
Some people might think that this is an over-engineering work if your controllers are simple and you have to deal with params like the first approach I described.
ActionParameter #
It’s a gem I created to implement this last pattern with some other features. I encourage you to take a look at it and try it, I’m pretty sure it would be helpful.
Conclusion #
All three patterns are good, you should choose the one that fits your needs. But bear in mind that applications and their logic always tends to grow and to get more complex.