Building a Many-to-Many Relationship in Rails

Rachel Emmer
4 min readOct 11, 2020
https://rubyonrails.org/

Ruby on Rails is a very useful web application framework for establishing complicated associations between models. Forming these relationships makes common operations simpler and doesn’t require a great deal of complex code. Before attempting a many-to-many relationship in Rails, it’s important to familiarize yourself with Ruby, ActiveRecord, and APIs. Feel free to check out my other blog post, How to Create a Simple API with Ruby on Rails for more information.

The first step in creating a many-to-many relationship in Rails is to create both parent tables. As an example, I am going to be making a dogs_and_owners application, where dogs can have many owners and owners can have many dogs. The join table in this case is simply dog_owners. Dog_owners will not have any unique attributes, and is simply being used to establish the relationship between dogs and owners. For simplicity, dogs and owners will each have one attribute, “name”.

To do this, I will begin by running rails g resource dog name followed by rails g resource owner name into my terminal. This is creating a migration, a model, a controller, and opens up all the routes for the dog and owner class. By writing “name” I am also giving each class the attribute of name. Normally you must specify the data type, however string is the default so in this case it is not necessary.

Next I will make our many-to-many connection by creating the join table. This table belongs_to/references both parent tables. To do this, I will run rails g resource dog-owner dog:references owner:references in my terminal. This is telling the database that this model belongs to the two parent models and creates a foreign key column for dog_id and owner_id. In some cases, you may want to give attributes to the join table, but it is not necessary. For example, if you had a many-to-many relationship between customers and banks with the join being an account, you could give the join table attributes like account number or date opened.

Before I run rails db:migrate, I will check all my migration files and make sure they all look okay. I should receive a message in my console that each table was successfully migrated.

class CreateDogs < ActiveRecord::Migration[6.0]
def change
create_table :dogs do |t|
t.string :name
t.timestamps
end
end
end
class CreateOwners < ActiveRecord::Migration[6.0]
def change
create_table :owners do |t|
t.string :name
t.timestamps
end
end
end
class CreateDogOwners < ActiveRecord::Migration[6.0]
def change
create_table :dog_owners do |t|
t.references :dog, null: false, foreign_key: true
t.references :owner, null: false, foreign_key: true
t.timestamps
end
end
end

Now it’s time to establish the relationships in the models. By giving the join table t.references, it automatically defined the relationship in dog_owner. Here is the code for the dog model and the owner model (to be placed in dog.rb and owner.rb, respectively).

class Dog < ApplicationRecord
has_many :dog_owners
has_many :owners, through: :dog_owners
end
class Owner < ApplicationRecord
has_many :dog_owners
has_many :dogs, through: :dog_owners
end

To ensure that the database is set up correctly, I am going to create some seeds and test them in the rails console. Once I am done creating the seeds I will run rails db:seed. If I don’t receive an error message, it means they were seeded successfully. I will then open up the rails console (by running rails c) and run tests such as Dog.all, Dog.first.owners, Owner.last.dogs, Owner.all etc.

Next, I will check out my config/routes file. This is the first point of contact for the HTTP requests. As I mentioned earlier, running rails g resource automatically opens up all of the routes. Since I am going to be testing all of my controller actions, I am going to leave them open with resources. In the future, I may want to limit access to these actions.

Rails.application.routes.draw do
resources :dog_owners
resources :owners
resources :dogs
end

Now I will build out my controller actions for my dogs and my owners. Sometimes you may want to build controller actions for your join model, but in this case I don’t need to. Here is the code I used for my dog controller. I will then do the same thing for my owner controller.

class DogsController < ApplicationControllerdef index
@dogs = Dog.all
render json: @dogs, include: [:dog_owners, :owners]
end
def show
@dog = Dog.find(params[:id])
render json: @dog, include: [:dog_owners, :owners]
end
def create
@dog = Dog.create(name: params[:name])
render json: @dog
end
def update
@dog = Dog.find(params[:id])
@dog.update(name: params[:name])
render json: @dog
end
def destroy
@dog = Dog.find(params[:id])
@dog.destroy
render json: "Successfully deleted."
end
end

Once I have implemented all of the code, I will spin up my server and test my get and post requests with Postman. If I don’t run into any errors, I have successfully built a Rails application with a many-to-many relationship. Hooray!

--

--