Build a Simple Movie Reviews Website in Rails 6, part 3
A full website, from installation to deployment
By: Ajdin Imsirovic 16 December 2019
In the previous article, we’ve added user authentication and authorization with devise, and we’ve set up the Movies model.
In this article, we’ll add the CRUD functionality - create, read, update, delete - for the Movies model.
Image by CodingExercises
Let’s begin by inspecting our database tables using the Rails console:
rails c
ActiveRecord::Base.connection.tables
The returned output:
=> ["schema_migrations", "ar_internal_metadata", "users", "movies"]
Alright, let’s inspect the columns in the movies table.
Movie.columns.map(&:name)
Here’s the output:
=> ["id","title","description","img","cover","rate","user_id","created_at","updated_at"]
Let’s load all the movies; note: we know this will not return anything:
Movie.all
As expected the above ActiveRecord query will map onto an PostgreSQL query, and execute it, returning empty:
Movie Load (0.5ms) SELECT "movies".* FROM "movies" LIMIT $1 [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
Great, everything in our model seems to work.
Before we can effectively work with the Movie Model, we need to add the movie controller too.
Adding Movie Controller
To add the movie controller, we’ll run this on the command line:
rails g controller Movies index show new create edit update destroy
Similar to how we added a single home
action to the Pages
controller, we’ve added all the needed actions for the Movies controller.
To see the change to our file structure, let’s run git status
:
The newest updates are committed as “Add the Movies controller” commit message.
Let’s also do a git diff --stat
to compare the newest commit with the previous one. To know the actual SHAs that we’ll be comparing, we’ll do the git log --oneline
first, then take the two most recent ones.
git diff 30dd5c9 5ab103a --stat
Here’s a screenshot of the output:
Currently the movies_controller.rb
looks like this:
class MoviesController < ApplicationController
def index
end
def show
end
def new
end
def create
end
def edit
end
def update
end
def destroy
end
end
Let’s update it to this:
before_action :set_movie, only: [:show, :edit, :update, :destroy]
def index
@movies = Movie.all
end
def show
end
def new
@movie = Movie.new
puts @movie
end
def create
@user = current_user
puts @user
@movie = @user.movies.create(params_requre)
redirect_to movies_path
#@movie = @user.movies.new(params_requre)
# if movie.save
end
def edit
end
def update
puts "77777777777777777777"
@movie.update(params_requre)
redirect_to movies_path
end
def destroy
end
def set_movie
@movie = Movie.find(params[:id])
puts @movie
end
protected
def params_requre
params.require(:movie).permit(:title, :description)
end
end
Additionally, looking at the Movie model (defined in the models/movie.rb
file) we can see that it defines the relationship between users and movies:
class Movie < ApplicationRecord
belongs_to :user
end
We also need to define the other side of the relationship, inside the models/user.rb
file, by adding this line just above the closing end
line of the User
class definition.
has_many :movies
Now the entire updated models/user.rb
files looks like this:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :movies
end
Let’s add the changes to movies controller and user model, in a commit titled “Update user model and movies controller”.
Now that we’ve got everything set up, let’s take a quick detour and add a new movie using the rails console.
Why?
Because it’s faster to do it this way than having to add the code to all the views that we’ve added when the movies controller was generated. Currently, all these view files are just empty slots waiting to be filled with actual code. Right now, they’re just static HTML, like, for example, the views/movies/create.html.erb
file:
<h1>Movies#create</h1>
<p>Find me in app/views/movies/create.html.erb</p>
Let’s also see how the generated movies controller affected the contents of the routes.rb file:
Rails.application.routes.draw do
get 'movies/index'
get 'movies/show'
get 'movies/new'
get 'movies/create'
get 'movies/edit'
get 'movies/update'
get 'movies/destroy'
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
We’ll get rid of all these get
requests and instead just do resources, like this:
Rails.application.routes.draw do
# get 'movies/index'
# get 'movies/show'
# get 'movies/new'
# get 'movies/create'
# get 'movies/edit'
# get 'movies/update'
# get 'movies/destroy'
root 'pages#home'
resources :movies
devise_for :users
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
Alright, so let’s take our detour and use the rails console first.
Add a New Movie With Rails Console
To add a movie, we first need to make sure we have a user in the database. Let’s run User.first
and User.second
in the console.
As we can see, we have the first User in the database, but for the second, it returns nil
.
Let’s now see if there are any movies that belong to User.first
:
User.first.movies
Finally, let’s add one.
We’ll need to add all the entries for each of the movies
table columns, except for the first table column, id
.
As a reminder, here’s what gets returned from running Movie.columns.map(&:name)
=> ["id","title","description","img","cover","rate","user_id","created_at","updated_at"]
Ok, so now we’re ready to add a movie.
I’ve split the following code on several lines for easier reading; however, you should type it as a one-liner in the Rails console:
User.first.movies.new(
title:"The Matrix",
description:"Neo Anderson lives in a simulation...",
img:"https://upload.wikimedia.org/wikipedia/en/thumb/c/c1/The_Matrix_Poster.jpg/220px-The_Matrix_Poster.jpg",
cover:"https://upload.wikimedia.org/wikipedia/en/thumb/c/c1/The_Matrix_Poster.jpg/220px-The_Matrix_Poster.jpg").save
Here’s the output in the console:
Let’s now run User.first.movies
again:
User.first.movies
Here’s the output this time:
Now that we have added our first movie using the Rails console, let’s add the views so that our web app visitors can also do it from our web app’s frontend.
Adding the Movie CRUD Views
Let’s start with movies edit. Open app/views/movies/edit.html.erb
and add this code at the bottom:
<%= form_form :movie, url: movie_path, method :patch do |f| %>
Title: <%= f.text_field :title %><br>
Description: <%= f.text_field :description %><br>
<%= f.submit %>
<% end %>
Now, visit this URL: localhost:3000/movies/edit
.
You’ll get an error.
Why is that?
To understand why this is happenning, let’s visit a different URL: localhost:3000/movies/new
.
This time, the actual URL gets served without issues:
The reason is the code in the movies_controller.rb
.
While we do have the code for adding a new movie in the controller, we don’t have a definition for the edit
action:
def new
@movie = Movie.new
puts @movie
end
.
.
def edit
end
Note the the two vertical dots in the code above are added for brevity, and are a stand-in for the actual code.
Obviously, we need to add some code to our edit
action.
Here it is:
def edit
@movie = Movie.find(params[:id])
end
To be continued…