Words and Code

One writer’s journey from words to code.

Peeking Under the Hood of ActionController Parameters, Part 1

#technicaltuesdays, rails, ruby


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
class OrdersController < ApplicationController
  def create
      @order = Order.create(order_params)

      render json: order
  end

  def show
      render json: order
  end

  def update
    if order.update(order_params)
      render json: order
    else
      render json: {}, status: :unprocessable_entity
    end
  end

    def destroy
      order.destroy

      render json: {}
    end

    private
    def order
      @order ||= Order.find(params[:id])
    end

    def order_params
      # OH GOD WHY?!
    end
end

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
class Order < ActiveRecord::Base
  attr_accessible :total, :number
end

in order to use mass assignment to instantiate an object from our controller actions:

1
2
3
4
5
class OrdersController < ApplicationController
  def create
      @order = Order.create(params[:order])
  end
end

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
def require(key)
  value = self[key]
  if value.present? || value == false
      value
  else
      raise ParameterMissing.new(key)
  end
end

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
def order_params
  params.require(:order)
end

We’re actually returning the value of the parameter at the key that we’re requiring (in our case, order):

1
2
3
4
params = ActionController::Parameters.new(order: { total: 100.00, number: 'ABC123' })

params.require(:order)
# => { total: 100.00, number: 'ABC123' }

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
def order_params
  params.require(:order).permit(:total)
end

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
params = ActionController::Parameters.new(order: { total: 100.00, number: 'ABC123' })

params.permit(:order)
# => { order { total: 100.00, number: 'ABC123' } }

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
def permit(*filters)
  params = self.class.new

  filters.flatten.each do |filter|
    case filter
    when Symbol, String
      permitted_scalar_filter(params, filter)
    when Hash then
      hash_filter(params, filter)
    end
  end

  unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters

  params.permit!
end

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
params.require(:order).permit(
  :total,
  :books: {
      { # book_object_1 }, 
      { # book_object_2 }, 
      { # book_object_3 } 
  }
)

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
class OrdersController < ApplicationController
  def create
      @order = Order.create(params.require(:order).permit(:total))
  end
end

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. The permit 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.