Skip to content

Instantly share code, notes, and snippets.

@ozydingo
Last active June 16, 2021 22:47
Show Gist options
  • Save ozydingo/70de96ad57ab69003446 to your computer and use it in GitHub Desktop.
Save ozydingo/70de96ad57ab69003446 to your computer and use it in GitHub Desktop.
ActiveRecord left join
module ActiveRecordExtension
extend ActiveSupport::Concern
module ClassMethods
# Simple left join taking advantage of existing Rails & Arel code
def left_joins(*args)
inner_joins = self.joins(*args).arel.join_sources
left_joins = inner_joins.map do |join|
Arel::Nodes::OuterJoin.new(join.left, join.right)
end
self.joins(left_joins)
end
# Find records of self where no records of given association exist
def without(assoc_name)
assoc = reflect_on_association(assoc_name)
left_joins(assoc_name).where(assoc.table_name => {assoc.klass.primary_key => nil})
end
end
end
ActiveRecord::Base.send(:include, ActiveRecordExtension)
@hunterae
Copy link

hunterae commented Sep 6, 2018

Hey, noticed a small issues with this solution: if you do an inner join prior to running this code, it will setup a left join for the previously instantiated inner join, i.e.:

Keyword.joins(:campaign).left_joins(:forms).arel.join_sources.length # => Outputs 3

As a workaround, I suggest changing the self.joins call on line 7 to unscoped.joins.

I cannot say yet whether this change would have unintentional side-effects.

@hunterae
Copy link

hunterae commented Sep 6, 2018

It may also be sufficient to use

unscope(:joins).arel.join_sources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment