Make left joins feel like first-class citizens in ActiveRecord.
Installation
Add this line to your application's Gemfile:
gem 'arlj'And then execute:
$ bundle
Or install it yourself as:
$ gem install arlj
Usage
Load Arlj into your class:
class Parent < ActiveRecord::Base
extend Arlj
endOr extend all of ActiveRecord models:
ActiveRecord::Base.extend ArljThen begin to left join!
puts Parent.left_joins(:children).group('records.id').select('COUNT(children.id)').to_sql
=> SELECT COUNT(children.id)
FROM "parents"
LEFT OUTER JOIN "children"
ON "children"."parent_id" = "parents"."id"
GROUP BY records.idleft_joins is purposely low level for maximum control.
Arlj has an aggregation method that is higher level and generally easier to use:
Parent.left_joins_aggregate(:children, 'COUNT(*)').to_sql
=> SELECT "parents".*
FROM "parents"
LEFT OUTER JOIN (SELECT "children"."parent_id"
, COUNT("children"."id") AS children_count
FROM "children"
GROUP BY "children"."parent_id") arlj_aggregate_children
ON arlj_aggregate_children."parent_id" = "parents"."id"Supported aggregation functions are COUNT(), SUM(), AVG(), MIN(), and MAX().
The aggregation column has a default name of {table}_{function}_{column} which
is easily renamed:
Parent.left_joins_aggregate(:children, 'SUM(age)' => 'ekkekkekkekkeptangya').to_sql
=> SELECT "parents".*
FROM "parents"
LEFT OUTER JOIN (SELECT "children"."parent_id"
, SUM("children"."age") AS ekkekkekkekkeptangya
FROM "children"
GROUP BY "children"."parent_id") arlj_aggregate_children
ON arlj_aggregate_children."parent_id" = "parents"."id"Since Arlj uses a sub-select, you can easily chain additional queries:
Parent.left_joins_aggregate(:children, 'COUNT(*)').select('children_count').to_sql
=> SELECT children_count
FROM "parents"
LEFT OUTER JOIN (SELECT "children"."parent_id"
, COUNT("children"."id") AS children_count
FROM "children"
GROUP BY "children"."parent_id") arlj_aggregate_children
ON arlj_aggregate_children."parent_id" = "parents"."id"Arlj also supports some basic where clauses:
Parent.left_joins_aggregate(:children, 'COUNT(*)', where: {age: 1..5}).to_sql
=> SELECT "parents".*
FROM "parents"
LEFT OUTER JOIN (SELECT "children"."parent_id"
, COUNT("children"."id") AS children_count
FROM "children"
WHERE ("children"."age" BETWEEN 1 AND 5)
GROUP BY "children"."parent_id") arlj_aggregate_children
ON arlj_aggregate_children."parent_id" = "parents"."id"If you prefer, you may also use arlj and arlj_aggregate instead of
left_joins and left_joins_aggregate respectively. To prevent potential
naming conflicts, use Arlj::Base instead:
class Parent < ActiveRecord::Base
extend Arlj::Base
endArlj has an experimental flag that uses the memoist gem to memoize the generated join SQL:
Arlj.memoize!This has not been proven to be faster.
Gotchas
-
Since
left_joins_aggregateuses a sub-select for its aggregation, it can underperform a better optimized query. -
When
left_joins_aggregatejoins zero records, the aggregate column is NULL. To operate correctly on these columns, please useCOALESCE(col, 0).
TODO
left_joins(nested: :relations)left_joins_aggregate([...], merge: User.active)has_and_belongs_to_manyhas_many :through =>
Contributing
- Fork it ( https://github.com/fengb/arlj/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request