Making Our App Dynamic

Dynamic Server Responses

Now we've got a route sending requests to our action and we have our action rendering a view, but so far all of our content has been static. Our Rails application now is essentially a file server that fetches a HTML document, wraps it in an HTTP response and returns it to the client.

This was the main paradigm of the Web in its early days, where web servers respond to requests and send back static documents. The modern web, however, gave rise to web "applications" that can assemble responses on the fly to respond to requests.

The dynamic nature of web apps come mainly from the following two sources:

  • The client embeds a piece of data inside the URL for the server to respond to. For example, when we type in the browser "https://www.google.com/search?q=robots" we are asking Google to dynamically assemble a web page containing only search results for "robots".

  • The server responds with data based on some internal state of the data in the server's database. For example, when you visit a blog, the server would retrieve the blog posts stored in the database, and dynamically assemble a web page based on this data.

We'll look at those two scenarios more closely in the upcoming topics.

Dynamic Documents With Embedded Ruby

ERB stands for "embedded Ruby". It's a templating engine that allows us to embed Ruby code in a text document to have dynamic documents.

In an ERB template, the Ruby code has to be put inside <%... %> tags for it be evaluated. Moreover, we can use the <%= ruby_expression %> syntax to put the evaluated value of the ruby expression into the HTML document.

When we "render" a template into a HTML document, it means we are going to run the template through a "rendering engine" (in this case, the ERB rendering engine), then execute the Ruby code embedded and output the resultant document.

For example, this ERB template

<html>
  <body>
    <% name = "John" %>
    <p>Hello, <%= name %>!</p>
  </body>
</html>

will be rendered into

<html>
  <body>
    <p>Hello, John!</p>
  </body>
</html>

In this example, we assigned value "John" to the variable name inside of the ERB template, then used it to render the HTML document. This works, though, it's not really interesting, because the template would always render into the same result.

We can also simply leave the template as

<html>
  <body>
    <p>Hello, <%= name %>!</p>
  </body>
</html>

and pass in the value of the variable name from outside of the template. If the value of variable name is also "John", this template will be rendered to the same result.

Responses With Query Parameters

Now that we know how to use ERB templates to render dynamic documents, we are going to pass a name in the request URL and have the server respond with a custom greeting, for example, "Hello, John!"

To be able to do this, we'll rename our HTML document to hello_world.html.erb. In Rails, this convention means that this is an ERB template that will eventually be rendered into a HTML document.

The ERB template should look familiar:

app/views/application/hello_world.html.erb

<html>
  <body>
    <p>Hello, <%= name %>!</p>
  </body>
</html>

We have a number of ways of passing values from the client to the server, and the most basic is to use query parameters in the URL, for example, http://localhost:3000/hello_world?name=John. The part following the ? in the path, name=John, is the query string that can be interpreted by the server

Let's see how we can access these parameters in our action:

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  def hello_world
    name = params['name']
    render 'application/hello_world', locals: { name: name }
  end
end

Rails parses the query parameters into a hash called params. In our action, we take the value of the name query parameter and pass it to the ERB rendering engine to render the hello_world.html.erb template.

If the previous line of code looks a bit confusing, it can also be written as this below:

render('application/hello_world', { locals: { name: name } })

You can see this is really a method call with two arguments, and the second argument is a hash with a key/value pair -- the key is :locals and the value is another hash. When the ERB engine sees the :locals key in the hash, it will use the value of that key to render the template.

Now if you type into your browser http://localhost:3000/hello_world?name=John you will see "Hello, John!".

This works great, but we've also introduced a bit of a problem: when we hit /hello_world all by itself now, we get this:

Hello, !

The reason being, name here is nil. There's no query parameter passed in with key name; this results in our strangely punctuated message above when the template is rendered.

We could go about fixing this in a number of ways, but let's just give name a default value inside our action:

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  def hello_world
    name = params['name'] || "World"
    render 'application/hello_world', locals: { name: name }
  end
end

Now we see Hello, World! again if we don't pass along a name in the query string.

With the params['name'] || 'World' line above, the || (logical OR) works as a check to see if anything is in params['name'] or not. If its value is truthy (any value other than false or nil), it simply returns the value. Otherwise ("OR"), it returns 'World'.

Responses With URL Capture Pattern

Query parameters are useful to pass data to the server, however, it is not the only way. We can also specify URL matching patterns for routing and capture data.

For example, we are going to add a new line in our routes.rb file:

config/routes.rb

Rails.application.routes.draw do
  get '/hello_world/' => 'application#hello_world'
  get '/hello/:name'  => 'application#hello_world'
end

Now hitting /hello/John will also display Hello, John!

The pattern /hello/:name tells Rails to route any request with URL like /hello/John to the hello_world action in the ApplicationController. The value that matches the name placeholder, in this case, John is going to be accessible in the controller with params['name'].

And one last thing to notice about our routes above: we have two routes pointing at the same action. There's nothing wrong with this, Rails will simply use the first route that matches the URL pattern.