2. Routes and templates
A route that returns a page, a layout that wraps it, a partial it reuses, and why
<%=and<%==are different.
Replace app.rb with a notes board. The notes are hardcoded for now; step 3 puts them in a database.
# frozen_string_literal: true
title "Notes"
NOTES = ["Buy milk & bread", "Book the <dentist>", "Water the ferns"].freeze
get "/" do
render :index, notes: NOTES
end
title names the app; app_title reads it back, and the log records it. The route block takes no arguments,
and whatever it returns is the response — a String is HTML with status 200, and render returns a String.
Templates
Templates are .erb files in views/. render :index means views/index.erb.
<%# locals: (notes:) %>
<ul class="notes">
<% notes.each do |note| %>
<%== partial :note, note: note %>
<% end %>
</ul>
<p class="count"><%= notes.size %> open</p>
The first line is not a comment you can skip. Every template declares its locals, and that declaration
becomes the method signature the template is compiled into. It is what lets the build step check every
render call in your app against the template it names — a missing or misspelled local is an error at build
time, with the line in app.rb that got it wrong, rather than a NameError in front of a visitor.
(notes:) is required. (notes:, error: nil) makes error optional with a default.
A template can only see its declared locals plus the top-level helpers (params, session, h,
csrf_field, app_title and the rest). There is no implicit access to instance variables, because there is
no instance.
Partials
views/_note.erb — the underscore marks it as a partial:
<%# locals: (note:) %>
<li><%= note %></li>
Rendered with partial :note, note: note. A partial has its own declared locals and gets nothing it was not
handed.
Layout
views/layout.erb wraps every render unless you pass layout: false:
<%# locals: (content:) %>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><%= app_title %></title>
<link rel="stylesheet" href="/app.css">
</head>
<body>
<h1><%= app_title %></h1>
<%== content %>
</body>
</html>
<%= escapes, <%== does not
This is the one piece of ERB syntax that differs from what you may be used to, and it matters.
<%= value %> | HTML-escapes the value. Use it for everything that came from a person. |
<%== value %> | Inserts it raw. Use it only for HTML you produced — content in the layout, partial output, csrf_field. |
<% ... %> | Runs Ruby, prints nothing. |
Escaping is the default, so the dangerous thing is the one you have to type an extra character for. Load the page and look at the source:
<li>Buy milk & bread</li>
<li>Book the <dentist></li>
Both notes went through <%= note %>, so both are safe text rather than markup.
Routes
get "/x" post "/x" put "/x" patch "/x" delete "/x"
get "/notes/:id" do # params.fetch(:id)
get "/files/*path" do # params.fetch(:path) — the rest of the path
Two more that are worth knowing now:
not_found do "No such page" end
error do |e| "Something broke: #{e.message}" end
Static files need no route at all: anything in public/ is served straight from disk, so public/app.css
answers at /app.css.
Restart and look
melee dev has no reload, so Ctrl-C and start it again:
melee dev: prepared 3 templates, 1 migrations
melee dev: http://127.0.0.1:4567 (Ctrl-C to stop)
GET / -> 200 (0.1 ms)
Next
A database — real notes, in SQLite.