Module: Sequel::Plugins::ManyThroughMany

Defined in:
lib/sequel/plugins/many_through_many.rb

Overview

The many_through_many plugin allow you to create an association to multiple objects using multiple join tables. For example, assume the following associations:

Artist.many_to_many :albums
Album.many_to_many :tags

The many_through_many plugin would allow this:

Artist.plugin :many_through_many
Artist.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]

Which will give you the tags for all of the artist’s albums.

Let’s break down the 2nd argument of the many_through_many call:

[[:albums_artists, :artist_id, :album_id],
 [:albums, :id, :id],
 [:albums_tags, :album_id, :tag_id]]

This argument is an array of arrays with three elements. Each entry in the main array represents a JOIN in SQL:

  • The first element in each array represents the name of the table to join.

  • The second element in each array represents the column used to join to the previous table.

  • The third element in each array represents the column used to join to the next table.

So the “Artist.many_through_many :tags” is translated into something similar to:

FROM artists
JOIN albums_artists ON (artists.id = albums_artists.artist_id)
JOIN albums ON (albums_artists.album_id = albums.id)
JOIN albums_tags ON (albums.id = albums_tag.album_id)
JOIN tags ON (albums_tags.tag_id = tags.id)

The “artists.id” and “tags.id” criteria come from other association options (defaulting to the primary keys of the current and associated tables), but hopefully you can see how each argument in the array is used in the JOIN clauses.

Here are some more examples:

# Same as Artist.many_to_many :albums
Artist.many_through_many :albums, [[:albums_artists, :artist_id, :album_id]]

# All artists that are associated to any album that this artist is associated to
Artist.many_through_many :artists, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id]]

# All albums by artists that are associated to any album that this artist is associated to
Artist.many_through_many :artist_albums, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], \
 [:albums_artists, :album_id, :artist_id], [:artists, :id, :id], [:albums_artists, :artist_id, :album_id]], \
 :class=>:Album

# All tracks on albums by this artist
Artist.many_through_many :tracks, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id]], \
 :right_primary_key=>:album_id

Often you don’t want the current object to appear in the array of associated objects. This is easiest to handle via an :after_load hook:

Artist.many_through_many :artists, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id]],
  :after_load=>proc{|artist, associated_artists| associated_artists.delete(artist)}

You can also handle it by adding a dataset block that excludes the current record (so it won’t be retrieved at all), but that won’t work when eagerly loading, which is why the :after_load proc is recommended instead.

It’s also common to not want duplicate records, in which case the :distinct option can be used:

Artist.many_through_many :artists, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id]],
 :distinct=>true

Defined Under Namespace

Modules: ClassMethods, DatasetMethods Classes: ManyThroughManyAssociationReflection