Permalink
Browse files

Add equality to ALL THE THINGS (that matter)

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...
1 parent 1de1041 commit 6e638bba594b6164190d2a6fb96ffa07a20b11f3 @ernie ernie committed Aug 19, 2012
Showing with 737 additions and 4 deletions.
  1. +10 −0 lib/arel/nodes/and.rb
  2. +11 −0 lib/arel/nodes/binary.rb
  3. +11 −0 lib/arel/nodes/extract.rb
  4. +7 −0 lib/arel/nodes/false.rb
  5. +11 −0 lib/arel/nodes/function.rb
  6. +12 −0 lib/arel/nodes/insert_statement.rb
  7. +9 −0 lib/arel/nodes/named_function.rb
  8. +20 −0 lib/arel/nodes/select_core.rb
  9. +15 −1 lib/arel/nodes/select_statement.rb
  10. +7 −0 lib/arel/nodes/terminal.rb
  11. +7 −0 lib/arel/nodes/true.rb
  12. +10 −0 lib/arel/nodes/unary.rb
  13. +15 −0 lib/arel/nodes/update_statement.rb
  14. +29 −1 lib/arel/nodes/window.rb
  15. +13 −0 lib/arel/table.rb
  16. +20 −0 test/nodes/test_and.rb
  17. +12 −0 test/nodes/test_as.rb
  18. +10 −0 test/nodes/test_ascending.rb
  19. +10 −0 test/nodes/test_bin.rb
  20. +12 −0 test/nodes/test_count.rb
  21. +20 −0 test/nodes/test_delete_statement.rb
  22. +10 −0 test/nodes/test_descending.rb
  23. +20 −0 test/nodes/test_distinct.rb
  24. +10 −0 test/nodes/test_equality.rb
  25. +14 −0 test/nodes/test_extract.rb
  26. +20 −0 test/nodes/test_false.rb
  27. +12 −0 test/nodes/test_grouping.rb
  28. +10 −0 test/nodes/test_infix_operation.rb
  29. +24 −0 test/nodes/test_insert_statement.rb
  30. +16 −0 test/nodes/test_named_function.rb
  31. +12 −0 test/nodes/test_not.rb
  32. +12 −0 test/nodes/test_or.rb
  33. +18 −0 test/nodes/test_over.rb
  34. +38 −0 test/nodes/test_select_core.rb
  35. +36 −0 test/nodes/test_select_statement.rb
  36. +10 −0 test/nodes/test_sql_literal.rb
  37. +12 −0 test/nodes/test_sum.rb
  38. +22 −2 test/nodes/test_table_alias.rb
  39. +21 −0 test/nodes/test_true.rb
  40. +40 −0 test/nodes/test_update_statement.rb
  41. +73 −0 test/nodes/test_window.rb
  42. +12 −0 test/test_attributes.rb
  43. +24 −0 test/test_table.rb
View
@@ -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
@@ -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{
@@ -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
@@ -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
@@ -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{
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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{
@@ -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
@@ -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
@@ -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
@@ -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)
View
@@ -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
@@ -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
+
View
@@ -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
@@ -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
@@ -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
Oops, something went wrong.

0 comments on commit 6e638bb

Please sign in to comment.