Ассоциирование удаленной таблицы с Active Record

С помощью Active Record таблицы базы данных могут быть соединены вместе посредством ассоциации Active Record, что позволяет нам легко работать с нашей базой данных. Рассмотрим приведенную ниже диаграмму:

Создавая различные ассоциации между этими таблицами, Active Record предоставит нашим моделям доступ к дополнительным методам, что значительно упростит работу с нашей базой данных. В итоге я смогу взаимодействовать с моими таблицами следующим образом:

playlist = Playlist.first

p playlist.name # => "Very Best of the 90's"
p playlist.tracks.sample.name # => "Step By Step"
p playlist.albums.sample.name # => "Siamese Dream"
p playlist.artists.sample.name # => "Alanis Morissette"

p playlist.albums.sample
          .tracks.first
          .playlists.sample
          .artists.first.name # => "Fleetwood Mac"
Войти в полноэкранный режим Выйти из полноэкранного режима

Настройка

Чтобы сделать эту статью как можно короче, я не буду рассказывать о том, как настроить Active Record или как создать начальные таблицы базы данных и модели таблиц. Для начала у меня есть схема базы данных, которая выглядит следующим образом:

# db/schema.rb

# ...

  create_table "albums", force: :cascade do |t|
    t.string "name"
    t.datetime "release_data"
    t.string "img_url"
  end

  create_table "artists", force: :cascade do |t|
    t.string "name"
    t.integer "popularity"
    t.string "img_url"
  end

  create_table "playlists", force: :cascade do |t|
    t.string "name"
    t.string "img_url"
  end

  create_table "tracks", force: :cascade do |t|
    t.string "name"
    t.integer "duration_s"
  end
Войти в полноэкранный режим Выход из полноэкранного режима

Настройка ассоциаций

Обратите внимание на диаграмму в начале этой статьи. Таблица «Playlists» и таблица «Artists» находятся совсем рядом друг с другом, но, используя ассоциации Active Record, мы можем получить прямой доступ ко всем исполнителям, представленным в определенном плейлисте, просто вызвав .artists на экземпляре Playlist.

playlist = Playlist.first
artist = Artist.first

p playlist.artists.pluck(:name).uniq
  # =>  ["Post Malone", "Kid Cudi", "Mr. Probz", "Miley Cyrus",
  #       "Fleetwood Mac","The xx", "Elton John", "Wiz Khalifa"]

# It works both ways
p artist.playlists.pluck(:name)
  # => ["Oldies", "Classic Hits"]


Вход в полноэкранный режим Выход из полноэкранного режима

Краткое объяснение для #pluck:

#pluck — очень полезный метод, предоставляемый Active Record. Он возвращает массив значений при вызове коллекции записей. apidock.com говорит следующее о #pluck:

«Используйте #pluck как ярлык для выбора одного или нескольких атрибутов, не загружая кучу записей, чтобы взять нужные вам атрибуты».


Я создал четыре модели (пока что), представляющие таблицы Playlists, Albums, Artists, & Tracks.

Теперь я создам ассоциации таблиц для каждой модели, начиная с Artists. Давайте снова посмотрим на диаграмму нашей базы данных:

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

# app/models/artist.rb
class Artist < ActiveRecord::Base
  has_many :albums # <- plural
end
Войти в полноэкранный режим Выйти из полноэкранного режима

А в модели альбома мы можем описать эту ассоциацию с помощью макроса belongs_to:

# app/models/album.rb
class Album < ActiveRecord::Base
  belongs_to :artist # <- singular
end
Войти в полноэкранный режим Выйти из полноэкранного режима

Примечание — при создании ассоциаций обязательно используйте правильную форму единственного/ множественного числа имени таблицы.

Продолжая, каждый альбом имеет много треков & каждый трек принадлежит одному альбому (в нашем случае). Добавляем к нашей модели альбомов:

# app/models/album.rb
class Album < ActiveRecord::Base
  belongs_to :artist
  has_many :tracks
end
Вход в полноэкранный режим Выйти из полноэкранного режима

И внутри модели треков:

class Track < ActiveRecord::Base
  belongs_to :album
end
Войти в полноэкранный режим Выход из полноэкранного режима

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

#db/migrate/20220402193754_add_foreign_keys_to_albums_and_tracks.rb
class AddForeignKeysToAlbumsAndTracks < ActiveRecord::Migration[6.1]
  def change
    add_column :albums, :artist_id, :integer
    add_column :tracks, :album_id, :integer
  end
end
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь я могу получить все треки исполнителя с помощью вызова:

Track.joins(album: [:artist]).where(:artist => {:name => "Nine Inch Nails"})`
  # returns an array of all tracks by "Nine Inch Nails"
Войти в полноэкранный режим Выйти из полноэкранного режима

Примечание — #joins и #where — это методы поиска, предоставляемые Active Record для запроса базы данных. Подробнее о них вы можете узнать здесь.

Все хорошо, пока что, давайте продолжим.

Наша последняя прямая ассоциация отличается от двух предыдущих. Продолжая рассуждать, каждый плейлист имеет множество треков, но каждый трек также может иметь множество плейлистов. Чтобы представить эту связь, нам нужно создать новую таблицу, которая объединит нашу таблицу треков и таблицу плейлистов (см. схему выше).

Создание объединенной таблицы

В новом файле миграции я помещу следующее:

class CreatePlaylistTracksTable < ActiveRecord::Migration[6.1]
  def change
    create_table :playlist_tracks do |t|
      t.integer :playlist_id
      t.integer :tracks_id
    end
  end
end
Войти в полноэкранный режим Выйти из полноэкранного режима

Это создаст таблицу joins для связи наших таблиц tracks & playlists вместе в отношениях «многие-ко-многим». Далее мне нужно создать новую модель для этой таблицы.

class PlaylistTrack < ActiveRecord::Base
  belongs_to :playlist
  belongs_to :track
end
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь вся моя база данных подключена. Теперь я могу соединять методы в цепочки, чтобы делать такие запросы, как…

artists = Artists.joins(album: [tracks: [:playlists]])
                 .where(:playlist => {:name => "Very Best of the 90's"})
Войти в полноэкранный режим Выйти из полноэкранного режима

который должен дать мне список исполнителей, представленных в плейлисте «Very Best of the 90’s».

Теперь я должен быть в состоянии получить любые данные, которые мне понадобятся, из базы данных. Но приведенный выше запрос довольно уродлив. Я бы предпочел написать что-то вроде этого:

artists = Playlist.find_by(name: "Very Best of the 90's").artists
Войти в полноэкранный режим Выйти из полноэкранного режима

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

Завершение ассоциаций

Чтобы усилить запросы к базе данных, я добавил в свои модели еще несколько ассоциаций:

# app/models/playlist.rb
class Playlist < ActiveRecord::Base
  has_many :playlist_tracks
  has_many :tracks, through: :playlist_tracks
  has_many :albums, through: :tracks
  has_many :artists, through: :albums
end

# app/models/track.rb
class Track < ActiveRecord::Base
  belongs_to :album
  has_one :artist, through: :album
  has_many :playlist_tracks
  has_many :playlists, through: :playlist_tracks
end

# app/models/album.rb
class Album < ActiveRecord::Base
  belongs_to :artist
  has_many :tracks
  has_many :playlists, through: :tracks
end

# app/models/artist.rb
class Artist < ActiveRecord::Base
  has_many :albums
  has_many :tracks, through: :albums
  has_many :playlists, through: :tracks
end
Войти в полноэкранный режим Выход из полноэкранного режима

С этой настройкой я могу делать очень простые запросы, например, такие:

artists = Playlist.find(10).artists.pluck(:name).uniq
Войти в полноэкранный режим Выйти из полноэкранного режима

Это вернет список всех исполнителей в списке воспроизведения. Это довольно круто.

Заключение

Вот и все. Теперь мои запросы стали более мощными, и я могу писать методы, которые работают с этой настройкой, чтобы сделать сложные запросы простым делом.
Спасибо за чтение!

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *