Lesson Monday

In the last lesson, we created our first view. However, we can't actually render a list of albums without adding albums in the first place. We'll do that now.

We'll start by adding a new route to app.rb:

app.rb
...
get('/albums/new') do
  erb(:new_album)
end

Our /albums/new route just needs to navigate to a view with a form. That's why the route routes to an ERB file and doesn't do anything else. Let's add a new_album.erb to views:

views/new_album.erb
<h1>Add a new album</h1>

<form action="/albums" method="post">
  <div class="form-group">
    <label for="album_name">Album name</label>
    <input id="n" name="album_name" class="form-control" type="text">
  </div>
  <button type="submit" class="btn btn-success">Create!</button>
</form>

This form has many similarities to forms we created in Introduction to Programming. We have <form> tags with <label> and <input> tags. We also have some basic Bootstrap styling with the "form-group" and "form-control" classes.

The <input> tag creates a field for user input and it has four attributes: name, id, class and type. Sinatra identifies our input fields based on the name attribute, not the id attribute. This is very important. While the id and name attributes will often be named the same in our applications, they are different here to emphasize that Sinatra will grab values based on the name attribute. We’ll cover that more in a moment.

When a user clicks the submit button, how does Sinatra know what to do? Sinatra will look at the form's action and method and then route the application to the corresponding block in app.rb.

Based on the form above, the form's action is /albums and the method is post, which means Sinatra will look in app.rb for post("/albums"). If that block doesn’t exist, we’ll get a "Sinatra doesn't recognize this ditty" error. This is a common point of confusion for beginners. Also note that routes should always begin with a /, but sometimes beginners forget to add the / to the form action attribute. That will lead to an error as well, one that can be tough for tired eyes to catch.

We don't have a post("/albums") route in app.rb yet so let's add that now:

app.rb
...
post('/albums') do
  binding.pry
end

Let's start the server and add a new album called "Blue". It's time to poke around with pry in the terminal:

[1] pry(#<Sinatra::Application>)> params
=> {"album_name"=>"Blue"}
[2] pry(#<Sinatra::Application>)> params[:album_name]
=> "Blue"
[3] pry(#<Sinatra::Application>)> params.fetch("album_name")
=> "Blue"
[4] pry(#<Sinatra::Application>)> params[:name]
=> nil

Every route has a params hash. We can look at it by typing in params. As we can see, the album_name field from our form is showing up. It's a key-value pair with the key being the name attribute from the form field and the value being the user input. params acts like any other Ruby hash. There are multiple ways to access values including bracket notation and the Hash#fetch method. If we try to fetch a value based on a non-existent key, we'll get nil. This is another important gotcha — if a key from params isn't properly typed in, it will lead to errors.

Now that we have a name for an album, we just need to apply some Album logic to the route:

app.rb
post('/albums') do
  name = params[:album_name]
  album = Album.new(name, nil)
  album.save()
end

That's it. Now we can add new albums and then navigate to the root route and see them populate. However, this route just ends up with a blank page. We have to physically navigate to the root route in order to see the list of albums. That's because we haven't called the erb method to render a view. So which view should this route render? We currently have two views and either would be fine — we could render the list of all albums or we could route back to the form so it's easy to add more albums. We'll go ahead and route back to /albums.

app.rb
post('/albums') do
  name = params[:album_name]
  album = Album.new(name, nil)
  album.save()
  erb(:albums)
end

Unfortunately, we get an error:

Sinatra shows an error that reads: "NoMethodError at /albums undefined method any? for nil:NilClass."

This is because our albums view expects the route to have an instance variable named @albums. We need to define @albums in our route:

app.rb
post('/albums') do
  name = params[:album_name]
  album = Album.new(name, nil)
  album.save()
  @albums = Album.all() # Adding this line will fix the error.
  erb(:albums)
end

Now we can both view and add new albums. Note that when we refresh the page, we won't lose our list of albums. That's because the server is still running. When we restart the server, all the albums will be cleared.

Creating a Form


views/new_album.erb
<h1>Add a new album</h1>

<form action="/albums" method="post">
  <div class="form-group">
    <label for="album_name">Album name</label>
    <input id="n" name="album_name" class="form-control" type="text">
  </div>
  <button type="submit" class="btn btn-success">Create!</button>
</form>

Sinatra identifies our input fields based on the name attribute, not the id attribute.

The form action and method corresponds to the route in Sinatra. In the form above:

<form action="/albums" method="post">

Corresponds to:

post "/albums"

Lesson 19 of 37
Last updated August 7, 2022