Одновременные ассоциации «has_many» и «has_many :through» с помощью Rails ActiveRecord


Мы можем соединить две таблицы с помощью нескольких ассоциаций одновременно. Например: User has_many: :tasks и User has_many: :tasks, through: :projects. Существует множество потенциальных применений этого метода. Я буду использовать пример приложения для управления задачами, в котором пользователи имеют прямую связь с задачами, которые они создают, и косвенную связь с задачами, находящимися в проектах, которыми с ними поделились другие пользователи.

Решение TL;DR

Чтобы установить множественные ассоциации между двумя таблицами, используйте другое имя для атрибута и используйте свойство source для определения связываемой таблицы. Мы можем использовать has_many, :through для определения косвенной ассоциации и has_many для создания прямой ассоциации.

  has_many :tasks`
  has_many :project_tasks, through: :projects, source: :tasks
Вход в полноэкранный режим Выход из полноэкранного режима

Далее я рекомендую создать метод модели для доступа ко всем задачам пользователя. Помните, что две коллекции могут пересекаться, поэтому мы будем использовать метод uniq для удаления дубликатов:

  def all_qr_codes
    (self.qr_codes + self.shared_qr_codes).uniq
  end
Вход в полноэкранный режим Выйти из полноэкранного режима

Примечание: В своих примерах я использую Rails 7.0.3.1. Другие версии могут не поддерживать этот синтаксис, но принципы остаются в силе.

Объяснение

Это краткий ответ, теперь перейдем к деталям. Давайте создадим отношения между тремя моделями в гипотетическом приложении для управления задачами.

Вот наша конечная цель:

  • Пользователи смогут создавать Задачи и Проекты.
  • Пользователи смогут добавлять задачи в проекты.
  • Пользователи смогут добавлять других пользователей в проекты.Сложная часть:
  • Не каждая задача будет частью проекта, поэтому пользователям нужна прямая связь с задачами.
  • У пользователей не будет прямой ассоциации с задачами, связанными с проектами, которые были разделены другими пользователями. Для этого потребуется косвенная ассоциация has_many :through.

Мы хотим дать Пользователям отношения has_many с Задачами, отношения has_many с Проектами, и отношения has_many :through, связывающие Пользователей с Задачами через Проекты. Однако если вы попытаетесь добавить и то, и другое в модель User, используя имя :tasks для обеих ассоциаций, Rails не сможет обработать миграцию. Вот почему использование другого имени для отношения has_many :through работает.

Далее я проведу вас через создание этой схемы от начала до конца. Эти инструкции предполагают, что вы уже создали проект Rails и имеете базовое понимание Ruby on Rails.

Шаг 1: Создание таблиц и моделей

Мы создадим базовые модели для Пользователей, Проектов, Задач. Я рекомендую использовать генераторы Rails, если вы умеете это делать, но вот как должны выглядеть соответствующие миграции базы данных (с минимальным количеством полей). Мы начнем с миграций для создания таблиц для моделей:

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
Войти в полноэкранный режим Выход из полноэкранного режима

Шаг 2: Многие ко многим: Проекты и пользователи с помощью таблицы Join

Для создания отношения «многие-ко-многим» между пользователями и проектами мы можем использовать таблицу объединения. Вы можете выполнить команду :

rails generate migration CreateProjectsUsersJoinTable projects users

В результате миграции будет создана таблица projects_users. Каждая запись будет хранить 2 внешних ключа, определяющих связь между пользователем и проектом:

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
Войти в полноэкранный режим Выйти из полноэкранного режима

Нам нужно добавить эти строки в модели (обратите внимание на порядок ассоциаций, мы должны подключиться к projects_users, прежде чем подключаться к projects через него):

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
Войти в полноэкранный режим Выйти из полноэкранного режима

Шаг 3: Один-ко-многим: Прямое объединение пользователей и задач

Давайте добавим has_many :tasks в модель User и модель Project. Убедитесь, что вы добавили столбцы t.belongs_to :user и t.belongs_to :project в таблицу Tasks, которая будет хранить внешние ключи. Затем мы можем добавить эти строки в модели:

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
Войти в полноэкранный режим Выйти из полноэкранного режима

Шаг 4: Один-ко-многим: Косвенная ассоциация пользователей и задач через проекты

Наконец, чтобы создать отношение has_many :through между пользователями и задачами через проекты, мы добавим эту строку в модель User:

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
Войти в полноэкранный режим Выйти из полноэкранного режима

И, как я уже говорил, мы можем создать метод модели для запроса всех Задач пользователя. Помните, что эти две коллекции могут пересекаться, поэтому мы воспользуемся методом uniq для удаления дубликатов:

  def all_qr_codes
    (self.qr_codes + self.shared_qr_codes).uniq
  end
Вход в полноэкранный режим Выйти из полноэкранного режима

Что такое прямая ассоциация?

Прямая ассоциация — это когда запись имеет столбец, содержащий идентификатор другой записи, с которой она связана. Обычно это относится к отношениям «один-ко-многим», когда дочерняя модель имеет столбец, содержащий ID родительской модели. Вот пример прямой связи «один-ко-многим» в Rails:

class Author < ApplicationRecord
  has_many :books
end

class Book < ApplicationRecord
  belongs_to :author
end
Перейдите в полноэкранный режим Выход из полноэкранного режима

Что такое косвенная ассоциация?

Косвенные ассоциации используются для определения связи между двумя записями через взаимную запись, с которой они связаны. Это часто используется для создания объединенных таблиц. В следующем примере врачи и пациенты связаны через взаимные записи о приеме. Назначения имеют прямую связь «один-ко-многим» с врачами и пациентами. При использовании «через:» создается косвенная связь «многие-ко-многим» между врачами и пациентами, имеющими общие записи на прием.

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
Вход в полноэкранный режим Выход из полноэкранного режима

Вот и все! Надеюсь, это объяснение помогло вам настроить вашу базу данных Rails и прояснило, как has_many и has_many :through можно использовать для создания прямых и косвенных ассоциаций. Если вы знаете лучшее решение для связи пользователей с задачами через проекты и напрямую, оставьте комментарий и дайте мне знать!

Оцените статью
devanswers.ru
Добавить комментарий