Skip to content

Instantly share code, notes, and snippets.

@dfurber
Created April 20, 2012 03:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfurber/2425600 to your computer and use it in GitHub Desktop.
Save dfurber/2425600 to your computer and use it in GitHub Desktop.
Union Query in AR with Lazy Evaluation
module UnionQuery
extend ActiveSupport::Concern
included do
def self.union(relation_to_unite, type=nil)
LazyUnionQuery.new self, relation_to_unite, type
end
end
end
class LazyUnionQuery
def initialize(klass, query2, type=:all)
@klass, @query2, @type = klass, query2, type
@query1 = @klass.scoped
end
def to_sql
if @limit or @order
to_sql_with_extras
else
to_sql_without_extras
end
end
def limit(value)
@limit = value
self
end
def order(value)
@order = value
self
end
def to_a
@records ||= @klass.find_by_sql to_sql
end
def all
to_a
end
def first
to_a.first
end
def each(&block)
to_a.each &block
end
def as_json(options = nil) #:nodoc:
to_a.as_json(options)
end
# Returns size of the records.
def size
to_a.length
end
# Returns true if there are no records.
def empty?
to_a.empty?
end
def any?
if block_given?
to_a.any? { |*block_args| yield(*block_args) }
else
!empty?
end
end
private
def to_sql_without_extras
"#{@klass.scoped.to_sql} UNION #{@type.to_s} #{@query2.to_sql}"
end
def to_sql_with_extras
extras = []
extras << "ORDER BY #{@order}" if @order
extras << "LIMIT #{@limit}" if @limit
"(#{@klass.scoped.to_sql}) UNION #{@type.to_s} (#{@query2.to_sql}) #{extras.join ' '}"
end
end
How to do union queries with lazy evaluation.
class Thing1 < ActiveRecord::Base
include UnionQuery
scope :recent, lambda { order 'created_at desc' }
end
class Thing2 < ActiveRecord::Base
end
Thing1 easily unites with another Thing1 query because they are of the same class:
Thing1.recent.union(Thing1.where('name like "%Bob%"'))
You can also unite Thing1 with Thing2. The objects returned will all be Thing1. Maybe refactor to pass in a presenter class?
Thing1.select('name').union Thing2.select('name')
If you want to put limit and order on the union, you can:
Thing1.select('name').union(Thing2.select('name')).limit(10).order('name desc')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment