3.3. Creating HTML Dynamically with erb

The the content of an erb file will propably seem familiar to you. It is a mixture of HTML and Ruby code (erb stands for embedded Ruby). erb pages are rendered as Views. This is the first time for us to get in touch with the MVC model.[7] We need a controller to use a view. That can be created it via the generator rails generate controller. Let's have a look at the onboard help of this generator:
$ rails generate controller
Usage:
  rails generate controller NAME [action action] [options]

Options:
      [--skip-namespace]        # Skip namespace (affects only isolated applications)
  -e, [--template-engine=NAME]  # Template engine to be invoked
                                # Default: erb
  -t, [--test-framework=NAME]   # Test framework to be invoked
                                # Default: test_unit
      [--helper]                # Indicates when to generate helper
                                # Default: true
      [--assets]                # Indicates when to generate assets
                                # Default: true

Runtime options:
  -f, [--force]    # Overwrite files that already exist
  -p, [--pretend]  # Run but do not make any changes
  -q, [--quiet]    # Suppress status output
  -s, [--skip]     # Skip files that already exist

Description:
    Stubs out a new controller and its views. Pass the controller name, either
    CamelCased or under_scored, and a list of views as arguments.

    To create a controller within a module, specify the controller name as a
    path like 'parent_module/controller_name'.

    This generates a controller class in app/controllers and invokes helper,
    template engine, assets, and test framework generators.

Example:
    `rails generate controller CreditCards open debit credit close`

    CreditCards controller with URLs like /credit_cards/debit.
        Controller: app/controllers/credit_cards_controller.rb
        Test:       test/controllers/credit_cards_controller_test.rb
        Views:      app/views/credit_cards/debit.html.erb [...]
        Helper:     app/helpers/credit_cards_helper.rb
$
Nice! We are kindly provided with an example further down:
rails generate controller CreditCard open debit credit close
Doesn't really fit the bill for our case but I am feeling brave and suggest that we simply try rails generate controller Example test
$ rails generate controller Example test
      create  app/controllers/example_controller.rb
       route  get "example/test"
      invoke  erb
      create    app/views/example
      create    app/views/example/test.html.erb
      invoke  test_unit
      create    test/controllers/example_controller_test.rb
      invoke  helper
      create    app/helpers/example_helper.rb
      invoke    test_unit
      create      test/helpers/example_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/example.js.coffee
      invoke    scss
      create      app/assets/stylesheets/example.css.scss
$
Phew... that's a lot of stuff being created. Amongst others, the file app/views/example/test.html.erb. Let's have a closer look at it:
$ cat app/views/example/test.html.erb 
<h1>Example#test</h1>
<p>Find me in app/views/example/test.html.erb</p>
$
It's HTML, but for it to be a valid HTML page, something is missing at the top and bottom (the missing HTML "rest" will be explained in the section called “Layouts”). We launch the web server to test it
$ rails server
and have a look at the web page in the browser at the URL http://0.0.0.0:3000/example/test:
test.html.erb web page
In the log log/development.log we find the following lines:
Started GET "/example/test" for 127.0.0.1 at 2013-07-15 14:32:07 +0200
Processing by ExampleController#test as HTML
  Rendered example/test.html.erb within layouts/application (0.8ms)
Completed 200 OK in 768ms (Views: 751.0ms | ActiveRecord: 0.0ms)

Started GET "/assets/example.css?body=1" for 127.0.0.1 at 2013-07-15 14:32:08 +0200
Started GET "/assets/example.js?body=1" for 127.0.0.1 at 2013-07-15 14:32:08 +0200
Started GET "/assets/application.css?body=1" for 127.0.0.1 at 2013-07-15 14:32:08 +0200
Started GET "/assets/jquery_ujs.js?body=1" for 127.0.0.1 at 2013-07-15 14:32:08 +0200
Started GET "/assets/jquery.js?body=1" for 127.0.0.1 at 2013-07-15 14:32:08 +0200
Started GET "/assets/application.js?body=1" for 127.0.0.1 at 2013-07-15 14:32:08 +0200
Started GET "/assets/turbolinks.js?body=1" for 127.0.0.1 at 2013-07-15 14:32:08 +0200
That almost reads like normal English. Let us analyse the first part:
Started GET "/example/test" for 127.0.0.1 at 2013-07-15 14:32:07 +0200
Processing by ExampleController#test as HTML
  Rendered example/test.html.erb within layouts/application (0.8ms)
localhost (127.0.0.1) sent in an HTTP GET request for the URI /example/test. That was then apparently rendered as HTML by the controller ExampleController using the method test.

Buy the new Rails 5.1 version of this book.

The other lines tell us that a bunch of CSS and JavaScript files are compiled and than delivered. In production mode these would be precompiled and delivered as one CSS and one J
Now we just need to find the controller. Good thing you bough this book. ;-) All controllers are in the directory app/controllers, and there you go, we indeed find the corresponding file app/controllers/example_controller.rb.
$ ls -l app/controllers/
total 16
-rw-r--r--  1 xyz  staff  204 15 Jul 13:56 application_controller.rb
drwxr-xr-x  3 xyz  staff  102 15 Jul 13:56 concerns
-rw-r--r--  1 xyz  staff   69 15 Jul 14:31 example_controller.rb
$
Please open the file app/controllers/example_controller.rb with your favorite editor:
class ExampleController < ApplicationController
  def test
  end
end
That is very clear. The controller ExampleController is a descendant of the ApplicationController and contains currently just one method with the name test. This method contains currently no program logic.
You will probably ask yourself how Rails knows that for the URL path /example/test it should process the controller ExampleController and the method test. This is not determined by some magical logic, but by a routing configuration. The current routings can be listed with the command rake routes
$ rake routes
      Prefix Verb URI Pattern             Controller#Action
example_test GET /example/test(.:format) example#test
$ 
These routes are configured in the file config/routes.rb which has been auto-filled by the controller generator with a route to example/test. The one line which is important for us right now is the second one:
$ head -n 2 config/routes.rb 
Testproject::Application.routes.draw do
  get "example/test"
$ 
The config/routes.rb file includes a lot of examples. Give it a read when you have time. We'll dive into that later (Chapter 6, Routes).

Buy the new Rails 5.1 version of this book.

A static file in the directory public always has higher priority than a route in the config/routes.rb! So if we were to save a static file public/example/test that file will be delivered.

Programming in an erb File

Erb pages can contain Ruby code. You can use it to program and give these page dynamic content.
Let's start with something very simple: adding 1 and 1. First we try out the code in irb:
$ irb --simple-prompt
>> 1 + 1
=> 2
>> exit
$
That was easy. We fill the erb file app/views/example/test.html.erb as follows:
<h1>First experiment with erb</h1>
<p>Addition:
<%= 1 + 1 %>
</p>
Then use rails server to launch the web server.
$ rails server
Visit that page with the URL http://0.0.0.0:3000/example/test
Simple addition

Buy the new Rails 5.1 version of this book.

If you want to output the result of Ruby code, enclose the code within a <%= ... %>.
You may ask yourself: how can the result of adding two Fixnums be displayed as a String? Let's first look up in irb if it really is a Fixnum:
$ irb --simple-prompt
>> 1.class
=> Fixnum
>> (1 + 1).class
=> Fixnum 
Yes, both the number 1 and the result of 1 + 1 is a Fixnum. What happened? Rails is so intelligent that it automatically calls all objects in a view (that is the file test.html.erb) that are not already a string via the method .to_s, which always converts the content of the object to a string (the section called “Method to_s for Your Own Classes”). Once more, a brief trip to irb:
>> (1 + 1).to_s
=> "2"
>> (1 + 1).to_s.class
=> String
>> exit
$
You are now going to learn the finer points of erb step by step. Don't worry, it's neither magic nor rocket science.

<% ... %> vs. <%= ... %>

In a .html.erb file, there are two kinds of Ruby code instructions in addition to the HTML elements:
  • <% … %>
    Executes the Ruby code it contains, but does not output anything (unless you explicitly use something like print or puts).
  • <%= … %>
    Executes the Ruby code it contains and outputs the result as a String. If is is not a String the methode to_s will be called.

Buy the new Rails 5.1 version of this book.

The output of <%= ... %> is automatically escaped. So you don't need to worry about "dangerous" HTML.
Let's use an example, to make sure it all makes sense. We use each to iterate through the Range (0..5). Edit the app/views/example/test.html.erb as follows:
<p>Loop from 0 to 5:
<% (0..5).each do |i| %>
<%= "#{i}, " %>
<% end %>
</p>
Open this view In the browser:
Simple addition
Let's now have a look at the HTML source code in the browser:
<!DOCTYPE html>
<html>
<head>
  <title>Testproject</title>
  <link data-turbolinks-track="true" href="/assets/application.css?body=1" media="all" rel="stylesheet" />
<link data-turbolinks-track="true" href="/assets/example.css?body=1" media="all" rel="stylesheet" />
  <script data-turbolinks-track="true" src="/assets/jquery.js?body=1"></script>
<script data-turbolinks-track="true" src="/assets/jquery_ujs.js?body=1"></script>
<script data-turbolinks-track="true" src="/assets/turbolinks.js?body=1"></script>
<script data-turbolinks-track="true" src="/assets/example.js?body=1"></script>
<script data-turbolinks-track="true" src="/assets/application.js?body=1"></script>
  <meta content="authenticity_token" name="csrf-param" />
<meta content="FjIAd8nQGbKSAdbXdY47d3dhW1ZNLhe4eWJYYOgVdes=" name="csrf-token" />
</head>
<body>

<p>Loop from 0 to 5:
  0, 
  1, 
  2, 
  3, 
  4, 
  5, 
</p>


</body>
</html>
Now you have the important tools to use Ruby code in a view.

Q & A

Potentially, there are two open questions:

Q:

I don't understand anything. I can't cope with the Ruby code. Could you please explain that again?

A:

Is it possible that you have not completely worked your way through Chapter 2, Ruby Basics? Please do take your time with it and have another thorough look. Otherwise, the rest won't make any sense here.

Q:

I can understand the Ruby code and the HTML output. But I don't get why some HTML code was rendered around it if I didn't even write that HTML code. Where does it come from, and can I influence it?

A:

Excellent question! We will get to that next (see the section called “Layouts”).

Layouts

The erb file in the directory app/views/example/ only forms the core of the later HTML page. By default, an automatically generated app/views/layouts/application.html.erb is always rendered around it. Let's have a closer look at it:
<!DOCTYPE html>
<html>
<head>
  <title>Testproject</title>
  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>
The interesting bit is the line
<%= yield %>
With <%= yield %> the View file is included here. The lines with the stylesheets, the JavaScript and the csrf_meta_tags can stay as they are for now. They integrate default CSS and JavaScript files. We'll have a look into that in Chapter 12, Asset Pipeline. No need to bother with that right now.
The file app/views/layouts/application.html.erb enables you to determine the basic layout for the entire Rails application. If you want to enter a <hr> for each page and above it a text, then you can do this between the <%= yield %> and the <body> tag:
<!DOCTYPE html>
<html>
<head>
  <title>Testproject</title>
  <%= stylesheet_link_tag    "application", :media => "all" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>

<h1>My Header</h1>
<hr>

<%= yield %>

</body>
</html>
You can also create other layouts in the directory app/views/layouts/ and apply these layouts depending on the relevant situation. But let's leave it for now. The important thing is that you understand the basic concept.

Passing Instance Variables from a Controller to a View

One of the cardinal sins in the MVC model[8]is to put too much program logic into the view. That's more or less what used to be done frequently in PHP programming in the past. I'm guilty of having done it myself. But one of the aims of MVC is that any HTML designer can create a view without having to worry about the programming. Yeah, yeah, … if only it was always that easy. But let's just play it through in our minds: if I have a value in the controller that I want to display in the view, then I need a mechanism for this. This is referred to as instance variable and always starts with a @. If you are not 100 % sure any more which variable has which scope, then please have another quick look at the section called “Scope of Variables”.
In the following example, we insert an instance variable for the current time in the controller and then insert it in the view. So we are taking programming intelligence from the view to the controller.
The controller file app/controllers/example_controller.rb looks like this:
class ExampleController < ApplicationController
  def test
    @current_time = Time.now
  end
end
In the view file app/views/example/test.html.erb we can then access this instance variable:
<p>
The current time is 
<%= @current_time %>
</p>
With the controller and the view, we now have a clear separation of programming logic and presentation logic. So now we can automatically adjust the time in the controller in accordance with the user's time zone, without the designer of the page having to worry about it. As always, the method to_s is automatically applied in the view.
I am well aware that no-one will now jump up from their chair and shout: Thank you for enlightening me! From now on, I will only program neatly in accordance with MVC. The above example is just the first small step in the right direction and shows how we can easily get values from the controller to the view with instance variables.

Partials

Even with small web projects, there are often elements that appear repeatedly, for example a footer on the page with contact info or a menu. Rails gives us the option of encapsulate this HTML code in form of partials and then integrating it within a view. A partial is also stored in the directory app/views/example/. But the file name must start with an underscore (_).
As an example, we now add a mini footer to our page in a separate partial. Copy the following content into the new file app/views/example/_footer.html.erb:
<hr>
<p>
Copyright 2009 - <%= Date.today.year %> the Easter Bunny.
</p>

Buy the new Rails 5.1 version of this book.

Yes, this is not the MVC way of doing it. Date.today.year should be defined in the Controller. I'm glad that you caught this mistake. I made it to show the use of a partial.
We edit the file app/views/example/test.html.erb as follows and insert the partial via the commandrender:
<p>Loop from 0 to 5:
<% (0..5).each do |i| %>
<%= "#{i}, " %>
<% end %>
</p>

<%= render "footer" %>
So now we have the following files in the directory app/views/example:
$ ls app/views/example
_footer.html.erb
test.html.erb
$ 
The new web page now looks like this:
Page with footer as partial

Buy the new Rails 5.1 version of this book.

The name of a partial in the code is always specified without the preceding underscore (_) and without the file extension .erb and .html. But the actual file must have the underscore at the beginning of the file name and end with the file extension .erb and .html.
Partials can also be integrated from other areas of the subdirectory app/views. For example, you can create a directory app/views/shared for recurring and shared content and create a file _footer.html.erb in this directory. You would then integrate this file into the erb code via the line:
<%= render "shared/footer" %>

Passing Variables to a Partial

Partials are great in the sense of the DRY (Don't Repeat Yourself) concept. But what makes them really useful is the option of passing variables. Let's stick with the copyright example. If we want to pass the start year as value, we can integrate this by adding the following in the file app/views/example/_footer.html.erb:
<hr>
<p>
Copyright <%= start_year %> - <%= Date.today.year %> the Easter Bunny.
</p>
So let's change the file app/views/example/test.html.erb as follows:
<p>Loop from 0 to 5:
<% (0..5).each do |i| %>
<%= "#{i}, " %>
<% end %>
</p>

<%= render "footer", :start_year => '2000' %>
If we now go to the URL http://0.0.0.0:3000/example/test, we see the 2000:
Partial with local start_year
Sometimes you need a partial that partially uses a local variable and somewhere else you may need the same partial, but without the local variable. We can take care of this in the partial itself with an if statement:
<hr>
<p>
Copyright 
<%= "#{start_year} - " if defined? start_year %>
<%= Date.today.year %> 
the Easter Bunny.
</p>

Buy the new Rails 5.1 version of this book.

defined? can be used to check if an expression has been defined.
You can call this partial with <%= render 'footer', :start_year => '2000' %> and with <%= render 'footer' %>.

Alternative Notation

In the section called “Passing Variables to a Partial” we only use the short form for rendering partials. Often, you will also see this long version:
<%= render :partial => "footer", :locals => { :start_year => '2000' } %>

Further Documentation on Partials

We have really only barely scratched the surface here. Partials are very powerful tools. You can find the official Ruby on Rails documentation on partials at http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials.

Thank you for your support and the visibility by linking to this website on Twitter and Facebook. That helps a lot!