Использование HABTM вместе с has_many: through - нужна помощь, чтобы определить, где я ошибся - PullRequest
2 голосов
/ 07 августа 2011

Примерно час назад я задал вопрос об ассоциациях рельсов:
Вопрос о правильных ассоциациях в рельсах

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

Мой предыдущий вопрос использовал поэта, поэму и печать в качестве моделей ... Для этого давайте используем музыкальную индустрию:

Модели:

  • Исполнитель
  • Альбом
  • Песня
  • Жанр

Следующие утверждения следует считать верными:

  1. Альбом может иметь несколько исполнителей - например, Metallica и Pantera выпускают рождественский альбом
  2. Песня может принадлежать нескольким альбомам - т.е. Yellow Submarine есть в оригинальном альбоме, а также в нескольких альбомах "Greatest hit" из The Beatles
  3. Отдельная песня также может иметь несколько «избранных» исполнителей, которые отличаются от исполнителя альбома - т.е. Снуп Догг владеет альбомом, но исполняет песню с участием Гарри Конника-младшего. Или еще лучший пример - когда диджей выпускает альбом, где ВСЕ песни других исполнителей.
  4. Исполнитель, Альбом и Песня могут быть классифицированы по нескольким / разным жанрам - то есть оркестр Брайана Сетцера относится к категории "Swing", один из их альбомов может быть "Swing, Rockabilly", а отдельная песня в этом альбоме может быть "Jump Blues".

При копании в этой проблеме я сразу же вижу, что такие модели, как Artist & Genre, можно «повторно использовать». Мы НЕ хотели бы сохранять информацию об исполнителе более одного раза - поэтому, например, в случае, если у нас есть песня с главным исполнителем и исполнителем, информация ОБА артистов должна находиться в таблице DB «Исполнитель».

Кроме того, при рассмотрении аспекта «Featured Artist» (утверждение № 2) кажется, что у нас есть дополнительный атрибут, который должен быть в ассоциации - что-то вроде флага «Featured».

Вот как я думаю, что ассоциации должны быть установлены, а также вершина моего поста .... это правильно, и как я могу сделать это лучше?

class Artist < ActiveRecord::Base
  has_and_belongs_to_many :albums
  has_and_belongs_to_many :genres
  has_many :featurings
  has_many :features, :through => :featurings, :conditions => "featured = true"
end

class Album < ActiveRecord::Base
  has_and_belongs_to_many :artists
  has_and_belongs_to_many :songs
  has_many :featurings
  has_many :featured_artists, :through => :featurings, :conditions => "featured = true"
end

class Song < ActiveRecord::Base
  has_and_belongs_to_many :genres
  has_many :artists
  has_many :featurings
  has_many :featured_artists, :through => :featurings, :conditions => "featured = true"
end

class Genre < ActiveRecord::Base
  has_and_belongs_to_many :artists
  has_and_belongs_to_many :songs
end

class Featurings < ActiveRecord::Base
  # the db table for this class should have a "featured" boolean.
  belongs_to :artist
  belongs_to :album
  belongs_to :song
end

Как обычно, огромное спасибо тем, кто нашел время, чтобы прочитать и внести свой вклад! это очень ценится!

Ответы [ 2 ]

5 голосов
/ 09 августа 2011

Это больше обсуждение, чем вопрос, но я попытаюсь ответить на ваши вопросы.Я не проверяю ваш код, так что это только мои мысли.

(Artist) has_any_belongs_to_many :genres

Нужна ли вам отдельная таблица для этого?Информация уже хранится благодаря связи между песнями и жанрами.Если артисты не принадлежат к жанрам, несмотря на отсутствие песен в указанных жанрах, вы не должны воспроизводить эту информацию в другой ассоциации HABTM.

И наоборот, противоположная ассоциация может быть избыточной:

(Genre) has_any_belongs_to_many :artists

Что касается вашего дизайна аспекта показа, кажется, что наличие функции с установленным флагом true также является излишним.Однако это из-за именования, поэтому на вашем месте я бы переименовал его в Release (песни могут быть выпущены на нескольких альбомах).Если вы считаете, что выбранный флаг существует между исполнителем и песней, вы должны добавить флаг к сущности призыва (забыл термин для этого).

Однако, поскольку он выражается как ассоциация HABTM,нет промежуточной модели.Таким образом, вам придется преобразовать это, чтобы использовать has_many как для Song, так и для Artist, с промежуточной моделью, содержащей ассоциации own_to, а также признак признака.Это на самом деле модель выпуска.

Это (опять же тестирование не проводилось) сокращает ваши модели до этого:

class Artist < ActiveRecord::Base
  has_and_belongs_to_many :albums
  has_many :releases
  has_many :songs, :through => :releases
  has_many :albums, :through => :releases
  has_many :featured_songs, through => :releases, :conditions => "featured = true"
end

class Album < ActiveRecord::Base
  has_and_belongs_to_many :artists
  has_many :releases
  has_many :songs, :through => :releases
  has_many :artists, :through => :releases
end

class Release < ActiveRecord::Base
  belongs_to :artist
  belongs_to :song
  belongs_to :album
  # there should be a featured boolean
end

class Song < ActiveRecord::Base
  has_and_belongs_to_many :genres
  has_many :releases
  has_many :artists, :through => :releases
  has_many :albums, :through => :releases
  has_many :featured_artists, through => :releases, :conditions => "featured = true"
end

class Genre < ActiveRecord::Base
  has_and_belongs_to_many :songs
end
2 голосов
/ 10 августа 2011

Я не фанат ассоциаций HABTM. Поэтому я использовал ассоциацию has_many. Для обозначения жанра, относящегося к Album, Song и т. Д., Я использовал полиморфные ассоциации Решение довольно сложное. Но он отвечает всем требованиям:

class Artist

  has_many :genre_links, :as => :genre_holder
  has_many :genres, :through => :genre_links

  has_many :artist_links

  has_many :albums, :through => :artist_links, 
                    :source => :artist_holder, :source_type => "Album"

  has_many :songs, :through => :artist_links, 
                    :source => :artist_holder, :source_type => "Song"


  has_many :featured_songs, :through => :artist_links, 
                    :source => :artist_holder, :source_type => "Song",
                    :conditions => {:featured => true}

end

Используйте параметр :source_type для создания ассоциаций для альбомов и песен.

class Genre
  has_many :genre_links
  has_many :albums, :through => :genre_links, 
                    :source => :genre_holder, :source_type => "Album"

  has_many :songs, :through => :genre_links, 
                    :source => :genre_holder, :source_type => "Song"

end

class GenreLink
  belongs_to :genre_holder, :polymorphic => true
  belongs_to :genre
end

class ArtistLink
  # featured
  belongs_to :artist
  belongs_to :artist_holder, :polymorphic => true 
end

Нам нужен собственный SQL, чтобы получить избранные песни для альбома.

class Album < ActiveRecord::Base

  has_many :genre_links, :as => :genre_holder
  has_many :genres, :through => :genre_links

  has_many :artist_links, :as => :artist_holder, 
             :condition => {:featured => false}
  has_many :artists, :through => :artist_links


  has_many :album_songs 
  has_many :songs, :through => :album_songs

  has_many :featured_artists, :class => "Artist", :custom_sql => '
    SELECT A.* FROM artists A WHERE A.id IN (
      SELECT DISTINCT B.artist_id FROM artist_links B 
      WHERE B.artist_holder_type = "Song" AND B.featured = 1 AND
            B.artist_holder_id IN (#{song_ids.join(",")}))' 

end

Теперь к остальным классам:

class AlbumSong
  belongs_to :album
  belongs_to :song
end

class Song < ActiveRecord::Base
  has_many :genre_links, :as => :genre_holder
  has_many :genres, :through => :genre_links

  has_many :album_songs 
  has_many :albums, :through => :album_songs

  has_many :artist_links, :as => :artist_holder, 
             :condition => {:featured => :false}
  has_many :artists, :through => :artist_links

  has_many :featured_artist_links, :class => "ArtistLink", 
             :as => :artist_holder, :condition => {:featured => :true }
  has_many :featured_artists, :through => :featured_artist_links, 
             :source => :artist

end 

Исполнитель может быть связан с альбомом и / или песней.

album1.artists << artist1
song1.artists << artist1

Пометка песни как избранной:

Рельсы 3

song1.featured_artists << artist2 

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

Рельсы 2.x

song1.featured_artist_links.create(:featured => true, :artist => artist2)
...