Сложные ассоциации в моделях ActiveRecord - PullRequest
1 голос
/ 22 апреля 2009

Я пытаюсь понять, как ActiveRecord работает с ассоциациями, которые являются более сложными, чем простые has_many, belongs_to и т. Д.

В качестве примера рассмотрим приложение для записи музыкальных концертов. У каждого концерта есть группа, которая имеет жанр. У каждого концерта также есть Место проведения, в котором есть Регион.

В грубой нотации 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

и т. Д. Для Venue и Region.

Однако, кажется, вместо этого я должен произвести что-то подобное

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} во второй модели жанра фактически возвращает идентификатор класса. (Я думаю). Тем не менее, похоже, что это работает здесь и здесь

Я понимаю, что это довольно эпичный вопрос, так что спасибо, если вы дошли до этого. Любая помощь будет принята с благодарностью!

Ответы [ 4 ]

3 голосов
/ 22 апреля 2009

Ассоциации предназначены для чтения и записи . Большая часть их ценности в том, что вы можете сделать что-то вроде этого:

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

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

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

потому что это не имеет смысла. Жанр у Места есть, только если у группы с этим жанром есть концерт.

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

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 голос
/ 23 апреля 2009

Вы можете добавить условия и параметры в ваши ассоциации. Последние версии 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

Который будет охватывать связанные записи, выполняя условия с предоставленными параметрами.

Plus! именованные области являются составными.

И другие области применения:

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

И связать их вместе: folder.pages.published.commented

0 голосов
/ 23 апреля 2009

Звучит как работа для nested_has_many_through ! Отличный плагин, который позволяет делать вложенные has_many :throughs

0 голосов
/ 22 апреля 2009

Для подобных ассоциаций вы в конечном итоге будете писать собственный SQL - нет никакого реального способа справиться с такой цепочкой ассоциаций без необходимости выполнять довольно массивные объединения, и в действительности эффективный способ для встроенных генераторов запросов обрабатывать его с помощью одной строки.

Вы также можете посмотреть на параметр: joins ActiveRecord - это может сделать то, что вы хотите.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...