Understanding Rails Parameters
Tags: rails, ruby
Published: 2022-05-17 14:45:19

If you are building a dynamic web application, you will need to access the user-submitted data on the server. Maybe the user uploaded a photo, wrote a comment, posted a tweet, or clicked a link that includes the `id` of the article they want to read. **How do you access this data on the back-end?** 

That's where `parameters` enter into the picture. This article explains everything you need to know to understand Rails parameters. 

<!--more-->

There are two ways to get data from the front-end to the back-end. 

1. **Query Strings** 
   - Includes the data provided in the URL after the `?`, e.g. `blog.com/posts?id=3&user=ak`
   - Data consists of key-value pairs separated by `&`
   - Typically submitted in a GET request, e.g. clicking a link 
2. **Form Submission** 
   - Contains the data entered into a form by the user. 
   - Typically posted in a POST request, e.g. submitting a form

The good thing is that Rails doesn't care about the request type (`GET` or `POST`) when accessing the data on the server. It won't distinguish between the information from query strings and the data from a form submission. It groups all the data into the `params` hash, which is available in the controller. 

## Query Strings

The following route in the `routes.rb` file routes the `/posts/sample` URL to the `sample` method on the `PostsController` class. 

```ruby
get 'posts/sample', to: 'posts#sample'
```

Here's the `PostsController#sample` method. To keep things simple, I am rendering plain text without using a view template. 

```ruby
class PostsController < ApplicationController
  def sample
    render plain: "#{params} \n\nid: #{params[:id]} \nuser: #{params[:user]}"
  end
end
```

Now, when I go to the URL `http://localhost:3000/posts/sample?id=3&user=ak`, it renders:

```
{"id"=>"3", "user"=>"ak", "controller"=>"posts", "action"=>"sample"} 

id: 3 
user: ak
```

Note that the `params` hash also includes the controller and action names. Though you can access it in your controller, Rails recommends using the [`controller_name`](https://api.rubyonrails.org/v7.0.3/classes/ActionController/Metal.html#method-i-controller_name) and [`action_name`](https://api.rubyonrails.org/v7.0.3/classes/AbstractController/Base.html#method-i-action_name) methods to access these values. 

## Form Submission

The following route in the `routes.rb` file routes the

- `posts/add` URL to the `add` method on the `PostsController` class.
- `posts/upload` URL to the `build` method on the `PostsController` class.

```ruby
get 'posts/add', to: 'posts#add'
post 'posts/upload', to: 'posts#build'
```

Here's the `PostsController#add` method, which renders the `add.html.erb` view. With the Rails conventions, it will render the `add.html.erb` view template. 

```ruby
class PostsController < ApplicationController
  def add
  end
end
```

Let's create a simple form in our `add.html.erb` template. When submitted, this form will make a `POST` request on the `/posts/upload` endpoint. 

```erb
<form action="upload" method="post">
  <div>
    <label for="name">Name:</label>
    <input type="text" id="name" name="user_name">
  </div><br>

  <div>
    <label for="mail">E-mail:</label>
    <input type="email" id="mail" name="user_email">
  </div><br>

  <button type="submit">Submit</button>
</form>
```

To handle this `Post` request, let's add the `build` method on the `PostsController`. Again, I am simply rendering the data as plain text, without the view to keep things simple. I have also disabled the Rails `authenticity_token` security measure to avoid adding unnecessary complexity to the example (don't do this in a real application). 

```ruby
skip_before_action :verify_authenticity_token, only: :build

def build
  render plain: "#{params} \n\nname: #{params[:user_name]} \nemail: #{params[:user_email]}"
end
```

Now, when you enter some data in the form and click the **Submit** button, you are greeted with:

```
{"user_name"=>"Akshay", "user_email"=>"ak@email.com", "controller"=>"posts", "action"=>"build"} 

name: Akshay 
email: ak@email.com
```

Notice that the keys I am using on the `params` hash are the same values I used for the `name` attributes on the form. Rails extracts these values from the submitted data and adds them to the `params` hash. 

Instead of sending the `name` values as keys directly, you can group them under a common key in the `name` attribute. 

```erb
<form action="upload" method="post">
  <div>
    <label for="name">Name:</label>
    <input type="text" id="name" name="client[user_name]">
  </div><br>

  <div>
    <label for="mail">E-mail:</label>
    <input type="email" id="mail" name="client[user_email]">
  </div><br>

  <button type="submit">Submit</button>
</form>
```

Let's modify the `build` method to access the nested parameters: 

```ruby
def build
    render plain: "#{params.to_json} \n\nname: #{params[:client][:user_name]} \nemail: #{params[:client][:user_email]}"
end
```

This prints the following output: 

```
{"client":{"user_name":"Akshay","user_email":"ak@email.com"},"controller":"posts","action":"build"} 

name: Akshay 
email: ak@email.com
```

Notice that Rails has grouped the `user_name` and `user_email` under the `client`.  

## Sending JSON

For a rails API, you can send JSON data from the client. Rails will load it into the `params` hash, and you can access it in the controllers. For example, if you send the following JSON data:

```json
{ "company": { "name": "acme", "address": "123 Carrot Street" } }
```

The `params` hash will be

```ruby
{ :company => { "name" => "acme", "address" => "123 Carrot Street" } }
```

## Dynamic Parameters

If your route URL contains a symbol, Rails will use that symbol name as the key in the `params` hash. In the following example, the route instructs Rails to map the URL parameter to `title`. 

```ruby
# routes.rb
get 'posts/lookup/:title', to: 'posts#lookup'

# posts_controller
def lookup
  render plain: "#{params} \n\ntitle: #{params[:title]}"
end

# browser output
{"controller"=>"posts", "action"=>"lookup", "title"=>"rails"} 

title: rails
```

## Passing Parameters when Redirecting

When redirecting to another route, you can pass the parameters by passing them as hash parameters to the `redirect_to` method. For example,

```ruby
redirect_to controller: 'articles', action: 'show', id: 3, user: 'ak'
```

The `ArticlesController` can access the passed values using the `params` hash as usual. However, an important thing to note is that you can't redirect a `POST` request. 

---

I hope that you have a pretty good understanding of how parameters work in Rails by now. To summarize what we've learned so far:

- Rails doesn't differentiate between the data that comes from a GET request (query string parameters) vs. the data that comes from a POST request (form submission), and 
- All the data is available in the `params` hash. 

In the next post, we will look at a common problem in software, known as **Mass Assignment Vulnerability**, and how Rails helps us tackle it by providing strong parameters.