squeel_rbg 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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