Skip to content

Union, Intersect, and Difference set operations for ActiveRecord (also, SQL's UnionAll).

License

Notifications You must be signed in to change notification settings

delonnewman/activerecord-setops

Repository files navigation

Ruby Gem Version Contact me on Codementor

Buy Me A Coffee

ActiveRecord::Setops

Union, Intersect, and Difference set operations for ActiveRecord (also, SQL's UnionAll). Has only been tested with Rails 5.

Synopsis

class Student < ActiveRecord::Base; end
class Employee < ActiveRecord::Base; end

(Student.select(:name, :birth_date) | Employee.select(:name, :birth_date)).where("name like John%")

Why?

Joins can be difficult to reason about in Arel (and SQL for that matter). Many joins can be replaced with set operations which are much simpler beasts, may offer performance gains, and have consistent mathematical properties. But these operations while present in Arel are missing in ActiveRecord. This module attempts to correct this lack.

Installation

Add this line to your application's Gemfile:

gem 'activerecord-setops'

And then execute:

$ bundle

Or install it yourself as:

$ gem install activerecord-setops

Non-Installation

If you'd like the functionality, but would prefer to avoid yet another dependency, please fill free to paste the following code into your nearest lib directory, I'm certain it's not perfect but it has been tested with Rails 5, and is being used in production.

module ActiveRecord
  class Relation
    def union(other)
      binary_operation(Arel::Nodes::Union, other)
    end
    alias | union

    def union_all(other)
      binary_operation(Arel::Nodes::UnionAll, other)
    end
    alias + union_all

    def intersect(other)
      binary_operation(Arel::Nodes::Intersect, other)
    end
    alias & intersect

    def difference(other)
      binary_operation(Arel::Nodes::Except, other)
    end
    alias - difference

    private

    def binary_operation(op_class, other)
      @klass.unscoped.from(Arel::Nodes::TableAlias.new(op_class.new(self.arel.ast, other.arel.ast), @klass.arel_table.name))
    end
  end
end

See Also