Building a Web Application Without Rails: Rendering Views Dynamically using ERB
This is the fourth article in the series on building awesome web applications with Ruby and Rails. We continue our journey to build a web application without using Rails to better understand how Rails works. Today, we'll learn how to render views dynamically with the ERB gem used by Rails.

In the last post, we set up a very basic project structure for our web application which returns a hard-coded string response directly from the Ruby code. In this post, we will separate the view from the application logic, and serve static files using the Rack::Static
middleware. Finally, we'll use the ERB gem to generate dynamic views.
As things stand now, our application mixes the logic and the views together in a single file. This is not a good practice in software development, as it tightly couples them together. You can't change one of them without understanding or affecting the other.
class App
def call(env)
headers = {
'Content-Type' => 'text/html'
}
# This is the view
response = ['<h1>Hello World!</h1>']
[200, headers, response]
end
end
We want to separate the application logic and the views. The benefit is that you can change the view without worrying about the logic, and vice versa.
You might have heard of the separation of concerns principle. This is what it means in the simplest form.
Let's fix this.
Set up Views
We will separate the view from the application logic by moving the response HTML out of the app.rb
and to its own views
directory, in a file named index.html
.
<!-- views/index.html -->
<h1>Hello World!</h1>
Now update the app.rb
to read the contents of this file to build the response. We will use the File#read
method to read the file.
class App
def call(env)
headers = {
'Content-Type' => 'text/html'
}
response_html = File.read 'views/index.html'
[200, headers, [response_html]]
end
end
Refresh the browser to verify that everything is still working.
Make It Pretty!
Have you noticed that our application is very plain-looking? Let's add some style to it.
First, we will standardize the index.html
by adding a proper HTML structure.
<html>
<head>
<title>Application</title>
<link rel="stylesheet" href="/public/style.css">
<meta charset="utf-8">
</head>
<body>
<main>
<h1>Hello World!</h1>
</main>
</body>
</html>
Notice that we are referring to a style.css
file in the public
directory.
Let's add a new public
directory and add a style.css
file in it.
/* /public/style.css */
main {
width: 600px;
margin: 1em auto;
font-family: sans-serif;
}
Now reload the page. But our page looks the same, and none of the styles are getting applied. If we check the response in the DevTools window, you can see it's loading the style.css
file, but the contents are from the index.html
file.

What's going on?
The reason is that we are still serving the contents of the index.html
file when the request for style.css
arrives, as we don't have any logic to differentiate the requests.
class App
def call(env)
headers = {
'Content-Type' => 'text/html'
}
response_html = File.read 'views/index.html'
# same response is sent no matter the request
[200, headers, [response_html]]
end
end
Hence it ignores the actual contents of the style.css
file.
Let's fix it.
Serving Static Files
When a request for style.css
arrives, we want to serve the contents of the style.css
file. For that, we will use the Rack::Static
middleware.
According to the documentation,
TheRack::Static
middleware intercepts requests for static files (javascript files, images, stylesheets, etc) based on the url prefixes or route mappings passed in the options, and serves them using aRack::Files
object. This allows a Rack stack to serve both static and dynamic content.
Let's update the config.ru
file to include the Rack::Static
middleware.
require 'rack'
require_relative './app'
use Rack::Reloader, 0
# Serve all requests beginning with /public from the "public" folder
use Rack::Static, urls: ['/public']
run App.new
Note: To learn more about Rack and the concept of middleware, check out my article: The Definitive Guide to Rack for Rails Developers
Restart the Puma server and reload the page. You can verify in the DevTools that our application sends the correct CSS this time, and our styles are getting applied.

Dynamically Rendering Views
As of now, our view is a static HTML. That means each time the page loads, our application renders the same HTML, no matter what.
We want a dynamic view, i.e., a view which can embed variables in it, to render a different page each time you reload the browser.
For this, we'll need a view template.
What's a view template? It's simply a file with predefined slots which will be filled dynamically.
Here's how we will convert the static view into a view template.
- Change the name of the
index.html
file toindex.html.erb
- Update the
<h1>
tag to use atitle
variable instead of a hard-coded string.
<!-- views/index.html.erb -->
<html>
<head>
<title>Application</title>
<link rel="stylesheet" href="/public/style.css">
<meta charset="utf-8">
</head>
<body>
<main>
<h1>
<%= title %> <!-- Note the % -->
</h1>
</main>
</body>
</html>
The above view template will insert the value of the title
variable to generate the final HTML view that will be shown to the user.
Within an ERB template, Ruby code can be included using both<% %>
and<%= %>
tags. The<% %>
tags are used to execute Ruby code that does not return anything, such as conditions, loops, or blocks, and the<%= %>
tags are used when you want output.
The next question is, how to pass this title
variable to the view from the app.rb
file? For this, we will use the erb gem.
For learning more about the erb
syntax, check out the Rails guides.
This gem allows you to add any Ruby code to a plain text document for dynamically generating a new document. I recommend reading its documentation.
Here's how it works in its essence.
require 'erb'
name = 'Akshay'
age = 30
template = ERB.new 'My name is <%= name %> and I am <%= age %> years old'
puts template.result(binding)
=> "My name is Akshay and I am 30 years old"
TheKernel#binding
method returns aBinding
object that wraps the current context, i.e. variables, methods,self
along with the other information at any given time in the code for a later use.
The above code creates an instance of the ERB
class with a template string. The result
method on this object takes a Binding
object, which we get by calling the binding
method. Finally, it uses the variables defined in that binding object to render the final text.
Let's use the above structure to render the template from the app.rb
file:
require 'erb'
class App
def call(env)
headers = { 'Content-Type' => 'text/html' }
title = 'Ruby on Rails'
template = ERB.new(template_html)
response_html = template.result(binding)
[200, headers, [response_html]]
end
def template_html
File.read 'views/index.html.erb'
end
end
Now reload the browser. If everything worked, you should see our (new) title Ruby on Rails on the page. Our application is generating views on the fly.
Making It Dynamic
Now you might have questions about the dynamic nature of the above code. We are still using the hard-coded string "Ruby with Rails". But the important part is that we are fetching the value from the title
variable. Now that value can come from anywhere, which is the part that makes it dynamic.
To illustrate this, let's tweak our code to read the title from the query string instead of a hard-coded string.
We will add a new method named get_title
which will parse the env
hash to fetch the query string, and return its value.
require 'erb'
class App
def call(env)
headers = { 'Content-Type' => 'text/html' }
title = get_title(env) # new code here
template = ERB.new(template_html)
response_html = template.result(binding)
[200, headers, [response_html]]
end
def get_title(env)
query = env['QUERY_STRING'] # "title=ruby"
values = query.split('=') # ["title", "ruby"]
values[1] # ruby
end
def template_html
File.read 'views/index.html.erb'
end
end
Now append ?title=whatever
to the existing URL and refresh the browser. You should see the browser display whatever text you typed in the URL.

Congratulations, you now have a fully dynamic web application. In the next article, I will introduce the concept of a router and controllers. Stay tuned!
—
I hope you found this article useful and that you learned something new.
If you have any questions or feedback, please send me an email. I look forward to hearing from you.
Please subscribe to my blog below if you'd like to receive future articles directly in your email. If you're already a subscriber, THANK YOU 🙏