Simultaneous “has_many” and “has_many :through” Associations using Rails ActiveRecord
Published: 2022-08-06 07:24:00

> 
We can connect two tables using multiple associations simultaneously. For example: `User has_many: :tasks` and `User has_many: :tasks, through: :projects`. There are a lot of potential uses for this method. I'll be using an example task management application in which Users have a direct connection to Tasks they create and an indirect association to Tasks that are in Projects that have been shared with them by other Users.

##TL;DR Solution
To establish multiple associations between two tables, use a different name for the attribute and use the `source` property to define the table being connected. We can use `has_many, :through` to define an indirect association and `has_many` to create a direct association.
```
  has_many :tasks`
  has_many :project_tasks, through: :projects, source: :tasks
```
Next, I recommend creating a model method to access all of a User’s Tasks. Keep in mind that the two collections may overlap, so we’ll use the `uniq` method to remove duplicates:
```
  def all_qr_codes
    (self.qr_codes + self.shared_qr_codes).uniq
  end
```
Note: In my examples, I’m using Rails 7.0.3.1. Other versions may not support this syntax, but the principles still apply.


##Explanation
That’s the short answer, now into the details. Let’s create relationships between three models in a hypothetical task management application.

Here’s our end goal:
- Users will be able to create Tasks and Projects.
- Users will be able to add Tasks to Projects.
- Users will be able to add other Users to Projects.
The tricky part:
- Not every task will be part of a project, so Users need a direct association with Tasks.
- Users won’t have a direct association with Tasks that are related to Projects that have been shared by other Users. This will require an indirect `has_many :through` association.

We want to give Users a `has_many` relationship with Tasks, a `has_many` relationship with Projects, and a `has_many :through` relationship connecting Users to Tasks through Projects. However, if you try to add both to the User model using the name `:tasks` for both associations, Rails won’t be able to process the migration. That’s why using a different name for the `has_many :through` relationship works.

Next up, I’ll walk you through creating this schema from start to finish. These instructions assume you’ve already created a Rails project and have a basic understanding of Ruby on Rails.

##Step 1: Creating the Tables and Models
We’ll create basic models for Users, and Projects, Tasks. I recommend using Rails generators if you know how to, but here’s what the corresponding database migrations should look like (with bare minimum fields). We’ll start with the migrations to create tables for the models:
```
class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :username
      t.timestamps
    end
  end
end
 
class CreateTasks < ActiveRecord::Migration[7.0]
  def change
    create_table :tasks do |t|
      t.string :title
      t.belongs_to :user
      t.belongs_to :project, optional: true
      t.timestamps
    end
  end
end
 
class CreateProjects < ActiveRecord::Migration[7.0]
  def change
    create_table :projects do |t|
      t.string :title
      t.timestamps
    end
  end
end
```

##Step 2: Many-to-Many: Projects and Users via Join Table

To create our many-to-many relationship between Users and Projects, we can use a join table. You can run:

```rails generate migration CreateProjectsUsersJoinTable projects users```

The resulting migration creates a table called projects_users. Each record will store 2 foreign keys defining a connection between a user and a project:
```
class CreateProjectsUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :projects_users do |t|
      t.references :user, foreign_key: true
      t.references :project, foreign_key: true
      t.timestamps
    end
  end
end
```
We need to add these lines to the models (pay attention to the order of associations, we have to connect to `projects_users` before we connect to `projects` through it):
```
class User < ActiveRecord::Base
  has_many :projects_users
  has_many :projects, through: :projects_users
end
 
class Project < ActiveRecord::Base
  has_many :projects_users
  has_many :users, through: :projects_users
end
```
##Step 3: One-To-Many: Users and Tasks Direct Association

Let’s add `has_many :tasks` to the User model and the Project model. Be sure you’ve added the `t.belongs_to :user` and `t.belongs_to :project` columns to the Tasks table which will store the foreign keys. Then we can add these lines to the models:
```
class User < ActiveRecord::Base
  has_many :projects_users
  has_many :projects, through: :projects_users
  has_many :tasks
end
 
class Project < ActiveRecord::Base
  has_many :projects_users
  has_many :users, through: :projects_users
  has_many :tasks
end
 
class Task < ActiveRecord::Base
  belongs_to :user
  belongs_to :project
end
```

##Step 4: One-To-Many: Users and Tasks Indirect Association through Projects

Finally, to create the `has_many :through` relationship between Users and Tasks through Projects, we’ll add this line to the User model:
```
class User < ApplicationRecord
  has_many :projects_users
  has_many :projects, through: :projects_users
  has_many :tasks
  has_many :project_tasks, through: :projects, source: :tasks
end
```
And, as I mentioned before, we can create a model method to query all of a User’s Tasks. Keep in mind that the two collections may overlap, so we’ll use the `uniq` method to remove duplicates:
```
  def all_qr_codes
    (self.qr_codes + self.shared_qr_codes).uniq
  end
```

##What is a Direct Association?

A direct association is when a record has a column containing the ID of another record it has a relationship with. This is usually the case for one-to-many relationships where the child model has a column containing the ID of the parent model. Here’s an example of a one-to-many direct relationship in Rails:
```
class Author < ApplicationRecord
  has_many :books
end

class Book < ApplicationRecord
  belongs_to :author
end
```
##What is an Indirect Association?

Indirect associations are used to define a relationship between two records through a mutual record they’re connected to. This is often used to create join tables. In the following example, Physicians and Patients are connected through mutual appointments. Appointments have a one-to-many direct association with Physicians and Patients. By using “through:” a many-to-many indirect relationship is created between Physicians and Patients who share an appointment.
```
class Physician < ApplicationRecord
  has_many :appointments
  has_many :patients, through: :appointments
end

class Appointment < ApplicationRecord
  belongs_to :physician
  belongs_to :patient
end

class Patient < ApplicationRecord
  has_many :appointments
  has_many :physicians, through: :appointments
end
```

And that’s it! Hopefully this explanation has helped you configure your Rails database and clarified how `has_many` and `has_many :through` can be used to create direct and indirect associations. If you know a better solution to connect Users to Tasks through projects and directly, leave a comment and let me know!