04.06.21
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:
- Project Setup
- Relationships (this post)
- Router
- Image Upload via S3
- Send Emails (coming soon!)
- Search Functionality (coming soon!)
- Pagination (coming soon!)