A browser asks for an address, and a server sends back HTML. That's the whole of the web, and it hasn't changed since 1992. This post follows one request through a Rails app: the address you type, the three files Rails reads to answer it, and the page that comes back.

Addresses, host names and paths

The thing in your browser's address bar is a URL (Uniform Resource Locator). Every URL has the same parts, which is the "uniform":

  • the protocol, https
  • the colon-slash-slash, ://
  • the host name, like entr451.com
  • the path, like /tacos

The "locator" part means each address points at one thing, one resource. You'd expect these two addresses to show a page about books and a page about shoes:

https://YourDomainName.com/books
https://YourDomainName.com/shoes

While you build, the protocol is always https, and the host name is the long address your Codespace gives port 3000. So from here on we only talk about the path. When this post says /tacos, it means "add /tacos to the end of the address in that browser tab."

The one route that's already there

Open config/routes.rb in your copy of web-apps. Ignoring the comment, it has one line:

Rails.application.routes.draw do
  get("/data", { :controller => "data", :action => "show" })
end

Read it as a sentence: when someone asks for /data, run the show action of the data controller. That line points at two more files:

  • app/controllers/data_controller.rb, a class with a method named show. The method decides what to show.
  • app/views/data/show.html.erb, the page itself.

Visit /data and you see every table in the database and its rows. That's three files in three folders, and every page in a Rails app is the same three: a route, a controller, a view. Now we'll write a page of our own: a page about tacos, naturally.

Don't fear the error

The route. Add a line under the data line:

Rails.application.routes.draw do
  # get("/things", { :controller => "things", :action => "index" })

  get("/data", { :controller => "data", :action => "show" })
  get("/tacos", { :controller => "tacos", :action => "index" })
end

Save, and visit /tacos. An error:

uninitialized constant TacosController

Error pages in Rails are loud and scary. Don't be afraid of them. They're there to help, and they often say exactly what to do next, if you stop and read them. This one says that Rails read your route, went looking for a tacos controller, and didn't find one. So that's the next file.

The controller. Rails has a command that makes it. In a terminal other than the one running the server, run it with the name from the route:

bin/rails generate controller tacos

It makes two things: the file app/controllers/tacos_controller.rb, and an empty folder, app/views/tacos/, which is where the view will go. You could type the file yourself, but the generator puts the file and the folder exactly where Rails looks for them, named the way it expects. Open the file:

class TacosController < ApplicationController
end

An empty class. Refresh, and Rails says what's missing:

The action 'index' could not be found for TacosController

The route asked for the index action, and an action is a method in the controller. Add it:

class TacosController < ApplicationController
  def index
    render({ :template => "tacos/index" })
  end
end

The class name matches the route's :controller, and the method name matches its :action. The method has one job tonight: show the template tacos/index. Refresh. Another error, and we're still not afraid:

Missing template tacos/index

Rails found the controller, ran index, and went looking for the view. The next file.

The view. Make app/views/tacos/index.html.erb, in the folder the generator made. The folder matters: it's app/views/tacos/, not app/views/. Put some HTML in it:

<h1>Tacos</h1>
<p>Every filling on the menu.</p>
<ul>
  <li>carnitas</li>
  <li>al pastor</li>
  <li>barbacoa</li>
</ul>

Refresh, and there's the page. No more errors.

You didn't have to stop and start the server for any of this. Rails reads your files again on every request, so the next refresh always shows the latest save.

Each of those errors was Rails telling you where it got to. The route sends it to a controller, the controller runs an action, the action sends it to a view, and the error names the first piece that isn't there yet. That's worth remembering the next time a page doesn't work: refresh, read the error, make the piece it names.

Where the rest of the HTML came from

Right-click the tacos page and choose View Page Source. You'll find something like this:

<!DOCTYPE html>
<html>
  <head>
    <title>web-apps</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="csrf-param" content="authenticity_token" />
    <meta name="csrf-token" content="..." />
    <link rel="stylesheet" href="/assets/application-....css" />
  </head>

  <body>
    <h1>Tacos</h1>
    <p>Every filling on the menu.</p>
    <ul>
      <li>carnitas</li>
      <li>al pastor</li>
      <li>barbacoa</li>
    </ul>
  </body>
</html>

Whoa. We only typed what's inside <body>. The rest comes from
app/views/layouts/application.html.erb, the application layout. Every page in an app shares the same outer shell (the DOCTYPE, <html>, <head>), so Rails keeps it in one file and puts each view inside it, where the layout says <%= yield %>.

What's next

That was a long way to get what a plain HTML file gives you. But every piece is now in place: an address, a route, a controller and a view. A view isn't only HTML, though. Its file name ends in .erb, and that's the next post: Two great things, HTML and Ruby in the same file.

Fell behind in class? code-along-complete/1-tacos/ in your copy has all three files, at the same paths as yours.