С помощью 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
Это вернет список всех исполнителей в списке воспроизведения. Это довольно круто.
Заключение
Вот и все. Теперь мои запросы стали более мощными, и я могу писать методы, которые работают с этой настройкой, чтобы сделать сложные запросы простым делом.
Спасибо за чтение!