Rails Params: Where do they come from?
Note: I’m currently using Rails 4.0.0 and the branch of Rails I was browsing through on Github was 4-0-stable
. The code you see in this post may look different than the code on your machine if you try to replicate it in the newest version of Rails.
I love Rails. I only started using it a couple weeks ago but even in that short time period I can see why it is a massively successful framework. The easiest way for me to describe it is programming magic.
Run a single command line binary and you have a project skeleton. Run a scaffolding command and you have a working app. It’s like nothing I’ve ever seen.
While Rails’s magic is extremely convenient, it can sometimes be too magical. One of the first Rails conventions that peaked my curiosity bug was params
. Having used Sinatra before, I am familiar with params
. However, it never occurred to me why it was always available without ever needing to be passed in. I can’t explain why this bit of magic nagged at me more than others, it just did.
Whenever I need to implement a show
Controller action, params
is there with the id
. Whenever I am mass assigning a new ActiveRecord
object, there is params
to the rescue with all data I need. It’s just always available at my beck and call — no questions asked.
Many of you may be rolling your eyes right now and asking, “Who cares? It’s convenient and works. Don’t worry about it.” But I do worry about it. I was one of those kids who would push all the buttons on the new TV remote to find out what everything did; much to the chagrin of my parents. I wanted to know how things worked so I could use it to my advantage. params
was my TV remote — I needed to find out how it worked and where it came from.
Before I go any further: this will not be a post about what params
does, how to use it, or what data is passed inside of it. There are a number of guides and blog posts out there that can explain it much better than me. My question was more specific:
Why is params
accessible to the actions in Rails controllers and how does it get there?
If that sounds like something you want to know, keep reading.
Rack
To understand how Rails interprets parameters, you’ll have to understand the basics of Rack and how Rails interacts with it. According to its website:
Rack provides a minimal interface between web servers that support Ruby and Ruby frameworks.
To use Rack, provide an “app”: an object that responds to the call method, taking the environment hash as a parameter, and returning an Array with three elements:
1. The HTTP response code
2. A Hash of headers
3. The response body, which must respond to each
Rack is basically the go between for web servers and your Rails app.
- It takes the request from the server, translates it into an
env
variable (Ruby hash) that Rails can understand. - Rails takes that
env
variable, does what it needs with it, and returns a simple array back to Rack with the status code, headers and response body. - Rack takes that array, translates it back into a proper HTTP response and gives it to the browser to display.
Now that we’ve covered Rack, let’s get into some Rails.
What is “params”?
In order to better understand where params
originates from, we need to better understand what it is. Is it a hash? Method? Something else?
The best way to find this out is to throw a binding.pry
into a controller action and try to reverse engineer the situation by peeling back Rails’s layers. I chose to put the binding.pry
in my Posts#create
since that is where I use params
most heavily.
After submitting a test form request, I was in.
Snooping around in pry revealed some interesting things:
params
is a method on theActionController::StrongParameter
class.- While
params
appears to be a hash, it is actually an instance of theActionController::Parameters
class.
If you are unfamiliar withActionController::StrongParameters
, it is a class that grants us more security and restrictions over which parameters from params
we will allow in mass assignment. If you’ve ever used this syntax before, you can thank ActionController::StrongParameters
:
params.require(:post).permit!
Inside of ActionController::StrongParameters
’s source code, all the way at the bottom, you will find params
in all its programmatical glory.
As you can see, pry was correct. params
is a getter method on the ActionController::StrongParameters
class that returns the instance variable @_params
, which is an instance of the ActionController::Parameters
class.
At first glance, this may not seem like much, but this explains why params
is available to us in our controllers without prior reference. A quick check in your pry console will tell you why.
We have access to params
because our controllers are descendants of ActionController::StrongParameters
and inherit all of its instance methods!
Woohoo! Mystery Solved!
Right?
Not quite.
While this explains the mystery of the omnipresent params
, it doesn’t explain how it got there. If you’re like me, you want to know the whole story. For example, what was this request.parameters
? Where had that come from?
Where Params Comes From
Note: This next section gets very Rails technical. If you get confused, don’t worry, you’re not alone. It took me several hours of debugging and reading Rails code to figure out the next steps. The following information is the result of those hours.
After more snooping in Rails, I came upon ActionController::Metal
. Essentially, ActionController::Metal
is a barebones version of ActionController::Base
and is one of the initial ancestors of our Rails app.
If you look at its source code, you will find several references to request
as well as it’s own set of params
methods.
At this point I should mention that, in order to better dive into the depths of Rails, I included a stack trace logger to complement my binding.pry
.
The reason I mention this is because if you output this stack trace to the console, you will see all the methods invoked when Rails receives a env
variable from Rack — and there are A LOT. If you want to see everything that happens behind the scenes, take a look at this.
While this log is impressive and monstrous, there only a few lines that we need to worry about in regards to params
and ActionController::Metal
.
What this stack trace is saying is that after Rails has been initiated with the env
variable, that variable gets passed all around Rails. At some point, it makes it’s way to the ActionController::Metal::action
class method. Inside this method, ActionController::Metal
initializes a new instance of itself and invokes #dispatch
, which sets @_request
to a new instance of the ActionDispatch::Request
class.
ActionDispatch::Request
is an interface Rails uses in order to interact with the HTTP Request object (env
) that originated from Rack. One of the methods this API provides is #parameters
.
This is where request.parameters
originates from!
To Wrap Things Up
That was a mountain of jargon I just unloaded onto you, so let me condense the steps down.
- User makes a request to the browser (GET/POST/etc).
- Browser sends HTTP request to Rack where it translates the request into an
env
object and passes it to Rails. - Rails passes that
env
object around internally until it makes it’s way toActionController::Metal::action
. ActionController::Metal
uses thatenv
object to create an instance ofActionDispatch::Request
and store it in its@_request
instance variable, available via therequest
getter method.ActionDispatch::Request
parses and formats the parameters from the original HTTP request.ActionController::StrongParameters
uses the#parameters
method supplied by theActionDispatch::Request
instance object to store the formatted parameters in the@_params
instance variable.- The data in that
@_params
instance variable is available to us and our controllers viaparams
.
Now that is quite the mouthful.
I hope you found this deep dive the Rails parameters informative and useful. If you noticed any inaccuracies in this post, please let me know and I will dutifully change them. Thanks for reading!