Build a Simple Movie Reviews Website in Rails 6, part 2

A full website, from installation to deployment

By: Ajdin Imsirovic 15 December 2019

In the previous article, we’ve installed all the pre-requisites to start building our site.

Let’s get right into it!

Build a simple movie review website in Rails 6 Image by CodingExercises

First, let’s commit the new Rails app

To commit the newly-made Rails app, let’s run the following commands:

git add --all
git status
git commit -m "Initialize a new Rails project"
git log --oneline

Now that we are sure that git is tracking our app, we can run our first migration.

Running our First Migration

To run our first migration, let’s open the console and type:

rails db:migrate

This will add a new file to our app; the file is called schema.rb, and it’s located in the db folder.

We can also run the git status command to verify a new file has been added.

Generate the schema file when you run the rails db migrate command

Now we’ll commit the changes:

git add --all
git commit -m "Run the first 'rails db:migrate' command"

Let’s verify that our app still works by visiting http://localhost:3000. Of course, for this to work, the rails s command has to already be running in a console on our machine.

Rails is stil running on our local machine

Now we can also push the app to remote, i.e to GitHub.

Pushing the local app to GitHub

Let’s make a new repository in GitHub.

Make a new repository on GitHub

Now we’ll push the existing local repository. Push the existing local repository to GitHub

As the screenshot shows, we’ll run these two commands from the command line:

git remote add origin https://github.com/ImsirovicAjdin/movieSiteRails6V2.git
git push -u origin master

Here’s the result of running these two commands in the console: The result of pushing the existing local repository to GitHub

Now, back on GitHub, you can click on the repository name to see the commits that have been pushed. List of commits are now showing on GitHub

Next we’ll install a front-end framework.

This time, it’s gonna be Bulma.

Install Bulma Framework in Rails 6

To do this, we’ll press the keyboard shortcut of CTRL + p in VS Code.

This shortcut will open the file launcher; now we’ll click the name of the file we’re looking for.

This time, we want to locate the file called Gemfile.

Once we’ve located and opened the Gemfile, let’s add Bulma gems:

gem 'bulma-rails', '~> 0.7.4'
gem 'bulma-extensions-rails', '~> 1.0.30'

Now that we’ve added the gems, we’ll need to use them in our application.css file, inside the app/assets/stylesheets folder.

@import 'bulma';
@import 'bulma-extensions';

Save all the changes, and inside the console, run:

bundle

This is the result of running the above command. The result of running the bundle command

Next, we’ll be adding jQuery.

Add jQuery to Rails 6

Since we’re using Yarn, adding jQuery is easy:

yarn add jquery

In the image below, you can see the output of the command:

The result of running the yarn add jquery command

Now we can try running a simple jQuery command inside browser devtools with the localhost:3000 homepage open.

$('body').hide();

This will throw an error: Trying to run jQuery results in an error

Alternatively, we can run the git diff command, to see all the changes that have taken place. The result of running git diff

Obviously, jQuery should be available in our app.

If we inspect our app’s Gemfile, we can find the jQuery dependency has been added. We also saw that earlier, yarn add command reported a successful addition of jQuery. So why doesn’t it work?

The reason is: we need to add it to webpacker, by expanding the environment.js file with additional code.

We need to locate the file called environment.js, inside the config/webpack folder.

The quickest way to do this is to run the file launcher in VS Code using the above mentioned keyboard combo of CTRL + p.

Once we’ve located the file, we need to add:

const webpack = require('webpack')
environment.plugins.append('Provide', 
    new webpack.ProvidePlugin({
        $: 'jquery/src/jquery',
        jQuery: 'jquery/src/jquery'
    })
)

Why did we do this? We added this code to make jQuery globally available in our app, in each of its files.

This is a good time to commit our changes: Commit the addition of Bulma and jQuery

Next, we’ll add our first static page in Rails.

Add a Static Page in Rails 6

To add a static page, we’ll run a generate command, in its short version, which is just g.

rails g controller Pages home

Pages is the controller name, and home is the Pages controller’s action.

It’s possible you’ll see a “FATAL” error at this point:

FATAL: Listen error: unable to monitor directories for changes.
Visit https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers for info on how to fix this.

The fix is to run this command:

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

Now our rails g command should run just fine, and generate the following output in console:

create  app/controllers/pages_controller.rb
route  get 'pages/home'
invoke  erb
create    app/views/pages
create    app/views/pages/home.html.erb
invoke  test_unit
create    test/controllers/pages_controller_test.rb
invoke  helper
create    app/helpers/pages_helper.rb
invoke    test_unit
invoke  assets
invoke    scss
create      app/assets/stylesheets/pages.scss

Let’s also inspect the changes with git, by running git status or git diff --stat. Running the rails g command and inspecting the changes with git

As shown in the image above, running the git status command shows both the existing, modified files, as well as the newly added files.

Running git diff --stat just shows one-liners for each modified file that was already tracked by git, but it doesn’t show new files.

Regardless of how we inspect the changes, here’s the list of files that the rails g command produced:

app/assets/stylesheets/pages.scss
app/controllers/pages_controller.rb
app/helpers/pages_helper.rb
app/views/pages/
test/controllers/pages_controller_test.rb

We can now look at the contents of the above-referenced app/views/pages/ folder, inside VS Code, by running the file launcher.

Expectedly, home.html.erb is the only file that gets filtered as we type app/views/pages inside the launcher.

Let’s inspect it:

<h1>Pages#home</h1>
<p>Find me in app/views/pages/home.html.erb</p>

If we navigate to localhost:3000/pages/home, this will be the output of the above code: Visiting the pages/home route in localhost

Next, let’s update the above page with some Bulma code.

This code is just to test-drive Bulma. It can be anything really. It’s just some temporary code.

So let’s copy a Bulma card example.

Here’s the code:

<div class="card">
  <div class="card-image">
    <figure class="image is-4by3">
      <img src="https://bulma.io/images/placeholders/1280x960.png" alt="Placeholder image">
    </figure>
  </div>
  <div class="card-content">
    <div class="media">
      <div class="media-left">
        <figure class="image is-48x48">
          <img src="https://bulma.io/images/placeholders/96x96.png" alt="Placeholder image">
        </figure>
      </div>
      <div class="media-content">
        <p class="title is-4">John Smith</p>
        <p class="subtitle is-6">@johnsmith</p>
      </div>
    </div>

    <div class="content">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit.
      Phasellus nec iaculis mauris. <a>@bulmaio</a>.
      <a href="#">#css</a> <a href="#">#responsive</a>
      <br>
      <time datetime="2016-1-1">11:09 PM - 1 Jan 2016</time>
    </div>
  </div>
</div>

And here’s the result of copy-pasting the above code and refreshing localhost:3000/pages/home. Bulma styles not loading

As we can see, our Bulma styles are obviously not loading.

If we inspect the application.css file in Chrome, we’ll see the following: Inspecting application.css in Chrome devtools

Sidenote: Notice the bunch of additional letters and numbers in the application.css file’s name? These are there for cache-busting.

Importing Bulma into application.html.erb

Why doesn’t our current import work?

Because of this error: Expecting mime type text/sass but getting mime type text/css

This is the first version of this error: The stylesheet was not loaded because its MIME type, “text/sass”, is not “text/css”.

An alternative version of this error is: “Resource interpreted as Stylesheet but transferred with MIME type text/sass:”.

So how do we fix this?

Actually, the fix is very easy. We’ll just rename the application.css file into application.scss file.

Why does this work?

The explanation can be found in the beginning lines of the multi-line comment inside the existing application.css file itself:

/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.

The heavy lifting is done by webpack in the background.

This is a good time to save our changes.

First, let’s make sure we’ve saved all changes in VS Code.

Next, let’s see our git status. Then we’ll add all our changes and commit them as: “Rename application.css to application.scss and add a card to pages/home”.

Running git status, git add, and git commit after code update

Next, we’ll add the navbar to our code.

Add Main Nav to our Application

The navbar will appear on every page of our application, thus we’ll need to add it as a partial view file, and then we’ll import that partial into application.html.erb file.

Let’s add a new folder to hold the partial files. In app/views, run:

mkdir partials

Thus, we have a new folder called app/views/partials.

Next, let’s add a new partial file. Partial files, by convention, begin with an underscore, so:

touch _nav.html.erb

Now let’s copy-paste the navbar from Bulma, and remove the redundant code.

Here’s what we end up with:

<nav class="navbar" role="navigation" aria-label="main navigation">
  <div class="navbar-brand">
    <a class="navbar-item" href="https://bulma.io">
      <img src="https://bulma.io/images/bulma-logo.png" width="112" height="28">
    </a>

    <a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
    </a>
  </div><!-- /navbar-brand -->

  <div id="navbarBasicExample" class="navbar-menu">

    <div class="navbar-start">

      <!-- <a class="navbar-item">
        Home
      </a> -->

    </div><!-- /navbar-start -->

    <div class="navbar-end">
      <div class="navbar-item">
        <div class="buttons">
          <a class="button is-primary">
            <strong>Sign up</strong>
          </a>
          <a class="button is-light">
            Log in
          </a>
        </div><!-- /buttons -->
      </div><!-- /navbar-item -->
    </div><!-- /navbar-end -->

  </div><!-- /navbar-menu -->

</nav>

Finally, we’ll need to add call the render function to call the _nav.html.erb file from the partials folder.

We’ll make this call to render function from inside the application.html.erb file, as follows:

<%= render "partials/nav" %>

Note: Rails is smart enough to understand what file we’re referring to.

Now that we’ve added the navbar, on narrow screens, it will look like this: Our site's navbar on narrow screens

As we can see, on small widths, our navbar’s links are hidden behind a toggleable button (commonly known as the “burger” button).

However, clicking on the button toggle will not work, because Bulma is a CSS-only framework, and for the toggle to work, we need to add some jQuery.

Before we do, let’s commit our changes: Committing navbar partial file changes

Making the toggle button work

To make the toggle button work, navigate to navbar-menu section of the navbar page on Bulma docs.

At the bottom of the JavaScript toggle card, you’ll find the jQuery code to add:

$(document).ready(function() {

  // Check for click events on the navbar burger icon
  $(".navbar-burger").click(function() {

      // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
      $(".navbar-burger").toggleClass("is-active");
      $(".navbar-menu").toggleClass("is-active");

  });
});

Now we’ll add the above code into _nav.html.erb, at the very bottom, inside a pair of opening and closing script tags:

<script>
$(document).ready(function() {

  // Check for click events on the navbar burger icon
  $(".navbar-burger").click(function() {

      // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
      $(".navbar-burger").toggleClass("is-active");
      $(".navbar-menu").toggleClass("is-active");

  });
});
</script>

If we now click the toggle button, this is the result: Collapsible menu toggled on

Let’s commit our changes again: Make nav toggle toggleable

Next, we’ll add authentication with the devise gem.

Adding the Devise Gem

To have our users log in and out of our application, we’ll use the devise gem.

We’ll add it to our app like this:

gem install devise

Here’s the bash output:

Successfully installed devise-4.7.1
1 gem installed

Successfully installed devise in Rails 6

Now we’ll add it to our Gemfile, and specify the version:

gem 'devise' '=4.7.1'

Now we’ll install it using bundler:

bundle

Run bundle install on devise gem in Rails 6

Next we’ll run rails g devise:install. This will install the devise initializer. Run rails g devise install in Rails 6

It will also print a lot of instructions to the console, which can be abbreviated to:

  1. Reminder to define default url options in the environment files
  2. Reminder to define root_url (which we haven’t yet done)
  3. Reminder to add flash messages in application.html.erb layout file
  4. Reminder to copy Devise views so that we can customize them

To use Devise, we’ll need to have a User, so let’s add a User model.

rails g devise User

Let’s also run git status to see the changes made. Looking at changes after running rails g devise User in Rails 6

Now we’ll migrate the database:

rails db:migrate

Let’s do git status again. Looking at changes after running rails db migrate in Rails 6

If we run git add --all, then git status, we’ll see the list of all the staged files, ready to commit. View all the files ready to commit

Now let’s run git commit:

git commit -m "Add the devise User model and run rails db:migrate"

Next, we can see the last five commits with git log --oneline -5.

Here’s the output: The output of running git log --oneline -5

Now we can see the commits’ SHA hashes (random letters and numbers before the commit message). These hashes are 40 characters long, but for our convenience, they’re shortened to only 8 characters.

Thus, to see the changes on files between any commit, we can do, for example, this:

git diff <previous-commit> <current-commit> <filename>

In practice, we’re interested in db/migrate/20191217144111_devise_create_users.rb, so:

git diff e462ad2 9be3ce3 db/migrate/20191217144111_devise_create_users.rb

Here’s the output: Inspecting the output of running the git diff command

As we can see, the db migration file gets a sort of a timestamp that will identify it inside the db/migrate folder.

The code in the migration file shows the changes to the database that need to be performed in order to create the User model.

Thus, database migrations are syncing the changes made to our Rails application’s code.

Now let’s add the flash messages.

Adding the Flash Messages

Flash messages are simply notifications in our app.

We’ll paste them inside application.html.erb so that they show up everywhere.

<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>

Here’s the complete application.html.erb file:

<!DOCTYPE html>
<html>
  <head>
    <title>MovieSiteRails6V2</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= render "partials/nav" %>

    <p class="notice"><%= notice %></p>
    <p class="alert"><%= alert %></p>

    <%= yield %>
  </body>
</html>

Let’s commit this update with message: “Add flash messages”.

Generate Devise Views

Now we’ll generate a whole bunch of views with the help of the devise gem.

rails g devise:views

Here’s the output: Inspecting the output of running the git diff command

That’s a whole bunch of new files right there: Inspecting the devise-generated view files

Earlier in the console, when we first added devise, the fourth step listed in the instructions included adding the following line:

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

So let’s add this line to config/environments/development.rb, just above the closing end.

Here’s a screenshot of the updated file: Setting up action mailer config on localhost

Finally, we need to add the root route in routes.rb.

Adding Root Route

We’ll point the root route to pages#home, inside config/routes.rb, like this:

Rails.application.routes.draw do
  root 'pages#home'
  
  devise_for :users
  #get 'pages/home'
  
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

Important: Notice we’ve “moved” the ‘pages#home’ from the bottom to right under the first line.

If we now visit localhost:3000, we’ll be taken to the ‘pages#home’ view. That’s our site’s root now.

Another important note: If for any reason you get an error when you visit the localhost:3000 url, simply stop and restart your rails server, and you should be fine.

Having a Look at Users

Let’s now visit the users/sign_up page. Inspecting users sign_up page

What we could do now is fix the styling for this site, using Bulma framework.

However, I’ll leave that up to you, and focus on the backend side of things.

For now, let’s take it one step at a time and click the login link at the bottom of the sign up form.

Here’s the users/sign_in page. Visiting users sign_in page

Next, we’ll look at our database using Rails console.

Inspecting the Database with Rails Console

Let’s start rails console inside VS code.

rails c

Here’s what it looks like: Running Rails console in VS Code

We can type all kinds of commands here. For example:

User

This will be the output of running the above code:

=> User (call 'User.connection' to establish a connection)

Great, it works and it also suggests what we should do! So let’s try it:

User.connection

The Rails console is gonna throw a whole bunch of gibberish. Obviously, it’s an error because we don’t have a user yet.

Let’s make sure that that’s what’s really happening here:

User.first

This will return:

User Load (0.5ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> nil

Great, the nil that got returned from the above SQL query shows that there’s no records available.

But, how did we get this?

We got this using ActiveRecord, a Rails ORM (Object-Relational Mapper).

Let’s see what else we can do with it.

ActiveRecord::Base.connection.tables

You can read up on it in this Stackoverflow question.

Anyway, this is what running the above command will produce in our case:

=> ["schema_migrations", "ar_internal_metadata", "users"]

To see the contents of the users table, you can run this:

ActiveRecord::Base.connection.columns('users').map(&:name)

The returned output is:

=> [
    "id",
    "email",
    "encrypted_password",
    "reset_password_token",
    "reset_password_sent_at",
    "remember_created_at",
    "created_at",
    "updated_at"
   ]

Note: the formatting above is mine; the console will return it on a single line and wrap it if it goes to the end of the screen.

An easier alternative that still works is this:

ModelName.columns.map(&:name)

So we can run this:

User.columns.map(&:name)

The output is exactly the same as with the previous command:

=> [
    "id",
    "email",
    "encrypted_password",
    "reset_password_token",
    "reset_password_sent_at",
    "remember_created_at",
    "created_at",
    "updated_at"
   ]

Let’s now add a new user, by visiting users/sign_up, and filling in the data.

Once we’ve entered the details and submitted the form, this is what we’ll get: Successful sign up

Let’s now try looking at the list of users.

First we’ll run rails c to start the Rails console, then we’ll run:

User.first

This will now return our one record:

#<User id: 1, email: "ajdin@codingexercises.com", created_at: "2019-12-17 17:51:25", updated_at: "2019-12-17 17:51:25">

Here’s the screenshot of the full output: Running the User.first command in Rails console

Now that we’re logged in, we need to find a way to get logged out.

Luckily, we can run the rails routes command.

Running the Rails Routes Command

For starters, let’s just run the command without any options:

rails routes

This will give us a large output of all the routes in our web app.

It spans a couple of screens; below’s the screenshot of the entire output, zoomed out. The result of running rails routes

That’s sure a lot of routes, but we can filter them with the grep command.

So let’s try it:

rails routes | grep users

Now we’ll get filtered output: Filtering rails routes command with grep

The output always follows the same pattern:

  1. Prefix (such as root, or new_user_session, etc)
  2. Verb (such as GET, POST, PATCH, etc)
  3. URI Pattern (such as users/sign_in(.:format))
  4. Controller#Action (such as pages#home)

Now that we understand the output of the rails routes command, we can locate the one that we need to look at.

We’re interested in user logging out or signing out, so let’s try this:

rails routes | grep sign

We can even see the sign_out route, so let’s filter it even further:

rails routes | grep sign_out

Filtering rails routes with grep sign_out

Now we can match each of the four parts of the output, using the above mentioned pattern:

  1. Prefix: destroy_user_session
  2. Verb: DELETE
  3. URI Pattern: users/sign_out(.:format)
  4. Controller#Action: devise/sessions#destroy

Now we can add the destroy_user_session path to the log out button, which we’ll add next.

Add the Log Out Button

Let’s go to _nav.html.erb and add the log out button:

<a class="button is-light">
  Log out
</a>

Next. we’ll turn this static button into a dynamic one, by replacing the above code with the following:

<%= link_to "Log out", 
    destroy_user_session_path, 
    method: :delete, 
    class: "button is-light" 
%>

Logging out of the app with our new log out button

This newest update is committed with this message: “Add the log out button”.

Now we need to conditionally display the log in, sign up, and the log out buttons, based on whether the user is already logged in or not.

Conditionally Showing Navbar Buttons

Before we can conditionally show navbar buttons, we need to updated the remaining sign up and log in buttons to their dynamic versions.

Thus, let’s update the navbar-end section of the _nav.html.erb file to this:

    <div class="navbar-end">
      <div class="navbar-item">
        <div class="buttons">
          <%= link_to "Sign Up",
              new_user_registration_path, 
              class: "button is-primary" 
          %>
          <%= link_to "Login", 
              new_user_session_path, 
              class: "button is-light" 
          %>          
          <%= link_to "Log out", 
              destroy_user_session_path, 
              method: :delete, 
              class: "button is-light" 
          %>
        </div><!-- /buttons -->
      </div><!-- /navbar-item -->
    </div><!-- /navbar-end -->

  </div><!-- /navbar-menu -->

While we’re at it, we might as well save it with a new commit message: “Make navbar buttons dynamic”.

We’re now ready to add the buttons conditionally, with the help of this opening if statement:

<% if (!user_signed_in?) %>

We’ll follow it up with an else:

<% else %>

…and finally we’ll close the if-end statement like this:

<% end %>

Here are the updates made to the navbar-end section of the _nav.html.erb file:

<div class="navbar-end">
  <div class="navbar-item">
    <div class="buttons">
      <% if (!user_signed_in?) %>
        <%= link_to "Sign Up",
            new_user_registration_path, 
            class: "button is-primary" 
        %>
        <%= link_to "Login", 
            new_user_session_path, 
            class: "button is-light" 
        %>
      <% else %>
        <%= link_to "Log out", 
            destroy_user_session_path, 
            method: :delete, 
            class: "button is-light" 
        %>
      <% end %>
    </div><!-- /buttons -->
  </div><!-- /navbar-item -->
</div><!-- /navbar-end -->

This commit is titled: “Display nav buttons conditionally based on whether user is logged in or not”.

An alternative approach to conditionally showing buttons based on user status could be this:

<% unless current_user.blank? %>
signed in
<% else %>
not signed in
<% end %>

Next, we’ll generate the base controller and the Movie model.

Generate the base controller

To generate the base controller, we’ll run:

rails g controller base

Here’s the output: The result of running rails g controller base

Running the git status will produce the following output:

app/assets/stylesheets/base.scss
app/controllers/base_controller.rb
app/helpers/base_helper.rb
test/controllers/base_controller_test.rb

If we open the base_controller.rb file, we’ll see this:

class BaseController < ApplicationController
end

Now we’ll add a line of code that looks like this:

before_action :authenticate_user!

This line of code will ensure that whatever action is called, we’ll make sure to authenticate the user first.

You can read more about it on official devise documentation.

Now let’s commit this change with this message: “Add base controller”.

Next, we’ll add the Movie model, and after that, the movie controller.

Once we add the movie controller, it will inherit from the BaseController we just added; this will ensure that whatever stuff is done inside the movie controller, the user needs to be logged in to access it.

However, first things first: let’s start by adding the Movie model.

Add the Movie Model

To add the Movie model, we’ll run the rails generate command again:

rails g model Movie title:string description:string img:string cover:string rate:float user:references

Here’s the output of running the above generate command:

invoke  active_record
create    db/migrate/20191217211500_create_movies.rb
create    app/models/movie.rb
invoke    test_unit
create      test/models/movie_test.rb
create      test/fixtures/movies.yml

Now let’s inspect it with git status:

On branch master
Your branch is up to date with 'origin/master'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        app/models/movie.rb
        db/migrate/20191217211500_create_movies.rb
        test/fixtures/movies.yml
        test/models/movie_test.rb

nothing added to commit but untracked files present (use "git add" to track)

We can add and commit our changes, with this message: “Generate Movies model”.

Now we can run the migration:

rails db:migrate

Here’s the output:

== 20191217211500 CreateMovies: migrating =====================================
-- create_table(:movies)
   -> 0.1877s
== 20191217211500 CreateMovies: migrated (0.1878s) ============================

Let’s also inspect it with git diff: Running git diff after rails db migrate

We’ll save this migration in commit titled: “Migrate the movies table”.

With this, we can wrap up the second part of our tutorial.

In part 3, we’ll see how to do the CRUD functionality for our simple movies site in Rails.

Feel free to check out my work here: