skip to content

In Sinatra, what does erb :receipt render, and how do the layout, locals and instance variables reach that template?

level: juniorimportance: should knowfreq 42%

answer

  1. a views folder beside app_file
  2. Symbol names a file, String is source
  3. layout.erb wraps it through yield
  4. self is the app instance
  5. output is not HTML-escaped

basics

~10 s

erb :receipt renders views/receipt.erb, wrapped in views/layout.erb when that file exists. The template runs with the request's app instance as self, so it sees instance variables and helpers; the locals option adds local variables.

solid answer

~40 s

`erb :receipt` looks for `receipt.erb` in the `views` setting, which defaults to a `views` folder next to `app_file`, and returns the rendered String for the route to return as the body. A Symbol names a template; a String is template source, so `erb 'receipt'` renders the word receipt. If `views/layout.erb` exists it wraps the output at its `<%= yield %>`; a missing default layout is silently skipped, `layout: false` turns it off and `layout: :admin` picks another. The template is evaluated with the app instance as `self`, so `@receipt` and helper methods are visible, while `locals: { title: 'x' }` defines local variables. `<%= %>` output is not HTML-escaped by default, so wrap untrusted text in `escape_html`.

code

ruby · 15 lines
ruby
# app.rb
require 'sinatra'

helpers do
  def money(cents) = format('%.2f', cents / 100.0)
end

get '/receipts/:id' do
  @receipt = { id: params['id'], cents: 1999, note: '<b>thanks</b>' }
  erb :receipt, locals: { title: "Receipt #{params['id']}" }
end

get '/receipts/:id/line' do
  erb :line_item, layout: false     # fragment without views/layout.erb
end

go deeper

for a junior

Recall that erb :name renders views/name.erb, that layout.erb wraps it through yield, and that instance variables from the route are visible in the template.

for a middle

Explain Symbol versus String arguments, how layouts are applied or skipped, locals versus instance variables, and why output needs escape_html.

for a senior

Show the production view: template caching per environment, escaping untrusted data, and keeping partials and layouts predictable as views multiply.

for a principal

Decide whether a service should render HTML at all, or stay a JSON API with templates handled elsewhere, given who maintains the views.

## Rendering methods and where they look Sinatra exposes one method per template engine (`erb`, `haml`, `builder`, `markdown`, `slim` and others), each a thin call to a private `render` method that compiles the template through the **Tilt** gem. For `erb`, Tilt picks an ERB implementation: the Erubi gem or Ruby's own ERB library. `erb :receipt` looks in the **`views`** setting. By default that is `File.join(root, 'views')`, where `root` is the directory of `app_file`, the file that defined the app. So a route in `app.rb` renders `views/receipt.erb`. If the file does not exist, rendering raises `Errno::ENOENT`. The call **returns a String**. Returning it from the route makes it the body; you can also post-process it or pass it to `halt`. ## What the first argument means | Argument | Meaning | |---|---| | `:receipt` | a named template defined with `template :receipt do ... end`, else the file `views/receipt.erb` | | `'receipt'` | template **source**: the output is the literal word `receipt` | | `'<%= Time.now %>'` | template source, compiled and cached like a file | The String form is the classic surprise. `erb 'receipt'` is not a file lookup, and nothing warns you. ## Named templates and a different views folder Small apps sometimes skip files entirely. `template :receipt do '<h1><%= title %></h1>' end` registers a named template in the class, and `layout do '<html><%= yield %></html>' end` does the same for the layout; `erb :receipt` then finds the named template before it looks on disk. To keep files elsewhere, change the setting once, for example `set :views, File.join(__dir__, 'templates')`, or pass `views:` for a single call. Options Sinatra does not recognise are handed on to the template engine. ## Layouts Rendering with a layout happens in this order: 1. Sinatra renders the inner template first. 2. It then renders the layout, by default `:layout`, i.e. `views/layout.erb`, passing the inner output as a block, which the layout emits with `<%= yield %>`. 3. If the **default** layout file is missing, the layout step is silently skipped. 4. `layout: false` skips it on purpose; `layout: :admin` uses `views/admin.erb`, and a layout you name explicitly that does not exist raises `Errno::ENOENT`. A partial rendered from inside a template (`<%= erb :line_item %>`) is not wrapped in the layout again, because Sinatra switches the default layout off while it renders a template. ## What the template can see The template is evaluated with the **request's application instance as `self`**, the same object the route and filters ran on: - instance variables set in filters or the route, such as `@receipt`; - helper methods, and built-ins such as `params`, `request`, `session` and `settings`; - local variables passed with `locals: { title: 'Receipt' }`, or as the third positional argument; - `escape_html`, available because `Sinatra::Base` includes `Rack::Utils`. ```erb <!-- views/layout.erb --> <html><body><%= yield %></body></html> <!-- views/receipt.erb --> <h1><%= title %></h1> <p>Total: <%= money(@receipt[:cents]) %></p> <p><%= escape_html(@receipt[:note]) %></p> ``` `<%= %>` inserts its value as-is, with no HTML escaping, under Sinatra's default ERB setup. Anything that came from a request or a third party goes through `escape_html`. ## Content type and the response Rendering does not touch headers. When a route finishes without setting a content type, Sinatra applies the `default_content_type` setting, `text/html`, which is what an ERB page wants. A route that renders something else says so first, for example `content_type :txt` before `erb :receipt_text`, and the symbol is resolved to a MIME type through Rack's registry. Because `erb` returns a plain String, the same rendered text can also be used as an email body, written to a file, or passed to `halt` with a status of its own, such as `halt 404, erb(:not_found)`. ## Caching The `reload_templates` setting defaults to on in development only. In development Sinatra clears its template cache on every request, so edits show up at once; in other environments each compiled template is cached for the life of the process, and a changed template needs a restart or deploy. ## Common mistakes - Passing a String name and getting the name printed back. - Expecting `locals` to be the only way in, when instance variables already reach the template. - Assuming a missing `layout.erb` raises; only an explicitly named layout does. - Printing untrusted text with `<%= %>` and no `escape_html`. - Editing a template in production and wondering why nothing changed.

  • A route returns erb 'receipt' and the page shows only the word receipt. Why?
    A String argument is template source, not a file name. Sinatra compiles the text 'receipt' as a template, which renders to itself. Use the Symbol `erb :receipt` to load `views/receipt.erb`.
  • Why does a partial rendered from inside a template not get wrapped in the layout a second time?
    While rendering a template Sinatra sets its default layout to false, so any `erb` call made from inside that template renders without a layout unless you pass one explicitly. Only the outermost call from the route gets `views/layout.erb`.
  • A template edit shows up at once in development but not in production. Why?
    `reload_templates` defaults to on only in development, where Sinatra clears its template cache on each request. Elsewhere compiled templates stay cached for the process lifetime, so the change appears after a restart.

A layout is a picture frame: the inner template is the painting, finished first, and <%= yield %> is the opening the frame leaves for it. layout: false hangs the painting without a frame.

saying these in an interview costs you the question

  • erb 'receipt' and erb :receipt both load views/receipt.erb.
  • Instance variables must be passed through locals to reach a template.
  • Sinatra HTML-escapes every <%= %> output by default.
  • Without a views/layout.erb file every erb call raises an error.
  • Templates are re-read from disk on every request in production.