【问题标题】:Use of HABTM along with has_many :through - Need Help Identifying Where I've Gone Wrong使用 HABTM 和 has_many :through - 需要帮助确定我哪里出错了
【发布时间】:2011-10-21 16:26:36
【问题描述】:

大约一个小时前,我问了一个关于 Rails 协会的问题:
Question on Proper Associations in Rails

这个问题的公认答案让我更深入地思考关系,我想向 SO 社区介绍这种情况。

我之前的问题使用诗人、诗歌和印刷作为模型...对于这个问题,让我们使用音乐行业:

模型是:

  • 艺术家
  • 专辑
  • 歌曲
  • 类型

以下陈述被认为是正确的:

  1. 一张专辑可以有多个艺术家 - 即 Metallica 和 Pantera 发布一张圣诞专辑
  2. 一首歌曲可以属于多张专辑 - 例如,Yellow Submarine 出现在原始专辑以及披头士乐队的多张“Greatest hits”专辑中
  3. 一首歌曲也可以有多个与专辑艺术家不同的“特色”艺术家 - 即 Snoop Dogg 拥有这张专辑,但创作了一首以 Harry Connick Jr. 为主角的歌曲。或者一个更好的例子是当 DJ 发行一张专辑时这些歌曲是由其他艺术家创作的。
  4. 艺术家、专辑和歌曲都可以归类为多种/不同的流派 - 即 Brian Setzer Orchestra 被归类为“Swing”,他们的一张专辑可能是“Swing, Rockabilly”,而该专辑中的一首歌曲可能是“跳蓝调”。

在深入研究这个问题时,我立即看到 Artist & Genre 等模型可以“重用”。我们不想多次保存艺术家的信息——例如,如果我们有一首歌曲,其中既有主要艺术家,也有特色艺术家,两位艺术家的信息都应该存在于 DB 的“艺术家”表中。

此外,在查看“特色”艺术家方面(声明 #2)时,我们似乎有一个附加属性应该在关联中——类似于“特色”标志。

这就是我认为应该如何建立关联——以及我的帖子的顶点......这是对的,我怎样才能让它变得更好?

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

像往常一样,非常感谢那些花时间阅读并提供意见的人!非常感谢!

【问题讨论】:

    标签: ruby-on-rails activerecord associations has-many has-many-through


    【解决方案1】:

    这更像是一个讨论而不是一个问题,但我会尝试解决您的疑虑。我没有测试你的任何代码,所以这些只是我的想法。

    (Artist) has_any_belongs_to_many :genres
    

    您应该为此需要一个单独的表吗?该信息已经通过歌曲和流派之间的关联存储。除非艺术家在没有所述流派的歌曲的情况下属于该流派,否则您不应在另一个 HABTM 协会中复制此信息。

    反之亦然,相反的关联可能是多余的:

    (Genre) has_any_belongs_to_many :artists
    

    至于您对特色方面的设计,似乎将标志功能设置为 true 的功能也是多余的。然而,这是因为命名,所以如果我是你,我会将它重命名为 Release(歌曲可以在多张专辑中发布)。如果您认为特色标志存在于艺术家和歌曲之间,您应该将标志添加到禁止实体(忘记了这个术语)。

    但是,由于它表示为 HABTM 关联,​​因此没有干预模型。因此,您必须将其转换为在 Song 和 Artist 上使用 has_many,中间模型包含belong_to 关联以及特色标志。这实际上是 Release 模型。

    这(同样没有进行测试)将您的模型缩减为:

    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
    

    【讨论】:

    • 我没有编辑权限,但有一个错字:“has_any_belongs_to_many”。感谢您的回复。审查代码。
    • 修正了拼写错误,而且我不希望这段代码开箱即用。它比任何东西都更具理论性 =)
    • 应该 features_artists 和 features_songs 实际上是范围而不是模型上的关联?
    • 如果只是风格问题,我更喜欢使用范围。我不确定是否有任何性能考虑。过去,named_scopes 被宣布死亡,见railway.at/2010/03/09/named-scopes-are-dead
    • Jaryl,后续问题:以 Artist 类为例...为什么在 has_many :albums, :through =&gt; :releases 应该允许访问专辑时保留 has_and_belongs_to_many :albums
    【解决方案2】:

    我不喜欢 HABTM 协会。所以我使用了 has_many 关联。为了解决属于专辑、歌曲等的流派,我使用了多态关联。解决方案相当复杂。但它满足了所有要求:

    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
    

    将歌曲标记为精选:

    Rails 3

    song1.featured_artists << artist2 
    

    Rails 在创建过程中自动设置关联的哈希条件参数。所以没有别的事可做。

    Rails 2.x

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

    【讨论】:

    • 你会如何在一首歌中将一位艺术家标记为“精选”?
    • @KandadaBoggu - 你说“我不是 HABTM 协会的粉丝。” - 我认为这是问题的关键,应该进行更多讨论。请向 OP 解释为什么您认为 HABTM 是不好的做法,同样重要的是为什么您的“相当复杂”的解决方案更好。
    • @Jeremy 解决方案很复杂,不是因为 HABTM。它很复杂,因为要求指出Genre 可以属于Song 或/和AlbumArtist 可以属于Alubm 或/和Song。另一个有趣的方面是Song 可能不包括来自其Alubm 的艺术家。为什么我更喜欢has_many 而不是has_and_belongs_to_many?使用双重has_many 实现HABTM 允许您存储附加属性以及关系(例如:featured 在这种情况下)。在大多数情况下,这种需求最终会蔓延。
    • @KandadaBoggu - 我认为你对 HABTM 的关键问题是 100% 正确的:它只有在记录链接的存在时才有效。一旦需要更多信息,您就会进入 has_many :through 领域,通常如此之快(对我来说,通常是在我的数据模型离开纸笔阶段之前),直接进入那里是有意义的。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-29
    • 1970-01-01
    • 1970-01-01
    • 2020-02-09
    相关资源
    最近更新 更多