Why I do not use strong parameters in Rails

Strong parameters have supposedly been an improvement in Rails 4. Yet, it feels to me, they were more a marketing trick after Github’s hacking, than a real feature.

It is surprising, as Rails is known to include controversial features which can hurt the developer. The latest one being, the introduction of global variables inside Current , see Ryan Bigg’s legitimate rant there.

For each controversial addition, the reasons put forward by DHH are most of the time a mix of:
- it is easy and user friendly
- anyway developers are responsible for their code

Except for Strong Params where we ended up with a not easy, neither user friendly way to handle params.

I have wondered many times how to permit params, how to permit an array of params, how to permit params which are actually hashes themselves and how to test them…
Judging by a number of questions about these topic on Stackoverflow, I can tell I am not the only one.

Eventually, I decided to give up on strong parameters. They were just a cause of trouble for me. It was quite simple to get rid of it.t.


For tips about how to get a bullet proof app, see my upcoming book:


How NOT to use Strong params and still be a responsible developer?

Even before Rails 4, whitelisting parameters was possible. Ha!

What is whitelisting? 
It is keeping only keys/values you want/expect.

How to do it? 
ActiveSupport adds the slice method to Hashes:

h = {a: 1, b: 2}
h.slice(:a) # => { a: 1 }

So here you are, it is as easy as using slice . So why Strong params? To force you to think about it… All of a sudden you cannot be trusted anymore.

A simple work around is to rely on request.parameters which would provide you with your good old params as HashWithIndifferentAccess . So you can do:

@post.update!(request.parameters[:post].slice(:body, :title))

But isn’t it unsafe?
It is a simple way to get good old params back in a HashWithIndifferentAccess form.
It can indeed be dangerous if you do not slice properly afterwards in the service. But the whole point there is to get a Hash and filter.

If you do not want to use strong params at all, you can redefine params in ApplicationController :

class ApplicationController < ActionController::Base
def params
request.parameters
end
end
# and then in any controller:
@post.update!(params.slice(:body, :title))

I rarely manipulate params in the controllers.
I delegate business logic to other classes, also known as Service Objects. Here is an example:

class Services::SavePost
  def initialize(post:, params:)
@post = post
@params = params.slice(:title, :content)
end
  def call
# have logic before save? put it there instead of callbacks
# have logic before create? check @post.new_record?
@post.update!(@params)
# have logic after save? put it there instead of callbacks
end
end

So you can tell what the purpose of SavePost is just by reading the name of the class. Because it is responsible for saving a post, you can put the business logic in a meaningful place.

But what sort of object are the params we expect in the service? 
A mere Hash, because it is the most reusable way.

Why do we whitelist in the service?
The Service job is to save the post. It has to know what is allowed: it is its job.

How to use it?

def create
@post = current_user.posts.build
Services::SavePost.new(
post: @post,
params: request.parameters[:post]
).call
# handling success and failure in service objects is yet
# another story and is developed in my book
end

Thanks to Tomasz Ras for proofreading!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.