params.expect where they used to generate require and permit. The new form is shorter. It also fixes an ordering problem that has raised avoidable exceptions in Rails applications for as long as strong parameters have existed.
What this covers
- The exception
requirethenpermitraises on tampered input. - Why reversing the order fixes it, and why
expectis better than doing that yourself. - Declaring a parameter as an array so it cannot arrive as a hash.
Requirements
The problem with require then permit
The familiar form reads:require(:post) returns whatever is at that key and asserts only that it is present. permit is then called on the result.
That is fine while post is a hash. It is not fine when someone sends ?post=hello. require happily returns the string "hello", permit is called on a String, and the request dies with:
NoMethodError is the wrong outcome. The parameters were malformed, which is a client error, and strong parameters exist precisely to reject malformed input. Instead the code sanitizing the input is the code that crashes. Bots probing an application find this quickly, which is why these show up in error tracking in volume.
Why reversing the order fixes it
Callpermit first and the shape is checked before anything is extracted:
permit evaluates post against a declared shape of “a hash that may contain title and body”. A string does not match, so it is dropped rather than passed on, and require then raises ActionController::ParameterMissing, which Rails already handles as a 400.
What expect does
params.expect is that ordering, built in:
ParameterMissing rather than NoMethodError, without you having to remember which way round to write two methods.
Declaring arrays
The second thingexpect fixes cannot be expressed with permit at all.
Consider nested categories. With permit, sending post[categories][name]=foo produces a single hash where your code expects an array of them. The parameters are permitted, the types are wrong, and the failure surfaces later, somewhere else.
expect lets you require an array, using a second pair of brackets:
Migrating
expect is opt-in, and require and permit are not deprecated. Nearly every existing Rails application uses them, so they will be around for a long time.
- On Rails 8: move controllers to
expect. Rails moved its own internal controllers, including Action Mailbox, in the same change. The edit is mechanical: replacerequireandpermitwithexpect, and add a second pair of brackets around anything that should be an array. - Not yet on Rails 8: write
permitbeforerequire. You get the ordering benefit without the upgrade.
What this changes in your error tracking
This is one of the few refactors with a directly visible effect on your incident list.NoMethodError: undefined method 'permit' for an instance of String is noise. It comes from whoever is probing your application, not from your users. It carries no information after the first occurrence, and it competes for attention with real regressions. Moving to expect turns those into ParameterMissing, which Rails answers with a 400 without raising through your application.
Two things worth doing alongside the change:
- Watch the incident in error tracking to confirm it stops after the deploy. Add a deploy marker so the before-and-after is visible on the graph. The request parameters on each incident show what was submitted, so you can tell a probe from a form your own application posts.
- If some volume remains, ignore the error rather than letting it sit in your list. Ignore it because you have established it is client-side noise, not because it is loud.
Transcript
Transcribed from the video and lightly edited: the automatic captions misheard a number of product and API names, and those have been corrected.Read the transcript
Read the transcript
0:02 Hey guys, this episode we’re gonna be talking about the brand new
params.expect in Rails 8 that you may or may not have upgraded to yet. You will see this in your scaffolds where you get params.expect ID, you also see params.expect your model name like post with title and body. And this addresses a couple of issues with the old way of doing things. So let’s take a look at some of those problems. The old way that you might do this is you might say require post, permit, title and body attributes, right?0:35 So if we were to go into our Rails application, here I’ve taken the new action and I’m just printing out the params that are permitted through there. So right now we see params.expect is raising an error saying there is no post. Now we are gonna use that params.require. And it will also say that this is invalid because there is no post in the params. So let’s add post and normally we do something like this where a title, the attribute is in square brackets next to post so it knows to put it inside of post and make that a hash.1:10 However, we have oftentimes malicious users or bots or whatever poking around changing things up. And so if we say post equals hello where it’s a string instead of a hash, our params are going to blow up with this no method error rather than actually sanitizing those params like it’s supposed to. So this is a problem where we have params.require post. This is going to give us, let’s blow this up a little bit. This is gonna give us the string back of hello and then obviously permit is not a method on that string.1:42 It needs to be on a parameter object. So the way to fix this is actually to flip flop it. So the way we can do that is we can say params.permit post with title and body. And then we also tell it to do that require of post. So in order to try that out, we will see if this require fails with the correct error message and the parameter missing error.2:13 And that is because when params.permit looks at this, it’s going to then evaluate post and see that, oh, this needs to be a hash with title and body is optional inside of it. And that is a string and not a hash. So we should not permit that parameter. So there we go, we get an empty item here, which then allows us to require post. But all of our scaffolds and controllers have done the opposite where they’ve done the require first since strong parameters came out.2:47 And this is one of the issues that params.expect handles for us. If we run this, we will get that missing param method automatically or missing param post with the parameter missing error just like we see here out of the box. But we don’t have to like double specify this require. It is going to be just this portion of everything for us, which is great. So that simplifies this a little bit and makes this a little bit more secure so that people tampering with parameters are going to get the correct errors back with these being invalid requests.3:29 So that will sanitize things a little bit smoother. But what happens when we have something like maybe we have a nested item like categories and categories could have a name on them. So if we were to do this and we say post with let’s do title and post categories as foo. This right here is going to then see that categories was not a hash, it was just a string. So it is not permitted in this parameters.4:01 But if we were to say categories name foo, this is going to set categories to that sub permitted parameters. But this is wrong because we want an array instead of a single item. And so our parameters are still something that can be messed with a little bit here because we need that empty square brackets inside to tell rack and action dispatch to make sure that this is parsed as an array instead of an object like a hash. So this is something that is not really doable in params permit, but we can do that in here. And the way we do it is we would say categories and we would do double square brackets so that the first square bracket says hey this is going to be an array and then the attributes that are permitted inside of that array for each of the objects.4:59 So now if we refresh this we will get the correct thing where it is square brackets for the category. So we get an array, if we were trying to mess with this and assign categories to a hash instead it is going to say that is not permitted and take care of that completely. And you will see the old way of doing things if we re-enable that we’ll still set categories but it will set it to a single item instead of an array which is not what we want. That is not properly being trusted and sanitized here for us. So params expect is a great improvement just to make this more consistent and less tamperable.5:41 So we have to do it correctly in order to give us those types. It either needs to be a hash or an array or an individual value and we can tell it that and it will enforce it like it is supposed to. Now one thing I want to point out before we go is that params expect is opt in, your existing Rails application controllers do not have to use this permit and require probably going to exist for a long time because most all Rails applications use them currently and it would be a very big change to require people to move to expect. And while permit and require do continue to work params expect is really the recommended way to do this just to keep things a little bit safer in your Rails apps. So if you can upgrade to Rails 8 definitely upgrade your controllers to the new syntax and if you can’t upgrade to Rails 8 quite yet use the permit and then the require in that order so that you will get a little bit more of that benefit of order of operations for permitting those parameters.6:46 So when people are doing those malicious post equals name or foo or whatever and submitting a string instead of a hash or an array you will be protected by those and you will have less errors in your error monitoring and this will be taken care of automatically for those situations which is great. So I highly recommend doing at least this but if you can get up to Rails 8 then take care of all those params methods and make sure that you use params expect everywhere you can. If you want to see some of the internals of this Martin Emde works on RubyGems and Bundler and made this PR to Rails back in like April finally got merged in September and there’s a lot of great discussion talking about how it should work and the back and forth on those various things ‘cause this is quite a core feature of Rails at this point to be changing. So it’s a pretty big PR here and it goes and updates all of the Action Mailbox and other internal controllers inside of Rails to use params expect as well and all of these are really pretty simple.7:59 It’s pretty much adding expect, removing require and permit and then wrapping any of those arrays with an extra square brackets to handle that. But take a look at this PR it’s really great, great discussion and stuff if you want to see what goes on on a core feature like this by the Rails core team and stuff. So that is it, params expect I think is gonna be a great improvement just protecting our code just a little bit more and we will benefit from that with less of those undefined method permit issues whenever somebody is poking around where they shouldn’t be. So that’s it for this episode, I hope you enjoyed it and I will talk to you in the next one. Peace!Related tutorials
About this tutorial
This tutorial summarizes a GoRails screencast onparams.expect in Rails 8, by Chris Oliver. The screencast is the original work. GoRails publishes it, and the rest of the series, at gorails.com.