squeel_rbg 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/.gitignore +4 -0
  2. data/.yardopts +3 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +20 -0
  5. data/README.md +398 -0
  6. data/Rakefile +19 -0
  7. data/lib/core_ext/hash.rb +13 -0
  8. data/lib/core_ext/symbol.rb +39 -0
  9. data/lib/squeel/adapters/active_record/3.0/association_preload.rb +15 -0
  10. data/lib/squeel/adapters/active_record/3.0/compat.rb +142 -0
  11. data/lib/squeel/adapters/active_record/3.0/context.rb +66 -0
  12. data/lib/squeel/adapters/active_record/3.0/join_association.rb +54 -0
  13. data/lib/squeel/adapters/active_record/3.0/join_dependency.rb +84 -0
  14. data/lib/squeel/adapters/active_record/3.0/relation.rb +327 -0
  15. data/lib/squeel/adapters/active_record/context.rb +66 -0
  16. data/lib/squeel/adapters/active_record/join_association.rb +44 -0
  17. data/lib/squeel/adapters/active_record/join_dependency.rb +83 -0
  18. data/lib/squeel/adapters/active_record/preloader.rb +21 -0
  19. data/lib/squeel/adapters/active_record/relation.rb +351 -0
  20. data/lib/squeel/adapters/active_record.rb +28 -0
  21. data/lib/squeel/configuration.rb +54 -0
  22. data/lib/squeel/constants.rb +24 -0
  23. data/lib/squeel/context.rb +67 -0
  24. data/lib/squeel/dsl.rb +86 -0
  25. data/lib/squeel/nodes/aliasing.rb +13 -0
  26. data/lib/squeel/nodes/and.rb +9 -0
  27. data/lib/squeel/nodes/as.rb +14 -0
  28. data/lib/squeel/nodes/binary.rb +32 -0
  29. data/lib/squeel/nodes/function.rb +66 -0
  30. data/lib/squeel/nodes/join.rb +113 -0
  31. data/lib/squeel/nodes/key_path.rb +192 -0
  32. data/lib/squeel/nodes/nary.rb +45 -0
  33. data/lib/squeel/nodes/not.rb +9 -0
  34. data/lib/squeel/nodes/operation.rb +32 -0
  35. data/lib/squeel/nodes/operators.rb +43 -0
  36. data/lib/squeel/nodes/or.rb +9 -0
  37. data/lib/squeel/nodes/order.rb +53 -0
  38. data/lib/squeel/nodes/predicate.rb +71 -0
  39. data/lib/squeel/nodes/predicate_operators.rb +29 -0
  40. data/lib/squeel/nodes/stub.rb +125 -0
  41. data/lib/squeel/nodes/unary.rb +28 -0
  42. data/lib/squeel/nodes.rb +17 -0
  43. data/lib/squeel/predicate_methods.rb +14 -0
  44. data/lib/squeel/version.rb +3 -0
  45. data/lib/squeel/visitors/attribute_visitor.rb +191 -0
  46. data/lib/squeel/visitors/base.rb +112 -0
  47. data/lib/squeel/visitors/predicate_visitor.rb +319 -0
  48. data/lib/squeel/visitors/symbol_visitor.rb +48 -0
  49. data/lib/squeel/visitors.rb +3 -0
  50. data/lib/squeel.rb +28 -0
  51. data/lib/squeel_rbg.rb +5 -0
  52. data/spec/blueprints/articles.rb +5 -0
  53. data/spec/blueprints/comments.rb +5 -0
  54. data/spec/blueprints/notes.rb +3 -0
  55. data/spec/blueprints/people.rb +4 -0
  56. data/spec/blueprints/tags.rb +3 -0
  57. data/spec/console.rb +22 -0
  58. data/spec/core_ext/symbol_spec.rb +75 -0
  59. data/spec/helpers/squeel_helper.rb +21 -0
  60. data/spec/spec_helper.rb +66 -0
  61. data/spec/squeel/adapters/active_record/context_spec.rb +44 -0
  62. data/spec/squeel/adapters/active_record/join_association_spec.rb +18 -0
  63. data/spec/squeel/adapters/active_record/join_dependency_spec.rb +66 -0
  64. data/spec/squeel/adapters/active_record/relation_spec.rb +627 -0
  65. data/spec/squeel/dsl_spec.rb +92 -0
  66. data/spec/squeel/nodes/function_spec.rb +149 -0
  67. data/spec/squeel/nodes/join_spec.rb +47 -0
  68. data/spec/squeel/nodes/key_path_spec.rb +100 -0
  69. data/spec/squeel/nodes/operation_spec.rb +149 -0
  70. data/spec/squeel/nodes/operators_spec.rb +87 -0
  71. data/spec/squeel/nodes/order_spec.rb +30 -0
  72. data/spec/squeel/nodes/predicate_operators_spec.rb +88 -0
  73. data/spec/squeel/nodes/predicate_spec.rb +50 -0
  74. data/spec/squeel/nodes/stub_spec.rb +198 -0
  75. data/spec/squeel/visitors/attribute_visitor_spec.rb +142 -0
  76. data/spec/squeel/visitors/predicate_visitor_spec.rb +342 -0
  77. data/spec/squeel/visitors/symbol_visitor_spec.rb +42 -0
  78. data/spec/support/schema.rb +104 -0
  79. data/squeel.gemspec +43 -0
  80. metadata +246 -0
data/lib/squeel/dsl.rb ADDED
@@ -0,0 +1,86 @@
1
+ module Squeel
2
+ # Interprets DSL blocks, generating various Squeel nodes as appropriate.
3
+ class DSL
4
+
5
+ # We're creating a BlankSlate-type class here, since we want most
6
+ # method calls to fall through to method_missing.
7
+ Squeel.evil_things do
8
+ (instance_methods + private_instance_methods).each do |method|
9
+ unless method.to_s =~ /^(__|instance_eval)/
10
+ undef_method method
11
+ end
12
+ end
13
+ end
14
+
15
+ # Called from an adapter, not directly.
16
+ # Evaluates a block of Squeel DSL code.
17
+ #
18
+ # @example A DSL block that uses instance_eval
19
+ # Post.where{title == 'Hello world!'}
20
+ #
21
+ # @example A DSL block with access to methods from the closure
22
+ # Post.where{|dsl| dsl.title == local_method(local_var)}
23
+ #
24
+ # @yield [dsl] A block of Squeel DSL code, with an optional argument if
25
+ # access to closure methods is desired.
26
+ # @return The results of the interpreted DSL code.
27
+ def self.eval(&block)
28
+ if block.arity > 0
29
+ yield self.new(block.binding)
30
+ else
31
+ self.new(block.binding).instance_eval(&block)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # This isn't normally called directly, but via DSL.eval, which will
38
+ # pass the block's binding to the new instance, for use with #my.
39
+ #
40
+ # @param [Binding] The block's binding.
41
+ def initialize(caller_binding)
42
+ @caller = caller_binding.eval 'self'
43
+ end
44
+
45
+ # If you really need to get at an instance variable or method inside
46
+ # a DSL block, this method will let you do it. It passes a block back
47
+ # to the DSL's caller for instance_eval.
48
+ #
49
+ # It's also pretty evil, so I hope you enjoy using it while I'm burning in
50
+ # programmer hell.
51
+ #
52
+ # @param &block A block to instance_eval against the DSL's caller.
53
+ # @return
54
+ def my(&block)
55
+ @caller.instance_eval &block
56
+ end
57
+
58
+ # Node generation inside DSL blocks.
59
+ #
60
+ # @overload node_name
61
+ # Creates a Stub. Method calls chained from this Stub will determine
62
+ # what type of node we eventually end up with.
63
+ # @return [Nodes::Stub] A stub with the name of the method
64
+ # @overload node_name(klass)
65
+ # Creates a Join with a polymorphic class matching the given parameter
66
+ # @param [Class] klass The polymorphic class of the join node
67
+ # @return [Nodes::Join] A join node with the name of the method and the given class
68
+ # @overload node_name(first_arg, *other_args)
69
+ # Creates a Function with the given arguments becoming the function's arguments
70
+ # @param first_arg The first argument
71
+ # @param *other_args Optional additional arguments
72
+ # @return [Nodes::Function] A function node for the given method name with the given arguments
73
+ def method_missing(method_id, *args)
74
+ super if method_id == :to_ary
75
+
76
+ if args.empty?
77
+ Nodes::Stub.new method_id
78
+ elsif (args.size == 1) && (Class === args[0])
79
+ Nodes::Join.new(method_id, Arel::InnerJoin, args[0])
80
+ else
81
+ Nodes::Function.new method_id, args
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,13 @@
1
+ require 'squeel/nodes/as'
2
+
3
+ module Squeel
4
+ module Nodes
5
+ module Aliasing
6
+
7
+ def as(name)
8
+ As.new(self, name.to_s)
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ require 'squeel/nodes/nary'
2
+
3
+ module Squeel
4
+ module Nodes
5
+ # A grouping of nodes that will be converted to an Arel::Nodes::And upon visitation.
6
+ class And < Nary
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ require 'squeel/nodes/binary'
2
+
3
+ module Squeel
4
+ module Nodes
5
+ # A node representing an SQL alias, which will result in same when visited.
6
+ class As < Binary
7
+ # @param left The node to be aliased
8
+ # @param right The alias name
9
+ def initialize(left, right)
10
+ @left, @right = left, right
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ require 'squeel/nodes/predicate_operators'
2
+
3
+ module Squeel
4
+ module Nodes
5
+ # A node that represents an operation with two operands.
6
+ class Binary
7
+
8
+ include PredicateOperators
9
+
10
+ # The left operand
11
+ attr_reader :left
12
+
13
+ # The right operand
14
+ attr_reader :right
15
+
16
+ # @param left The left operand
17
+ # @param right The right operand
18
+ def initialize(left, right)
19
+ @left, @right = left, right
20
+ end
21
+
22
+ # Comparison with other nodes
23
+ def eql?(other)
24
+ self.class == other.class &&
25
+ self.left == other.left &&
26
+ self.right == other.right
27
+ end
28
+ alias :== :eql?
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,66 @@
1
+ require 'squeel/predicate_methods'
2
+
3
+ module Squeel
4
+ module Nodes
5
+ # A node that represents an SQL function call
6
+ class Function
7
+
8
+ include PredicateMethods
9
+ include Operators
10
+
11
+ alias :== :eq
12
+ alias :'^' :not_eq
13
+ alias :'!=' :not_eq if respond_to?(:'!=')
14
+ alias :>> :in
15
+ alias :<< :not_in
16
+ alias :=~ :matches
17
+ alias :'!~' :does_not_match if respond_to?(:'!~')
18
+ alias :> :gt
19
+ alias :>= :gteq
20
+ alias :< :lt
21
+ alias :<= :lteq
22
+
23
+ # @return [Symbol] The name of the SQL function to be called
24
+ attr_reader :name
25
+
26
+ # @return [Array] The arguments to be passed to the SQL function
27
+ attr_reader :args
28
+
29
+ # @return [String] The SQL function's alias
30
+ # @return [NilClass] If no alias
31
+ attr_reader :alias
32
+
33
+ # Create a node representing an SQL Function with the given name and arguments
34
+ # @param [Symbol] name The function name
35
+ # @param [Array] args The array of arguments to pass to the function.
36
+ def initialize(name, args)
37
+ @name, @args = name, args
38
+ end
39
+
40
+ # Set an alias for the function
41
+ # @param [String, Symbol] The alias name
42
+ # @return [Function] This function with the new alias value.
43
+ def as(alias_name)
44
+ @alias = alias_name.to_s
45
+ self
46
+ end
47
+
48
+ def asc
49
+ Order.new self, 1
50
+ end
51
+
52
+ def desc
53
+ Order.new self, -1
54
+ end
55
+
56
+ # expand_hash_conditions_for_aggregates assumes our hash keys can be
57
+ # converted to symbols, so this has to be implemented, but it doesn't
58
+ # really have to do anything useful.
59
+ # @return [NilClass] Just to avoid bombing out on expand_hash_conditions_for_aggregates
60
+ def to_sym
61
+ nil
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,113 @@
1
+ module Squeel
2
+ module Nodes
3
+ # A node representing a joined association
4
+ class Join
5
+ undef_method :id if method_defined?(:id)
6
+
7
+ # @return [Symbol] The join's association name
8
+ attr_reader :_name
9
+
10
+ # @return [Arel::InnerJoin, Arel::OuterJoin] The ARel join type
11
+ attr_reader :_type
12
+
13
+ # @return [Class] The polymorphic belongs_to join class
14
+ # @return [NilClass] If the join is not a polymorphic belongs_to join
15
+ attr_reader :_klass
16
+
17
+ # Create a new Join node
18
+ # @param [Symbol] name The association name
19
+ # @param [Arel::InnerJoin, Arel::OuterJoin] type The ARel join class
20
+ # @param [Class, String, Symbol] klass The polymorphic belongs_to class or class name
21
+ def initialize(name, type = Arel::InnerJoin, klass = nil)
22
+ @_name, @_type = name, type
23
+ @_klass = convert_to_class(klass) if klass
24
+ end
25
+
26
+ # Set the join type to an inner join
27
+ # @return [Join] The join, with an updated join type.
28
+ def inner
29
+ @_type = Arel::InnerJoin
30
+ self
31
+ end
32
+
33
+ # Set the join type to an outer join
34
+ # @return [Join] The join, with an updated join type.
35
+ def outer
36
+ @_type = Arel::OuterJoin
37
+ self
38
+ end
39
+
40
+ # Set the polymorphic belongs_to class
41
+ # @param [Class, String, Symbol] class_or_class_name The polymorphic belongs_to class or class name
42
+ # @return [Class] The class that's just been set
43
+ def _klass=(class_or_class_name)
44
+ @_klass = convert_to_class(class_or_class_name)
45
+ end
46
+
47
+ # Returns a true value (the class itself) if a polymorphic belongs_to class has been set
48
+ # @return [NilClass, Class] The class, if present.
49
+ def polymorphic?
50
+ @_klass
51
+ end
52
+
53
+ # Compare with other objects
54
+ def eql?(other)
55
+ self.class == other.class &&
56
+ self._name == other._name &&
57
+ self._type == other._type &&
58
+ self._klass == other._klass
59
+ end
60
+ alias :== :eql?
61
+
62
+ # Ensures that a Join can be used as the base of a new KeyPath
63
+ # @overload node_name
64
+ # Creates a new KeyPath with this Join as the base and the method_name as the endpoint
65
+ # @return [KeyPath] The new keypath
66
+ # @overload node_name(klass)
67
+ # Creates a new KeyPath with this Join as the base and a polymorphic belongs_to join as the endpoint
68
+ # @param [Class] klass The polymorphic class for the join
69
+ # @return [KeyPath] The new keypath
70
+ def method_missing(method_id, *args)
71
+ super if method_id == :to_ary
72
+ if (args.size == 1) && (Class === args[0])
73
+ KeyPath.new(self, Join.new(method_id, Arel::InnerJoin, args[0]))
74
+ else
75
+ KeyPath.new(self, method_id)
76
+ end
77
+ end
78
+
79
+ # Return a KeyPath containing only this Join, but flagged as absolute.
80
+ # This helps Joins behave more like a KeyPath, as anyone using the Squeel
81
+ # DSL is likely to think of them as such.
82
+ # @return [KeyPath] An absolute KeyPath, containing only this Join
83
+ def ~
84
+ KeyPath.new [], self, true
85
+ end
86
+
87
+ # expand_hash_conditions_for_aggregates assumes our hash keys can be
88
+ # converted to symbols, so this has to be implemented, but it doesn't
89
+ # really have to do anything useful.
90
+ # @return [NilClass] Just to avoid bombing out on expand_hash_conditions_for_aggregates
91
+ def to_sym
92
+ nil
93
+ end
94
+
95
+ private
96
+
97
+ # Convert the given value into a class.
98
+ # @param [Class, String, Symbol] value The value to be converted
99
+ # @return [Class] The class after conversion
100
+ def convert_to_class(value)
101
+ case value
102
+ when String, Symbol
103
+ Kernel.const_get(value)
104
+ when Class
105
+ value
106
+ else
107
+ raise ArgumentError, "#{value} cannot be converted to a Class"
108
+ end
109
+ end
110
+
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,192 @@
1
+ require 'squeel/nodes/operators'
2
+ require 'squeel/nodes/predicate_operators'
3
+
4
+ module Squeel
5
+ module Nodes
6
+ # A node that stores a path of keys (of Symbol, Stub, or Join values) and
7
+ # an endpoint. Used similarly to a nested hash.
8
+ class KeyPath
9
+ include PredicateOperators
10
+ include Operators
11
+
12
+ # We need some methods to fall through to the endpoint or create a new
13
+ # stub of the given name
14
+ %w(id == != =~ !~).each do |method_name|
15
+ undef_method method_name if method_defined?(method_name)
16
+ end
17
+
18
+ # @return [Array<Symbol, Stub, Join>] The path
19
+ attr_reader :path
20
+
21
+ # @return The endpoint, either another key as in the path, or a predicate, function, etc.
22
+ attr_reader :endpoint
23
+
24
+ # Create a new KeyPath.
25
+ # @param [Array, Object] path The intial path. Will be converted to an array if it isn't already.
26
+ # @param endpoint the endpoint of the KeyPath
27
+ # @param [Boolean] absolute If the KeyPath should start from the base
28
+ # or remain relative to whatever location it's found.
29
+ def initialize(path, endpoint, absolute = false)
30
+ @path, @endpoint = path, endpoint
31
+ @path = [@path] unless Array === @path
32
+ @endpoint = Stub.new(@endpoint) if Symbol === @endpoint
33
+ @absolute = absolute
34
+ end
35
+
36
+ # Whether or not the KeyPath should be interpreted relative to its current location
37
+ # (if nested in a Hash, for instance) or as though it's at the base.
38
+ # @return [Boolean] The flag's value
39
+ def absolute?
40
+ @absolute
41
+ end
42
+
43
+ # Object comparison
44
+ def eql?(other)
45
+ self.class == other.class &&
46
+ self.path == other.path &&
47
+ self.endpoint.eql?(other.endpoint) &&
48
+ self.absolute? == other.absolute?
49
+ end
50
+
51
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
52
+ # responds to |
53
+ # @param other The right hand side of the operation
54
+ # @return [Or] An Or node with the KeyPath on the left side and the other object on the right.
55
+ def |(other)
56
+ endpoint.respond_to?(:|) ? super : no_method_error(:|)
57
+ end
58
+
59
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
60
+ # responds to &
61
+ # @param other The right hand side of the operation
62
+ # @return [And] An And node with the KeyPath on the left side and the other object on the right.
63
+ def &(other)
64
+ endpoint.respond_to?(:&) ? super : no_method_error(:&)
65
+ end
66
+
67
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
68
+ # responds to -@
69
+ # @return [Not] A not node with the KeyPath as its expression
70
+ def -@
71
+ endpoint.respond_to?(:-@) ? super : no_method_error(:-@)
72
+ end
73
+
74
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
75
+ # responds to +
76
+ # @param other The right hand side of the operation
77
+ # @return [Operation] An operation (with operator +) with the KeyPath on its left and the other object on the right.
78
+ def +(other)
79
+ endpoint.respond_to?(:+) ? super : no_method_error(:+)
80
+ end
81
+
82
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
83
+ # responds to -
84
+ # @param other The right hand side of the operation
85
+ # @return [Operation] An operation (with operator -) with the KeyPath on its left and the other object on the right.
86
+ def -(other)
87
+ endpoint.respond_to?(:-) ? super : no_method_error(:-)
88
+ end
89
+
90
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
91
+ # responds to *
92
+ # @param other The right hand side of the operation
93
+ # @return [Operation] An operation (with operator *) with the KeyPath on its left and the other object on the right.
94
+ def *(other)
95
+ endpoint.respond_to?(:*) ? super : no_method_error(:*)
96
+ end
97
+
98
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
99
+ # responds to /
100
+ # @param other The right hand side of the operation
101
+ # @return [Operation] An operation (with operator /) with the KeyPath on its left and the other object on the right.
102
+ def /(other)
103
+ endpoint.respond_to?(:/) ? super : no_method_error(:/)
104
+ end
105
+
106
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
107
+ # responds to #op
108
+ # @param [String, Symbol] operator The custom operator
109
+ # @param other The right hand side of the operation
110
+ # @return [Operation] An operation with the given custom operator, the KeyPath on its left and the other object on the right.
111
+ def op(operator, other)
112
+ endpoint.respond_to?(:op) ? super : no_method_error(:/)
113
+ end
114
+
115
+ # Set the absolute flag on this KeyPath
116
+ # @return [KeyPath] This keypath, with its absolute flag set to true
117
+ def ~
118
+ @absolute = true
119
+ self
120
+ end
121
+
122
+ # For use with equality tests
123
+ def hash
124
+ [self.class, endpoint, *path].hash
125
+ end
126
+
127
+ # expand_hash_conditions_for_aggregates assumes our hash keys can be
128
+ # converted to symbols, so this has to be implemented, but it doesn't
129
+ # really have to do anything useful.
130
+ # @return [NilClass] Just to avoid bombing out on expand_hash_conditions_for_aggregates
131
+ def to_sym
132
+ nil
133
+ end
134
+
135
+ # Delegate % to the KeyPath's endpoint, with a bit of special logic for stubs or functions.
136
+ # @param val The value to be supplied to the created/existing predicate
137
+ # @return [KeyPath] This KeyPath, with a predicate endpoint containing the given value
138
+ def %(val)
139
+ case endpoint
140
+ when Stub, Function
141
+ Array === val ? self.in(val) : self.eq(val)
142
+ self
143
+ else
144
+ endpoint % val
145
+ self
146
+ end
147
+ end
148
+
149
+ # @return [Array] The KeyPath's path, including its endpoint, as a single array.
150
+ def path_with_endpoint
151
+ path + [endpoint]
152
+ end
153
+
154
+ # Impleneted (and aliased to :to_str) to play nicely with ActiveRecord grouped calculations
155
+ def to_s
156
+ path.map(&:to_s).join('.') << ".#{endpoint}"
157
+ end
158
+ alias :to_str :to_s
159
+
160
+ # Appends to the KeyPath or delegates to the endpoint, as appropriate
161
+ # @return [KeyPath] The updated KeyPath
162
+ def method_missing(method_id, *args)
163
+ super if method_id == :to_ary
164
+ if endpoint.respond_to? method_id
165
+ @endpoint = @endpoint.send(method_id, *args)
166
+ self
167
+ elsif Stub === endpoint || Join === endpoint
168
+ @path << endpoint
169
+ if args.empty?
170
+ @endpoint = Stub.new(method_id)
171
+ elsif (args.size == 1) && (Class === args[0])
172
+ @endpoint = Join.new(method_id, Arel::InnerJoin, args[0])
173
+ else
174
+ @endpoint = Nodes::Function.new method_id, args
175
+ end
176
+ self
177
+ else
178
+ super
179
+ end
180
+ end
181
+
182
+ private
183
+
184
+ # Raises a NoMethodError manually, bypassing #method_missing.
185
+ # Used by special-case operator overrides.
186
+ def no_method_error(method_id)
187
+ raise NoMethodError, "undefined method `#{method_id}' for #{self}:#{self.class}"
188
+ end
189
+
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,45 @@
1
+ module Squeel
2
+ module Nodes
3
+ # A node containing multiple children. Just the And node for now.
4
+ class Nary
5
+ include PredicateOperators
6
+
7
+ # @return [Array] This node's children
8
+ attr_reader :children
9
+
10
+ # Creates a new Nary node with the given array of children
11
+ # @param [Array] children The node's children
12
+ def initialize(children)
13
+ raise ArgumentError, '#{self.class} requires an array' unless Array === children
14
+ # We don't dup here, as incoming arrays should be created by the
15
+ # PredicateOperators#& method on other nodes. If you're creating And nodes
16
+ # manually, by sure that they're new arays.
17
+ @children = children
18
+ end
19
+
20
+ # Add a new child to the node's children
21
+ # @param other A new child node
22
+ # @return [Nary] This node, with its updated children.
23
+ def &(other)
24
+ @children << other
25
+ self
26
+ end
27
+
28
+ # Append a new Not node to this node's children
29
+ # @param other A new child node
30
+ # @return [Nary] This node, with its new, negated child
31
+ def -(other)
32
+ @children << Not.new(other)
33
+ self
34
+ end
35
+
36
+ # Object comparison
37
+ def eql?(other)
38
+ self.class == other.class &&
39
+ self.children == other.children
40
+ end
41
+ alias :== :eql?
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,9 @@
1
+ require 'squeel/nodes/unary'
2
+
3
+ module Squeel
4
+ module Nodes
5
+ # A node that represents SQL NOT, and will result in same when visited
6
+ class Not < Unary
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ require 'squeel/nodes/function'
2
+
3
+ module Squeel
4
+ module Nodes
5
+ # An Operation is basically a function with its operands limited, and rearranged,
6
+ # so Squeel implements it as such.
7
+ class Operation < Function
8
+
9
+ # Create a new Operation with the given operator and left and right operands
10
+ # @param left The left operand
11
+ # @param [String, Symbol] operator The operator
12
+ # @param right The right operand
13
+ def initialize(left, operator, right)
14
+ super(operator, [left, right])
15
+ end
16
+
17
+ # An operation should probably call its "function" name an "operator", shouldn't it?
18
+ alias :operator :name
19
+
20
+ # @return The left operand
21
+ def left
22
+ args[0]
23
+ end
24
+
25
+ # @return The right operand
26
+ def right
27
+ args[1]
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,43 @@
1
+ module Squeel
2
+ module Nodes
3
+ # Module containing Operation factory methods for inclusion in Squeel nodes
4
+ module Operators
5
+
6
+ # Factory for a new addition operation with this node as its left operand.
7
+ # @param value The right operand
8
+ # @return [Operation] The new addition operation
9
+ def +(value)
10
+ Operation.new(self, :+, value)
11
+ end
12
+
13
+ # Factory for a new subtraction operation with this node as its left operand.
14
+ # @param value The right operand
15
+ # @return [Operation] The new subtraction operation
16
+ def -(value)
17
+ Operation.new(self, :-, value)
18
+ end
19
+
20
+ # Factory for a new multiplication operation with this node as its left operand.
21
+ # @param value The right operand
22
+ # @return [Operation] The new multiplication operation
23
+ def *(value)
24
+ Operation.new(self, :*, value)
25
+ end
26
+
27
+ # Factory for a new division operation with this node as its left operand.
28
+ # @param value The right operand
29
+ # @return [Operation] The new division operation
30
+ def /(value)
31
+ Operation.new(self, :/, value)
32
+ end
33
+
34
+ # Factory for a new custom operation with this node as its left operand.
35
+ # @param value The right operand
36
+ # @return [Operation] The new operation
37
+ def op(operator, value)
38
+ Operation.new(self, operator, value)
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ require 'squeel/nodes/binary'
2
+
3
+ module Squeel
4
+ module Nodes
5
+ # A node representing an SQL OR, which will result in same when visited.
6
+ class Or < Binary
7
+ end
8
+ end
9
+ end