When you write code every day for a living, it’s easy to get hyper-focused on building out new features and getting things done quickly. What’s much harder is to take a step back and figure out exactly how something is working. Of course, sometimes this happens inherently and without any effort on your part — say for example, when you’re fixing a bug or need to integrate with a third-party service and are forced to understand what’s happening on a more granular level. But generally speaking, that is less common when you’re working a within a framework that you’re already comfortable with and use daily, without giving it a second thought.
I had one of those “take a step back and question everything” kind of moments recently. I was writing a controller for the admin interface of a Rails application, and hit a roadblock. To be clear, there wasn’t anything super complex about the controller I was writing; it had the basic CRUD actions that any controller does, and I had written controllers like it plenty of times before. Yet somehow, when I got to writing the params
private method for this controller, I couldn’t remember what methods I needed to use. I was super tempted to open up another controller and just copy and paste the strong parameters from one file into another. But I realized that this wasn’t really going to help me at all. What I really needed to do was grasp how strong parameters worked on a more conceptual level. If I could understand why we use the methods that we use, and to what end, I would never need to even look up the documentation for whitelisting parameters ever again!
So that’s exactly what I did. I decided to peek under the hood of Rails’ ActionController
, and set my mind to learning everything there was the know about strong parameters. Spoiler alert: I didn’t completely succeed, and I definitely don’t know everything about whitelisting parameters. But what I did finally come to understand was why we use the methods that we do (think require
and permit
), and why we invoke them in that order. And hopefully I’ll be able to explain how some that black box magic in Rails actually works!
Strong(est) Params
When we’re first introduced to the Rails framework, there are some fairly basic components that we learn about, including the MVC structure, which stands for Model, View, Controller. Personally, I found models and views to be far easier to grasp than controllers. Controllers were a whole other beast entirely. In fact, one of the very first concepts that I struggled with was the very basics of writing a controller action! I eventually got a better at that, and now for our bookstore application, I have a more sophisticated controller (in this case, for our Order
objects), which looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
Nice! We’re using our new memoization technique, and we’re rendering json responses to make it much easier to integrate with a JavaScript frontend; of course, we could have also just rendered our @order
instance if this were a simple Rails application with no frontend framework. But…there’s one thing that we’re still missing, and clearly put off because I was dreading it: our strong parameters!
The ActionController
strong parameters module was introduced back in 2012 with the release of Rails 4. The idea behind strong params was to abstract out the creation of models via mass assignment into the controller, rather than in the context of a model. Prior to this feature, we used to need to whitelist attributes in our models using attr_accessible
1 2 3 |
|
in order to use mass assignment to instantiate an object from our controller actions:
1 2 3 4 5 |
|
Not the best solution, right? Enter our knight in shining armor: strong_parameters
, a gem that the Rails core team released to fix this problem, which was eventually merged into Rails. Okay, let me rephrase that: our rather misunderstood knight in shining armor.
In order to comprehend how strong params permits attributes, there are two essential methods we are required to understand.
Requirements
The main reason for even needing strong parameters of any sort is to ensure that we’re only passing safe, protected data from our user-accesible interface. We don’t really expect most users to try to pass in malicious data, but it’s always a possibility. Moreover, there are various situations (dealing with money, for example, or people’s personal information) when delicate information must be passed around securely across the web.
So, how can we ensure that only the correct data is being passed through, and that too in a safe way? The Rails solution to this problem is to whitelist a set of attributes that should be allowed to be modified and passed through by a user (regardless of whether the role of the user is that of a guest, admin, etc.).
The first step to whitelisting any set of attributes is making sure that the parameter we are looking to “whitelist” is actually present. If we think about it, this is pretty logical: how can we pass in the correct attributes to create or update an object if don’t first check that we have the parameter that contains the attributes we need? Only once we ensure that a parameter exists can we begin to strip away the “blacklisted” attributes.
In fact, that’s exactly what this method does under the hood. If we peek into the source code for this method, this is what we’ll find:
1 2 3 4 5 6 7 8 |
|
At this point, we should be wondering what self
is in the context of this method. In other words, what object responds to this require
method? And what is this ParameterMissing
error, exactly? Let’s take a look at the require
method documentation to find out:
require
ensures that a parameter is present. If it’s present, returns the parameter at the given key, otherwise raises an ActionController::ParameterMissing error.
Okay, so now we know that the error being raised in the conditional is actually an ActionController::ParameterMissing
error. One question answered. But what does require
get invoked on? Well, it turns out that the answer to that is a new instance of an ActionController::Parameters
object!
There’s a great StackOverflow answer that points this fact pretty explicitly. The params
object that we refer to in the context of our controllers gets treated as though it’s just an instance of a Ruby Hash
. But in actuality, it’s an instance of something called ActionController::Parameters
, which is the object that responds to the require
method. But more on that a little bit later.
For now, all that matters is that the require
method returns a new instance of an ActionController::Parameters
object for the key that is passed into it. If that key doesn’t exist in the object, it throws an error. What does this mean, exactly? Well, when we write our order_params
method in our OrdersController
, it will begin like this:
1 2 3 |
|
We’re actually returning the value of the parameter at the key that we’re requiring (in our case, order
):
1 2 3 4 |
|
And what’s more: we’re creating a new instance of an ActionController::Parameters
object whenever we invoke params
in our controller! Okay, so that’s the first step: making sure our parameter exists. The second requires a bit more…permission on our part.
Permissions
Now that we’re sure that our parameter exists, we need to actually permit the correct attributes on our objects. Since our users can add more books to their cart while they’re shopping, they should be able to update the total
of their Order
. However, the number
on their order is unique (and probably has an index on it), and is generated on the backend when it’s created. We definitely don’t want them to be able to update or modify that value at any point!
So, we need to permit
a single attribute on our required parameter key. That means that our whitelisted params now look like this:
1 2 3 |
|
So what’s happening here? Well, the permit
method also is defined on the ActionController::Parameters
class. It returns a new ActionController::Parameters
instance, which includes only the given filters — the arguments that we’re passing in here as symbols — and sets an attribute using a protected instance method called @permitted
on the newly-created ActionController::Parameters
to be true
.
This means that if we invoke permit
on our required parameters, we’ll return the actual hash, not the value for the parameter
1 2 3 4 |
|
Wait, but how does this happen? Well, if we look at the source code, we’ll see that the permit method takes an array of arguments named filters
, and then iterates through them to check what type of object they are:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
The reason for the case
statement here is to handle nested attributes, which might look like this:
1 2 3 4 5 6 7 8 |
|
So if we really only need these two methods to require a parameter key and then permit the correct key and value pairs inside of it, what’s the need for having a seperate method? Why do we need to encapsulate this logic? We could very well just do something like this, right? And it would work:
1 2 3 4 5 |
|
It turns out that abstracting our strong params out into a private order_params
method is just a good practice that was established by the Rails core team. The Rails guides explain this pretty well:
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, you can specialize this method with per-user checking of permissible attributes.
Another reason for having a separate method is because things can get kind of tricky once you need to permit nested attributes. At that point, it’s especially important to understand how these methods work.
Tune in next week, when I’ll continue this deep dive by exploring the ActionController::Parameters
class and answer the super confusing question of where params get instantiated (no really…where?), and what makes a parameter different from a hash (no really, I promise that they’re different!). Until then, keep peeking into that source code and stay afloat like this guy:
tl;dr?
- The
require
method ensures that a parameter key is present, and throws an error of it doesn’t exist. Thepermit
method filters out the keys that we want to whitelist based on the filters (which we pass in as symbols) that are passed into it. - Check out the documentation on
ActionController::Parameters
in the Rails guides. - This blog post does a great job of explaining permit and require by example.