Зарегистрируйтесь, чтобы продолжить обучение

Связи Ruby: ActiveRecord (ORM)

Связи

Сущности предметной области не существуют сами по себе. Они часто зависят друг от друга. На уровне базы данных такие связи задаются через внешние ключи или даже промежуточные таблицы, как в случае связи «многие ко многим».

ORM использует эти ключи для работы со связями. Он добавляет множество полезных методов, которые упрощают работу с зависимыми сущностями: выборкой, добавлением, модификацией и удалением.

Представим, что мы создаем блог, и нам понадобится сущность Post. Пользователи связаны с постами «один ко многим»:

  • Один пользователь может быть автором многих постов
  • У одного поста всегда один автор

Структура

Для поддержки такой связи при создании таблицы постов нужно добавить внешний ключ на таблицу пользователей. Далее приведен пример файла миграции, который создает таблицу posts с внешним ключом creator таблицы users. Что такое миграции и как с ними работать, мы разберем в следующих уроках.

Рассмотрим пример:

class CreatePosts < ActiveRecord::Migration[6.0]
  def change
    create_table :posts do |t|
      t.string :state, null: true
      t.string :title
      t.text :body
      # Поле, которое будет внешним ключом
      t.references :creator, null: false, foreign_key: {to_table: :users}
      t.timestamps
    end
  end
end

По умолчанию ActiveRecord не воспринимает внешние ключи как что-то особенное. Она требует явного указания связи на уровне моделей. Для этого в каждой из моделей определяется специальный метод, через который будет происходить взаимодействие между связанными сущностями. Имя этого метода произвольно. Оно выбирается так, чтобы лучше отражать суть связи: у поста есть автор, у каждого автора есть посты.

В следующем примере мы используем методы belongs_to и has_many для установки связей между моделями. При этом используется параметр class_name для указания класса, с которым устанавливается связь. Это особенно полезно в случаях, когда имя ассоциации не совпадает с именем соответствующего класса.

В данном контексте belongs_to и has_many обозначают «принадлежит к» и «имеет много» соответственно.

Так это выглядит на практике:

# post.rb
class Post < ApplicationRecord
  belongs_to :creator, class_name: 'User'
end

В этом примере Post принадлежит User, который обозначен как creator.

# user.rb
class User < ApplicationRecord
  # Во множественном числе, потому что это коллекция
  has_many :posts, foreign_key: 'creator_id'
  # У каждого пользователя много постов
  # has_many определяется у модели, имеющей внешние ключи в других таблицах
end

А здесь каждый User «имеет много» posts, связанных через внешний ключ creator_id.

Метод has_many также поддерживает соглашение для определения имени внешнего ключа. Только здесь оно определяется не по имени метода, а по имени модели, в которой описывается связь. Для модели User это будет user_id. В нашем случае такая логика не работает, поэтому имя свойства указано явно.

CRUD

Теперь ActiveRecord знает о связях и позволяет работать с ними напрямую:

user = User.find(1)
# SELECT "posts".* FROM "posts" WHERE "posts"."creator_id" = 1
user.posts.each do |post|
  puts post
end

Обращение к коллекции зависимых сущностей возвращает специальный объект, который может использоваться как массив.

Есть и другой способ взаимодействовать с зависимостями. Вызов posts как метода позволяет управлять этой коллекцией, например, удалить или добавить новый пост:

# Создаем новый пост и автоматически связываем его с пользователем
post = user.posts.build(title: 'title', body: 'body') # Параметры поста можно передать в build
post.save

# Выводим все посты пользователя
user.posts # => [#<Post id: 1, title: "title", body: "body", creator_id: 1, ...>]

# Находим пост по id
post2 = user.posts.find(1)

post == post2 # => true

# Удаляем все посты пользователя
user.posts.destroy_all

То же самое происходит и с другой стороны связи:

post = Post.find(1)
post.creator.first_name

# Устанавливаем пользователя
post.creator = user
post.save

При работе со связями важно переключиться от мышления через таблицы и ключи к сущностям и связям. Технически это значит, что код опирается на сами сущности, а не их идентификаторы:

# Плохо
post.creator_id = user.id

# Хорошо
post.creator = user

Выборки

Все типы связей в ActiveRecord поддерживают построение запросов на выборку:

# В запрос будет включено условие по creator_id, равным текущему пользователю
# SELECT "posts".* FROM "posts" WHERE "posts"."creator_id" = 1 AND "posts"."state" = 'active'
user.posts.where(state: 'active')

Самостоятельная работа

  1. Повторите шаги из теории.
  2. Создайте еще несколько моделей - Урок, Пользователь, Участник Курса.
  3. Создайте связь многие-ко-многим между пользователем и курсом.

Дополнительные материалы

  1. Связи (ассоциации) Active Record

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff