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.

Build a simple movie review website in Rails 6 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. Generating the movie controller from the command line

To see the change to our file structure, let’s run git status: Running git status after generating the movie controller

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: Running git diff --stat after committing movie controller changes

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.

Running User.first and User.second in rails 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

Checking to see if there are any movies that belong to the first user

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: The result of adding a new movie to a user using active record

Let’s now run User.first.movies again:

User.first.movies

Here’s the output this time: Running User first movies again

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 movies new url is served in the browser

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…