React & Rails API Part 2: Relationships

We’re diving into Part 2 of our API tutorial series to geek out with you and share the lessons that we have learned working with Rails and React. Click here to start at the beginning.

Database Setup

In Part 1 of this series, we setup React and Rails and established their working relationship. Next we want to start building out models and their relationships to each other. Since we’re building a basic IMDB-style database, we’ll start with the relevant models: Movies, People and Roles.

Before we can start on our data, we need to make sure Rails is using the right database. As I stated earlier, we’ll be using postgresql for our database – so first go to your config/database.yml and update it to match the code below.

default: &default
  adapter: postgresql
  encoding: unicode
  pool: 5
  timeout: 5000

development:
  <<: *default
  database: imdb

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: imdb_test

production:
  <<: *default
  url: <%= ENV['DATABASE_URL'] %>

Install Postgres

Next, we’ll be creating our databases for the project. I’m on a mac, so I’ll be using the Postgres App but if you’re on a Windows machine you can use these resources.

First we’ll create the necessary Models – in your console, run the following commands:

rails g model Movie title:string year:integer
rails g model Person name_first:string name_middle:string name_last:string
rails g model Role title:string

Next we’ll create the Controllers to work with these models:

rails g controller Movies
rails g controller People
rails g controller Roles

Update your movie_controller.rb to match the code below:

class MoviesController < ApplicationController
  before_action :set_movie, only: [:show]

  def index
    render json @movie.order('title')
  end

  def show
    render json: @movie
  end

  private

    def set_movie
      @movie = Movie.find(params[:id])
    end

    def movie_params
      params.require(:movie).permit!
    end

end

Update your people_controller.rb to match the code below.

class PeopleController < ApplicationController
    before_action :set_person, only: [:show]

    def index
      @people = Person.all
      render json: @people.order("NULLIF(name_last, '') NULLS LAST, NULLIF(name_first, '') NULLS LAST")
    end

    def show
      render json: @person
    end

    private

      def set_person
        @person = Person.find(params[:id])
      end

      def person_params
        params.require(:movie).permit!
      end

end

Update your roles_controller.rb to match the code below.

class RolesController < ApplicationController
    before_action :set_role, only: [:show]

    def index
      @roles = Role.all
      render json: @roles.order("title")
    end

    def show
      render json: @role
    end

    private

      def set_role
        @role = Role.find(params[:id])
      end

      def role_params
        params.require(:movie).permit!
      end

end

Relationships

Next we’ll need to setup relationships between models, as well as creating models for a has_many through relationship. First we’ll create the model to connect People and roles by running the following in the command line.

rails g model person_role person_id:integer role_id:integer

Update app/models/role.rb, app/models/person_role.rb and app/models/person.rb to add the has_many through relationship, as well as accepts_nested_attributes_for to allow nested models.

class Role < ApplicationRecord
    has_many :person_roles
    has_many :people, through: :person_roles
    accepts_nested_attributes_for :person_classifications, allow_destroy: true
end
class Person < ApplicationRecord
    has_many :person_roles
    has_many :roles, through: :person_roles
    accepts_nested_attributes_for :person_roles, allow_destroy: true
end
class PersonClassication < ApplicationRecord
    belongs_to :person
    belongs_to :classification
end

Next we’ll create the model to connect People and Movies by running the following in the command line.

rails g model cast person_id:integer movie_id:integer

Update app/models/cast.rb, app/models/movie.rb and app/models/person.rb to add the has_many through relationship, as well as accepts_nested_attributes_for to allow nested models.

class Cast < ApplicationRecord
    belongs_to :person
    belongs_to :movie, optional: true
end
class Production < ApplicationRecord
    has_many :casts
    has_many :people, through: :casts
    accepts_nested_attributes_for :casts, allow_destroy: true
end
class Person < ApplicationRecord
    has_many :person_roles
    has_many :roles, through: :person_roles
    accepts_nested_attributes_for :person_roles, allow_destroy: true

    has_many :casts
    has_many :movies, -> { order('movies.title')}, through: :casts
    accepts_nested_attributes_for :casts, allow_destroy: true
end

Serializers

Lastly, we’ll create and update serializers to have more control over the object return from the API. First we’ll add the active_model_serializers gem to the gemfile.

gem 'active_model_serializers', '~> 0.10.0'

Run in console, run bundle

bundle

Next in the console, we want to create serializers for each model.

rails g serializer cast
rails g serializer person
rails g serializer movie

Update app/serializers/cast_serializer.rb

class CastSerializer < ActiveModel::Serializer
    attributes :id, :role, :person, :movie

    def person
      {
        id: object.person.id,
        name_first: object.person.name_first,
        name_middle: object.person.name_middle,
        name_last: object.person.name_last,
      }
    end

    def movie
      {
        id: object.movie.id,
        title: object.movie.title,
      }
    end
end

Update app/serializers/person_serializer.rb

class PersonSerializer < ActiveModel::Serializer
attributes :id, :name_first, :name_middle, :name_last
has_many :casts
end

Update app/serializers/movie_serializer.rb

class MovieSerializer < ActiveModel::Serializer
    attributes :id, :title
    has_many :casts
end

In Part 3, we’ll go over Routing for our project so we can begin building out additional views for our application.

React & Rails API series:

  1. Project Setup
  2. Relationships (this post)
  3. Router
  4. Image Upload via S3
  5. Send Emails (coming soon!)
  6. Search Functionality (coming soon!)
  7. Pagination (coming soon!)