Joins

joins and left-outer-joins add SQL JOIN clauses to a relation. They accept several forms:

By association name#

Pass an association name (defined via has-many / belongs-to):

Subscription.joins('user').count;            # belongs_to side
User.joins(:subscriptions).count;            # has_many side
User.joins(:magazines).count;                # has_many :through

The join condition (subscriptions.user_id = users.id, etc.) is derived from the association definition.

Nested associations#

A nested-hash form follows multi-level associations:

User.joins(:subscriptions(:magazine)).count;

This emits a join through subscriptions then magazines.

Raw SQL#

For one-offs that don't map to an association, pass a literal join clause:

my $raw = 'INNER JOIN subscriptions ON subscriptions.user_id = users.id';
User.joins($raw).count;

left-outer-joins#

left-outer-joins keeps rows from the base table even when no related row exists. Useful for "find users with or without subscriptions".

User.left-outer-joins(:subscriptions).count;            # all users
User.left-outer-joins(:subscriptions).distinct.count;   # all users, deduped

Filtering on a joined table#

Once you've joined, where accepts a nested hash to filter on the joined table's columns.

Subscription.joins(:user).where({users => {fname => 'Alice'}}).all;
User.joins(:subscriptions(:magazine))
    .where({magazines => {title => 'Mad'}})
    .distinct
    .all;

A bare column name in where after a join still resolves to the base table:

User.joins(:subscriptions).where({fname => 'Alice'}).distinct.count;
# WHERE users.fname = 'Alice'

references#

references(*@table-names) is a hint that named tables will be filtered or ordered against. It pre-declares the join targets without altering row counts itself, which matters when combining with eager loading (where the difference between preload and eager_load depends on whether you reference the joined table elsewhere).

User.references('posts').count;
User.references('posts', 'comments');

distinct under joins#

A joins on a has_many returns one row per join row, so deduping with distinct is common when counting base records.

User.joins(:subscriptions).count;             # = total subscription count
User.joins(:subscriptions).distinct.count;    # = users with >= 1 subscription