Wham Bam, Thanks, Params
How “params” is generated, used, and is stronger than a plain hash
So you’ve gotten started with Ruby and you’re ready to bring your code to life with Rails! You’ve gotten the basics of a Model-View-Controller (MVC) design down, your RESTful routes are set, and the server’s up and running. The index view was a breeze, but now you’re trying to render a view of a single instance of your model and you recall the standard syntax for that route (for a cat, because, why not):
get ‘cats/:id’, to: ‘cats#show’, as: ‘cat’
Huh, where’s that :id
coming from and where does it go? This route syntax is indicating to your application that the :id
will dynamically be determined by the value in the URL, which corresponds to the primary key of the instance of Cat
from the database you want to see. For example:
http://<server-domain>/cats/42
would bring up information on a cat with an :id
of 42.
But how does that :id
get passed from the URL to the controller to retrieve the right cat? Nested in the HTTP request is an object, which contains the cat’s id number in a key-value pair. This allows us to find the cat with code like:
class CatsController < ApplicationController def show
@cat = Cat.find(params[:id])
endend
Where did the params hash come from?
The params
is a hash-like object which allows users, whether they know it or not, to send information along with requests to the server. (These parameter objects are not quite hashes, but more on that later.) Some of the more common ways params
will be sent to your controller are through the URL itself, a query parameter, or a form. Let’s go over those briefly.
The URL
As described in the example above, the params
may contain information from the URL in plain sight, like when it identified the primary key of the Cat
object the user wanted to find.
Query parameters
As you travel around the web, take note of any question marks that appear in the URLs (or Uniform Resource Locators) of the pages you are browsing. The “?” delimits the boundary between the resource and the querying string, a key-value pair that is communicated through the params
.
For example, when you use a search engine, you are sending search terms to run some sort of query. You can see some evidence of this in the results page. If you go to DuckDuckGo and enter “cat photos”, the page you get back will have something like /?q=cat+photos
in the URL.
Query parameters are not limited to searching. You may also often see UTM parameters (e.g. ?utm_source=
) in your address bar, which can track if a user has reached a site through an e-mail campaign or a different website, or any number of things! Parameters are everywhere, and contain a lot of useful information.
As a quick use example, as the administrator of animal databases, let’s say you branched out from cats and now work with dogs. You entered these dogs into the database as they came (…or used the Faker gem to generate some) and when grabbing a list of them through Dog.all
, they display in primary key order.
But what if a user wanted to sort them alphabetically? You could make a new page called index_a_to_z_.html.erb
and use a differently ordered list, possibly involving new routes or at the very least, a new view.
Or you could create a link to reload the index page, with parameters, and use some logic in your controller index action to display a different order.
The link in your index view and index action in your dogs controller could look something like this:
<-- dogs/index.html.erb --><%= link_to "See the Dogs in Alphabetical Order", dogs_path(:sort =>'alpha') %>---# dogs_controller.rbdef index
if params[:sort] == 'alpha'
@dogs = Dog.order(:name)
# set instance variable to a list ordered by 'name'
else
@dogs = Dog.all
end
end
Result: http://localhost:3000/dogs?sort=alpha
Form submissions
Another direct way you may encounter params
early on is when you implement a form on your web interface. When a user enters information in a “Contact Us” form, their name and any other information they entered are sent to the controller (and probably ultimately written to your database) via params
. As params
behave like hashes, information can be infinitely nested and hold quite a lot!
A user wants to add another dog to the database. Let’s take a look at an simple example form and the resulting params
object. To add a dog, the user must enter the dog’s name and choose the dog’s type, as predetermined by theType
table.
<!-- dogs/new.html.erb --><h1>Add a Doggo!</h1><%= form_with model: @dog do |f| %>
<%= f.label "Name of doggo" %>
<%= f.text_field :name %><br>
<%= f.label "Type of doggo" %>
<%= f.collection_select :type_id, Type.all, :id, :name %><br>
<%= f.submit %>
<% end %>
(If you’re curious, here is the resulting HTML that the Action View form helpers above generated. The form won’t submit anything, but you can look at the HTML and visualize the form better!)
If a user enters a name of “Hamstar”, chooses “smol pupperino”, and hits that submit button, here are the params
that are returned to the server (the authenticity_token
will differ):
params #=> #<ActionController::Parameters {"authenticity_token"=>"U2K2Nnolg8eGv0ayjnf_1yDUEu1kczoWibSNonUlhGviadr9Ia3uoYnomugheL_A9JtdmpGq3Q7dKnNakIZneA", "dog"=>{"name"=>"Hamstar", "type_id"=>"2"}, "commit"=>"Create Dog", "controller"=>"dogs", "action"=>"create"} permitted: false>
Nestled in there are the values you need to create the dog!
You can also play around with hidden_tag
/ hidden_field
form helpers and buttons (tiny forms), too, to get and pass through the information you seek in your controller methods.
Is params a hash? How do we use params data?
Technically, params
is not a hash, though it looks like one. As you can see above, params
is actually an instance ofActionController::Parameters
. We can interact with params
largely like a hash and in fact, up until Rails 5.0, ActionController::Parameters
did inherit from the Hash
class. As a different class, hashes have some familiar methods that params
does not, such as #flatten
, #each_key
, #each_value
, and #map
.
But params
has a few tricks of its own. Here are all the methods available to params
that a hash can’t do.
:permitted?, :to_unsafe_h, :to_unsafe_hash, :permit!, :converted_arrays, :permit, :require, :parameters, :permitted=, :fields_for_style?, :method_missing, :always_permitted_parameters, :always_permitted_parameters=, :init_with, :required
There is a lot here having to do with safety and permissions! That’s because params
are the conduit through which the unsafe internet/absent-minded user can send data to your controllers and possibly ultimately your database. And for best practices, you should use…
Strong Params! 🦾
You should be glad that params
isn’t exactly a hash because there are two important methods to learn to use on them: #require
and #permit
. If you recall a little while ago, the params
had this little value at the end: permitted: false
. If you tried to use params[:dog]
directly as you would with a hash to provide attributes to #create
a new dog, you would have hit an “ActiveModel::ForbiddenAttributesError” (given standard settings). To allow form data to be used, you would have to do something like this:
# dogs_controller.rbdef dog_params
params.require(:dog).permit(:name, :type_id)
end
The #require
method verifies that the params
contains a key of and information about a dog. If params
comes in without this dog
key, most likely it’s form data from elsewhere, or data that isn’t useful to you to create or update an instance of theDog
class. The #permit
method specifies which keys within dog
to allow the values of. You could allow a user to change a dog’s name, for example, but not their type.
With these two methods performed on params
, you can use it directly in creating your dog with :name
and :type_id
as attributes.
dog_params #=> #<ActionController::Parameters {"name"=>"Hamstar", "type_id"=>"2"} permitted: true>---new_dog = Dog.create(dog_params)
But other than that small feature, treat params
as you would any other hash!
…one more factoid before you leave
Now this is for coding cocktail party chatter - did you notice that one of the params
-only methods was #to_unsafe_hash
? This would turn params
into a hash with indifferent access, which you can do anything to that you would with any old piece of nested data. Use your power wisely.
I hope sharing what I may understand about params
has helped you in some way. Please let me know if anything seems off, and then we’ll discuss and learn more!
Thanks to those involved in this exasperated Stack Overflow question on what params
are and this post on params’
status as a hash or not, who all helped me sleep at night whether they know it or not.