Skip to content

Commit

Permalink
Add equality to ALL THE THINGS (that matter)
Browse files Browse the repository at this point in the history
People are often trying to use ARel nodes inside ActiveRecord, and when
they do so, lots of things can break, because ActiveRecord relies on
Array#uniq and sometimes hash key equality to handle values that end up
in wheres, havings, etc. By implementing equality for all the nodes, we
should hopefully be able to prevent any nodes (even nodes containing
other nodes) from failing an equality check they should otherwise pass,
and alleviate many of these errors.

Fixes #130
  • Loading branch information
ernie committed Aug 19, 2012
1 parent 1de1041 commit 6e638bb
Show file tree
Hide file tree
Showing 43 changed files with 737 additions and 4 deletions.
10 changes: 10 additions & 0 deletions lib/arel/nodes/and.rb
Expand Up @@ -18,6 +18,16 @@ def left
def right
children[1]
end

def hash
children.hash
end

def eql? other
self.class == other.class &&
self.children == other.children
end
alias :== :eql?
end
end
end
11 changes: 11 additions & 0 deletions lib/arel/nodes/binary.rb
Expand Up @@ -13,6 +13,17 @@ def initialize_copy other
@left = @left.clone if @left
@right = @right.clone if @right
end

def hash
[@left, @right].hash
end

def eql? other
self.class == other.class &&
self.left == other.left &&
self.right == other.right
end
alias :== :eql?
end

%w{
Expand Down
11 changes: 11 additions & 0 deletions lib/arel/nodes/extract.rb
Expand Up @@ -18,6 +18,17 @@ def as aliaz
self.alias = SqlLiteral.new(aliaz)
self
end

def hash
super ^ [@field, @alias].hash
end

def eql? other
super &&
self.field == other.field &&
self.alias == other.alias
end
alias :== :eql?
end
end
end
7 changes: 7 additions & 0 deletions lib/arel/nodes/false.rb
@@ -1,6 +1,13 @@
module Arel
module Nodes
class False < Arel::Nodes::Node
def hash
self.class.hash
end

def eql? other
self.class == other.class
end
end
end
end
11 changes: 11 additions & 0 deletions lib/arel/nodes/function.rb
Expand Up @@ -16,6 +16,17 @@ def as aliaz
self.alias = SqlLiteral.new(aliaz)
self
end

def hash
[@expressions, @alias, @distinct].hash
end

def eql? other
self.class == other.class &&
self.expressions == other.expressions &&
self.alias == other.alias &&
self.distinct == other.distinct
end
end

%w{
Expand Down
12 changes: 12 additions & 0 deletions lib/arel/nodes/insert_statement.rb
Expand Up @@ -14,6 +14,18 @@ def initialize_copy other
@columns = @columns.clone
@values = @values.clone if @values
end

def hash
[@relation, @columns, @values].hash
end

def eql? other
self.class == other.class &&
self.relation == other.relation &&
self.columns == other.columns &&
self.values == other.values
end
alias :== :eql?
end
end
end
9 changes: 9 additions & 0 deletions lib/arel/nodes/named_function.rb
Expand Up @@ -7,6 +7,15 @@ def initialize name, expr, aliaz = nil
super(expr, aliaz)
@name = name
end

def hash
super ^ @name.hash
end

def eql? other
super && self.name == other.name
end
alias :== :eql?
end
end
end
20 changes: 20 additions & 0 deletions lib/arel/nodes/select_core.rb
Expand Up @@ -37,6 +37,26 @@ def initialize_copy other
@having = @having.clone if @having
@windows = @windows.clone
end

def hash
[
@source, @top, @set_quantifier, @projections,
@wheres, @groups, @having, @windows
].hash
end

def eql? other
self.class == other.class &&
self.source == other.source &&
self.top == other.top &&
self.set_quantifier == other.set_quantifier &&
self.projections == other.projections &&
self.wheres == other.wheres &&
self.groups == other.groups &&
self.having == other.having &&
self.windows == other.windows
end
alias :== :eql?
end
end
end
16 changes: 15 additions & 1 deletion lib/arel/nodes/select_statement.rb
Expand Up @@ -5,7 +5,6 @@ class SelectStatement < Arel::Nodes::Node
attr_accessor :limit, :orders, :lock, :offset, :with

def initialize cores = [SelectCore.new]
#puts caller
@cores = cores
@orders = []
@limit = nil
Expand All @@ -19,6 +18,21 @@ def initialize_copy other
@cores = @cores.map { |x| x.clone }
@orders = @orders.map { |x| x.clone }
end

def hash
[@cores, @orders, @limit, @lock, @offset, @with].hash
end

def eql? other
self.class == other.class &&
self.cores == other.cores &&
self.orders == other.orders &&
self.limit == other.limit &&
self.lock == other.lock &&
self.offset == other.offset &&
self.with == other.with
end
alias :== :eql?
end
end
end
7 changes: 7 additions & 0 deletions lib/arel/nodes/terminal.rb
@@ -1,6 +1,13 @@
module Arel
module Nodes
class Distinct < Arel::Nodes::Node
def hash
self.class.hash
end

def eql? other
self.class == other.class
end
end
end
end
7 changes: 7 additions & 0 deletions lib/arel/nodes/true.rb
@@ -1,6 +1,13 @@
module Arel
module Nodes
class True < Arel::Nodes::Node
def hash
self.class.hash
end

def eql? other
self.class == other.class
end
end
end
end
10 changes: 10 additions & 0 deletions lib/arel/nodes/unary.rb
Expand Up @@ -7,6 +7,16 @@ class Unary < Arel::Nodes::Node
def initialize expr
@expr = expr
end

def hash
@expr.hash
end

def eql? other
self.class == other.class &&
self.expr == other.expr
end
alias :== :eql?
end

%w{
Expand Down
15 changes: 15 additions & 0 deletions lib/arel/nodes/update_statement.rb
Expand Up @@ -18,6 +18,21 @@ def initialize_copy other
@wheres = @wheres.clone
@values = @values.clone
end

def hash
[@relation, @wheres, @values, @orders, @limit, @key].hash
end

def eql? other
self.class == other.class &&
self.relation == other.relation &&
self.wheres == other.wheres &&
self.values == other.values &&
self.orders == other.orders &&
self.limit == other.limit &&
self.key == other.key
end
alias :== :eql?
end
end
end
30 changes: 29 additions & 1 deletion lib/arel/nodes/window.rb
Expand Up @@ -32,6 +32,17 @@ def initialize_copy other
super
@orders = @orders.map { |x| x.clone }
end

def hash
[@orders, @framing].hash
end

def eql? other
self.class == other.class &&
self.orders == other.orders &&
self.framing == other.framing
end
alias :== :eql?
end

class NamedWindow < Window
Expand All @@ -46,6 +57,15 @@ def initialize_copy other
super
@name = other.name.clone
end

def hash
super ^ @name.hash
end

def eql? other
super && self.name == other.name
end
alias :== :eql?
end

class Rows < Unary
Expand All @@ -60,7 +80,15 @@ def initialize(expr = nil)
end
end

class CurrentRow < Arel::Nodes::Node; end
class CurrentRow < Node
def hash
self.class.hash
end

def eql? other
self.class == other.class
end
end

class Preceding < Unary
def initialize(expr = nil)
Expand Down
13 changes: 13 additions & 0 deletions lib/arel/table.rb
Expand Up @@ -123,6 +123,19 @@ def insert_manager
InsertManager.new(@engine)
end

def hash
[@name, @engine, @aliases, @table_alias].hash
end

def eql? other
self.class == other.class &&
self.name == other.name &&
self.engine == other.engine &&
self.aliases == other.aliases &&
self.table_alias == other.table_alias
end
alias :== :eql?

private

def attributes_for columns
Expand Down
20 changes: 20 additions & 0 deletions test/nodes/test_and.rb
@@ -0,0 +1,20 @@
require 'helper'

module Arel
module Nodes
describe 'And' do
describe 'equality' do
it 'is equal with equal ivars' do
array = [And.new(['foo', 'bar']), And.new(['foo', 'bar'])]
assert_equal 1, array.uniq.size
end

it 'is not equal with different ivars' do
array = [And.new(['foo', 'bar']), And.new(['foo', 'baz'])]
assert_equal 2, array.uniq.size
end
end
end
end
end

12 changes: 12 additions & 0 deletions test/nodes/test_as.rb
Expand Up @@ -17,6 +17,18 @@ module Nodes
assert_kind_of Arel::Nodes::SqlLiteral, as.right
end
end

describe 'equality' do
it 'is equal with equal ivars' do
array = [As.new('foo', 'bar'), As.new('foo', 'bar')]
assert_equal 1, array.uniq.size
end

it 'is not equal with different ivars' do
array = [As.new('foo', 'bar'), As.new('foo', 'baz')]
assert_equal 2, array.uniq.size
end
end
end
end
end
10 changes: 10 additions & 0 deletions test/nodes/test_ascending.rb
Expand Up @@ -29,6 +29,16 @@ def test_descending?
ascending = Ascending.new 'zomg'
assert !ascending.descending?
end

def test_equality_with_same_ivars
array = [Ascending.new('zomg'), Ascending.new('zomg')]
assert_equal 1, array.uniq.size
end

def test_inequality_with_different_ivars
array = [Ascending.new('zomg'), Ascending.new('zomg!')]
assert_equal 2, array.uniq.size
end
end
end
end
10 changes: 10 additions & 0 deletions test/nodes/test_bin.rb
Expand Up @@ -18,6 +18,16 @@ def test_mysql_to_sql
node = Arel::Nodes::Bin.new(Arel.sql('zomg'))
assert_equal 'BINARY zomg', viz.accept(node)
end

def test_equality_with_same_ivars
array = [Bin.new('zomg'), Bin.new('zomg')]
assert_equal 1, array.uniq.size
end

def test_inequality_with_different_ivars
array = [Bin.new('zomg'), Bin.new('zomg!')]
assert_equal 2, array.uniq.size
end
end
end
end

0 comments on commit 6e638bb

Please sign in to comment.