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')

Рекомендуемые программы

Дальше

Завершено

0 / 6

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845