Improvements to Strong Params

I’ve just been getting re-acquainted with Rails 4 after spending the past year working in some other frameworks.

One thing I noticed are strong parameters which I see as a really great addition! One flaw I’ve noticed however is in the behaviour of params.require which I don’t think is 100% user friendly yet. The two major issues I can see are:

  • It returns a value, when really what it should be doing is returning params to continue with a fluent API

  • It doesn’t accept multiple values (an array)

It’s been observed in the strong params github issue tracker by several others that this violates the principle of least astonishment by coupling unrelated functionality (returning nested keys) with the enforcement of parameter presence. It also could be argued that people might expect to be able to use it as follows:

my_params = params.require(:one, :two, :three).permit(:one, :two, :three, :four)

This basically sets up a simple request-specific schema for parameter presence as it begins to travel further into the application’s layers. I think the feature of params.require being able to verify the presence of a key and then returning it’s contents for nested attributes needlessly couples two pieces of functionality. I typically store all attributes for a request at the root of my params and so I never have any nested object data to dereference. It’s all at the top, and this results in me having to make multiple separate calls to params.require just to verify each one. Which as I’m sure you can guess gets very ugly, very quickly:

params.require :one

params.require :two

params.require :three

my_params = params.permit :one, :two, :three, :four

I expect params.require(:key) to return its value, not self. I can see that perspective on astonishment, though.

In practice, it leads to fluently chained code to set up your params "schema":

def post_params
  params.require(:post).permit(:subject, :body, ...)
end

Sure, but keep in mind, you’ve made an assumption about the schema of the posted variables and imposed it on all consumers of the API.

Probably goes beyond astonishment at that point…

I’ve just been getting re-acquainted with Rails 4 after spending the past year working in some other frameworks.

One thing I noticed are strong parameters which I see as a really great addition! One flaw I’ve noticed however is in the behaviour of params.require which I don’t think is 100% user friendly yet. The two major issues I can see are:

  • It returns a value, when really what it should be doing is returning params to continue with a fluent API
  • It doesn’t accept multiple values (an array)

It’s been observed in the strong params github issue tracker by several others that this violates the principle of least astonishment by coupling unrelated functionality (returning nested keys) with the enforcement of parameter presence. It also could be argued that people might expect to be able to use it as follows:

my_params = params.require(:one, :two, :three).permit(:one, :two, :three, :four)

What would this specify the expected format as? Doing the more usual:

params.require(:one).permit(:one, :two, :three, :four)

is saying that you expect params like:

one:

one: (scalar)

two: (scalar)

three: (scalar)

four: (scalar)

In the case where require takes multiple keys, is it expecting the sub keys in each of those? Any of them?

This basically sets up a simple request-specific schema for parameter presence as it begins to travel further into the application’s layers. I think the feature of params.require being able to verify the presence of a key and then returning it’s contents for nested attributes needlessly couples two pieces of functionality. I typically store all attributes for a request at the root of my params and so I never have any nested object data to dereference. It’s all at the top, and this results in me having to make multiple separate calls to params.require just to verify each one. Which as I’m sure you can guess gets very ugly, very quickly:

params.require :one
params.require :two
params.require :three
my_params = params.permit :one, :two, :three, :four

Does this actually work? I swear I’ve gotten into trouble with calling permit on the top-level params hash, since there’s other stuff (:controller, :action, :format, etc) in there…

—Matt Jones

That’s a really good question, but I don’t know if presenting a new problem necessarily invalidates the original issue which is that by returning the “required” value, the API is basically saying strong params has no way of enforcing more than one required value. What if one of the values contained within the returned structure was itself also required?

one: (index, required)

one: (scalar, required)

two: (scalar, not required)

I think that rather than trying to accommodate all these legitimate - but complex - edge cases, it’d be smarter to just remove the complexity and push nested validation to somewhere else. I’d be willing to bet there are quite a few people out there misusing the API as it is currently such that at the very least, the name require isn’t really helping. And again, I’m not even using nested structures. If the idea is to move away from validations coupled with the model (something I agree with), params is going to have to pick up a lot more slack.

Alexander,

In your last example, I think require would actually work fine as-is, i.e., params.require(:one).require(:one) would check the presence of the root and the specified child node.

That said, I think you have a good point that require may be a little too strongly opinionated and lacks consistency with permit. I don’t think making a breaking change to require is the answer though, not at this point at least.

I know the core team must constantly fight against API bloat, but I’m wondering if simply adding another method is the best compromise here. I threw together a params.ensure method – not a perfect name, I know – that I think provides the flexibility you want, specifically chaining and allowing multiple/nested keys in single call, similar to though not as robust as permit. (If you look at my sample, focus on API, not specifics of implementation.)

I personally have no problem with require as I tend to follow the root element pattern, but I definitely get how it could be cumbersome to work with if you don’t.

-Al

WRT my example, try two required params in the child structure instead of just one then :wink:

A part of me thinks require was designed in haste and a regression - while never pleasant - shouldn't be ruled out this early in the game.

Two methods will have to exist after all is said and done for sure. But just because anyone has standardised around a potentially bad design and has no problems with it doesn't vindicate that it is making people do very obscure and esoteric things with params. It's still a bad design imposing a requirement on your request schema.

Broken windows being what they are....

Apologies in advance if this is off topic, but after deciding strong params didn’t work well with my particular use case I decided to write this: https://github.com/ismasan/parametric

I understand it might be overkill for most cases but I wonder if Rails’ built-in solution could benefit from a rethink of the strong-params DSL along similar lines.

Looks interesting - although I think it's important that the design of a rails feature still get the attention it deserves...