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.
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.
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'
.
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.