ActiveRecord模型中的复杂关联

时间:2009-04-22 17:00:21

标签: sql ruby-on-rails activerecord

我正在尝试了解ActiveRecord如何处理比简单has_manybelongs_to等更复杂的关联。

例如,考虑一个录制音乐演出的应用程序。每个Gig都有一个Band,它有一个类型。每个Gig还有一个Venue,它有一个Region。

在MS Access的粗略表示法中(我突然开始感到非常怀旧),这些关系会像这样呈现

      1  ∞      1  ∞     ∞  1       ∞  1
Genre ---- Band ---- Gig ---- Venue ---- Region

我希望能够找到所有在一个地区玩过的乐队,或所有主持某种类型的场地。

理想情况下,我的模型会包含此代码

class Genre
  has_many :bands
  has_many :gigs, :through => bands
  has_many :venues, :through => :gigs, :uniq => true
  has_many :regions, :through => :venues, :uniq => true
end

class Band
  belongs_to :genre
  has_many :gigs
  has_many :venues, :through => :gigs, :uniq => true
  has_many :regions, :through => :venues, :uniq => true
end

class Gig
  belongs_to :genre, :through => :band
  belongs_to :band
  belongs_to :venue
  belongs_to :region, :through => :venue
end

VenueRegion

然而,似乎我必须生产这样的东西

class Genre
  has_many :bands
  has_many :gigs, :through => bands
  has_many :venues, :finder_sql => "SELECT DISTINCT venues.* FROM venues " +
    "INNER JOIN gigs ON venue.id = gig.venue_id " +
    "INNER JOIN bands ON band.id = gig.band_id " +
    "WHERE band.genre_id = #{id}"
  # something even yuckier for regions
end

class Band
  belongs_to :genre
  has_many :gigs
  has_many :venues, :through => :gigs, :uniq => true
  # some more sql for regions
end

class Gig
  delegate :genre, :to => :band
  belongs_to :band
  belongs_to :venue
  delegate :region, :to => :venue
end

我有两个问题 - 一个是普通的,一个是特别的。

将军:

我原本以为我试图做的事情会经常出现。我真的有最好的方法,或者有一些我更容易忽视的东西吗?

具体:

我上面的内容实际上并没有起作用!第二种类型模型中的#{id}实际上是返回类的id。 (我认为)。但是,这似乎有效herehere

我意识到这是一个相当史诗般的问题,所以谢谢你,如果你已经走到了这一步。任何帮助将不胜感激!

4 个答案:

答案 0 :(得分:3)

关联设计为可读且可写。他们的很大一部分价值在于你可以做这样的事情:

@band.gigs << Gig.new(:venue => @venue)

听起来,就像你想要一些只读的东西一样。换句话说,你想要关联场地和流派,但你永远不会这样做:

@venue.genres << Genre.new("post-punk")

因为没有意义。如果具有该特定类型的乐队在那里有一个Gig,那么Venue只有一个类型。

关联不起作用,因为它们必须是可写的。这是我如何做只读协会:

class Genre
  has_many :bands 

  def gigs
    Gig.find(:all, :include => 'bands', 
             :conditions => ["band.genre_id = ?", self.id])
  end

  def venues 
    Venue.find(:all, :include => {:gigs => :band}, 
      :conditions => ["band.genre_id = ?", self.id])
  end
end

答案 1 :(得分:1)

您可以为关联添加条件和参数。 最近版本的ActiveRecord提供了 named_scopes 的强大功能,它也适用于相关记录。

来自当前项目

Folder has_many Pages
Page has_many Comments

# In Page
named_scope :commented,
  :include => "comments", 
  :conditions => ["comments.id IS NULL OR comments.id IS NOT NULL"], 
  :order => "comments.created_at DESC, pages.created_at DESC"

使用这个我们可以说:

folder.pages.commented

将使用提供的参数对相关记录进行范围调整。

加! named_scopes是可组合的。

更多范围:

named_scope :published, :conditions => ["forum_topics.status = ?", "published"]

将它们链接在一起:     folder.pages.published.commented

答案 2 :(得分:0)

对于像这样的关联,你最终会编写自定义SQL - 没有真正的方法可以处理这样的关联链而不必进行一些相当大的连接,并且实际上没有内置查询生成器使用单行程序处理它的有效方法。

你也可以查看ActiveRecord的:joins参数 - 这可能就是你想要的。

答案 3 :(得分:0)

听起来像nested_has_many_through的工作!很棒的插件,允许你做嵌套的has_many :throughs