squeel 0.5.5 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +3 -0
- data/Gemfile +8 -3
- data/README.md +368 -0
- data/lib/core_ext/hash.rb +8 -8
- data/lib/core_ext/symbol.rb +7 -6
- data/lib/squeel.rb +2 -0
- data/lib/squeel/adapters/active_record.rb +25 -20
- data/lib/squeel/adapters/active_record/3.0/compat.rb +1 -2
- data/lib/squeel/adapters/active_record/3.0/context.rb +6 -7
- data/lib/squeel/adapters/active_record/3.0/join_dependency.rb +5 -5
- data/lib/squeel/adapters/active_record/context.rb +6 -7
- data/lib/squeel/adapters/active_record/join_dependency.rb +5 -5
- data/lib/squeel/configuration.rb +29 -0
- data/lib/squeel/constants.rb +1 -0
- data/lib/squeel/context.rb +36 -7
- data/lib/squeel/dsl.rb +57 -2
- data/lib/squeel/nodes.rb +6 -0
- data/lib/squeel/nodes/and.rb +1 -0
- data/lib/squeel/nodes/binary.rb +11 -2
- data/lib/squeel/nodes/function.rb +30 -48
- data/lib/squeel/nodes/join.rb +56 -12
- data/lib/squeel/nodes/key_path.rb +68 -2
- data/lib/squeel/nodes/nary.rb +12 -2
- data/lib/squeel/nodes/not.rb +1 -0
- data/lib/squeel/nodes/operation.rb +9 -0
- data/lib/squeel/nodes/operators.rb +16 -0
- data/lib/squeel/nodes/or.rb +1 -0
- data/lib/squeel/nodes/order.rb +19 -1
- data/lib/squeel/nodes/predicate.rb +25 -3
- data/lib/squeel/nodes/predicate_operators.rb +12 -0
- data/lib/squeel/nodes/stub.rb +55 -48
- data/lib/squeel/nodes/unary.rb +7 -1
- data/lib/squeel/predicate_methods.rb +2 -10
- data/lib/squeel/version.rb +1 -1
- data/lib/squeel/visitors/attribute_visitor.rb +80 -4
- data/lib/squeel/visitors/base.rb +70 -4
- data/lib/squeel/visitors/predicate_visitor.rb +28 -9
- data/lib/squeel/visitors/symbol_visitor.rb +1 -1
- data/spec/core_ext/symbol_spec.rb +2 -2
- data/spec/spec_helper.rb +6 -1
- data/spec/squeel/adapters/active_record/context_spec.rb +0 -7
- data/spec/squeel/adapters/active_record/relation_spec.rb +27 -0
- data/spec/squeel/dsl_spec.rb +20 -1
- data/spec/squeel/nodes/join_spec.rb +11 -4
- data/spec/squeel/nodes/key_path_spec.rb +1 -1
- data/spec/squeel/nodes/predicate_spec.rb +0 -42
- data/spec/squeel/nodes/stub_spec.rb +9 -8
- data/spec/squeel/visitors/predicate_visitor_spec.rb +34 -9
- data/squeel.gemspec +6 -9
- metadata +8 -10
- data/README.rdoc +0 -117
- data/lib/squeel/predicate_methods/function.rb +0 -9
- data/lib/squeel/predicate_methods/predicate.rb +0 -11
- data/lib/squeel/predicate_methods/stub.rb +0 -9
- data/lib/squeel/predicate_methods/symbol.rb +0 -9
data/lib/squeel/nodes/join.rb
CHANGED
@@ -1,40 +1,72 @@
|
|
1
1
|
module Squeel
|
2
2
|
module Nodes
|
3
|
+
# A node representing a joined association
|
3
4
|
class Join
|
4
|
-
|
5
|
+
undef_method :id if method_defined?(:id)
|
5
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
|
6
21
|
def initialize(name, type = Arel::InnerJoin, klass = nil)
|
7
|
-
@
|
8
|
-
@
|
22
|
+
@_name, @_type = name, type
|
23
|
+
@_klass = convert_to_class(klass) if klass
|
9
24
|
end
|
10
25
|
|
26
|
+
# Set the join type to an inner join
|
27
|
+
# @return [Join] The join, with an updated join type.
|
11
28
|
def inner
|
12
|
-
@
|
29
|
+
@_type = Arel::InnerJoin
|
13
30
|
self
|
14
31
|
end
|
15
32
|
|
33
|
+
# Set the join type to an outer join
|
34
|
+
# @return [Join] The join, with an updated join type.
|
16
35
|
def outer
|
17
|
-
@
|
36
|
+
@_type = Arel::OuterJoin
|
18
37
|
self
|
19
38
|
end
|
20
39
|
|
21
|
-
|
22
|
-
|
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)
|
23
45
|
end
|
24
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.
|
25
49
|
def polymorphic?
|
26
|
-
@
|
50
|
+
@_klass
|
27
51
|
end
|
28
52
|
|
53
|
+
# Compare with other objects
|
29
54
|
def eql?(other)
|
30
55
|
self.class == other.class &&
|
31
|
-
self.
|
32
|
-
self.
|
33
|
-
self.
|
56
|
+
self._name == other._name &&
|
57
|
+
self._type == other._type &&
|
58
|
+
self._klass == other._klass
|
34
59
|
end
|
35
|
-
|
36
60
|
alias :== :eql?
|
37
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
|
38
70
|
def method_missing(method_id, *args)
|
39
71
|
super if method_id == :to_ary
|
40
72
|
if (args.size == 1) && (Class === args[0])
|
@@ -44,15 +76,27 @@ module Squeel
|
|
44
76
|
end
|
45
77
|
end
|
46
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
|
+
|
47
87
|
# expand_hash_conditions_for_aggregates assumes our hash keys can be
|
48
88
|
# converted to symbols, so this has to be implemented, but it doesn't
|
49
89
|
# really have to do anything useful.
|
90
|
+
# @return [NilClass] Just to avoid bombing out on expand_hash_conditions_for_aggregates
|
50
91
|
def to_sym
|
51
92
|
nil
|
52
93
|
end
|
53
94
|
|
54
95
|
private
|
55
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
|
56
100
|
def convert_to_class(value)
|
57
101
|
case value
|
58
102
|
when String, Symbol
|
@@ -3,12 +3,25 @@ require 'squeel/nodes/predicate_operators'
|
|
3
3
|
|
4
4
|
module Squeel
|
5
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.
|
6
8
|
class KeyPath
|
7
9
|
include PredicateOperators
|
8
10
|
include Operators
|
9
11
|
|
10
|
-
|
12
|
+
undef_method :id if method_defined?(:id)
|
11
13
|
|
14
|
+
# @return [Array<Symbol, Stub, Join>] The path
|
15
|
+
attr_reader :path
|
16
|
+
|
17
|
+
# @return The endpoint, either another key as in the path, or a predicate, function, etc.
|
18
|
+
attr_reader :endpoint
|
19
|
+
|
20
|
+
# Create a new KeyPath.
|
21
|
+
# @param [Array, Object] path The intial path. Will be converted to an array if it isn't already.
|
22
|
+
# @param endpoint the endpoint of the KeyPath
|
23
|
+
# @param [Boolean] absolute If the KeyPath should start from the base
|
24
|
+
# or remain relative to whatever location it's found.
|
12
25
|
def initialize(path, endpoint, absolute = false)
|
13
26
|
@path, @endpoint = path, endpoint
|
14
27
|
@path = [@path] unless Array === @path
|
@@ -16,10 +29,14 @@ module Squeel
|
|
16
29
|
@absolute = absolute
|
17
30
|
end
|
18
31
|
|
32
|
+
# Whether or not the KeyPath should be interpreted relative to its current location
|
33
|
+
# (if nested in a Hash, for instance) or as though it's at the base.
|
34
|
+
# @return [Boolean] The flag's value
|
19
35
|
def absolute?
|
20
36
|
@absolute
|
21
37
|
end
|
22
38
|
|
39
|
+
# Object comparison
|
23
40
|
def eql?(other)
|
24
41
|
self.class == other.class &&
|
25
42
|
self.path == other.path &&
|
@@ -27,38 +44,72 @@ module Squeel
|
|
27
44
|
self.absolute? == other.absolute?
|
28
45
|
end
|
29
46
|
|
47
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
48
|
+
# responds to |
|
49
|
+
# @param other The right hand side of the operation
|
50
|
+
# @return [Or] An Or node with the KeyPath on the left side and the other object on the right.
|
30
51
|
def |(other)
|
31
52
|
endpoint.respond_to?(:|) ? super : no_method_error(:|)
|
32
53
|
end
|
33
54
|
|
55
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
56
|
+
# responds to &
|
57
|
+
# @param other The right hand side of the operation
|
58
|
+
# @return [And] An And node with the KeyPath on the left side and the other object on the right.
|
34
59
|
def &(other)
|
35
60
|
endpoint.respond_to?(:&) ? super : no_method_error(:&)
|
36
61
|
end
|
37
62
|
|
63
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
64
|
+
# responds to -@
|
65
|
+
# @return [Not] A not node with the KeyPath as its expression
|
38
66
|
def -@
|
39
67
|
endpoint.respond_to?(:-@) ? super : no_method_error(:-@)
|
40
68
|
end
|
41
69
|
|
70
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
71
|
+
# responds to +
|
72
|
+
# @param other The right hand side of the operation
|
73
|
+
# @return [Operation] An operation (with operator +) with the KeyPath on its left and the other object on the right.
|
42
74
|
def +(other)
|
43
75
|
endpoint.respond_to?(:+) ? super : no_method_error(:+)
|
44
76
|
end
|
45
77
|
|
78
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
79
|
+
# responds to -
|
80
|
+
# @param other The right hand side of the operation
|
81
|
+
# @return [Operation] An operation (with operator -) with the KeyPath on its left and the other object on the right.
|
46
82
|
def -(other)
|
47
83
|
endpoint.respond_to?(:-) ? super : no_method_error(:-)
|
48
84
|
end
|
49
85
|
|
86
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
87
|
+
# responds to *
|
88
|
+
# @param other The right hand side of the operation
|
89
|
+
# @return [Operation] An operation (with operator *) with the KeyPath on its left and the other object on the right.
|
50
90
|
def *(other)
|
51
91
|
endpoint.respond_to?(:*) ? super : no_method_error(:*)
|
52
92
|
end
|
53
93
|
|
94
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
95
|
+
# responds to /
|
96
|
+
# @param other The right hand side of the operation
|
97
|
+
# @return [Operation] An operation (with operator /) with the KeyPath on its left and the other object on the right.
|
54
98
|
def /(other)
|
55
99
|
endpoint.respond_to?(:/) ? super : no_method_error(:/)
|
56
100
|
end
|
57
101
|
|
102
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
103
|
+
# responds to #op
|
104
|
+
# @param [String, Symbol] operator The custom operator
|
105
|
+
# @param other The right hand side of the operation
|
106
|
+
# @return [Operation] An operation with the given custom operator, the KeyPath on its left and the other object on the right.
|
58
107
|
def op(operator, other)
|
59
108
|
endpoint.respond_to?(:op) ? super : no_method_error(:/)
|
60
109
|
end
|
61
110
|
|
111
|
+
# Set the absolute flag on this KeyPath
|
112
|
+
# @return [KeyPath] This keypath, with its absolute flag set to true
|
62
113
|
def ~
|
63
114
|
@absolute = true
|
64
115
|
self
|
@@ -69,18 +120,26 @@ module Squeel
|
|
69
120
|
undef_method operator
|
70
121
|
end
|
71
122
|
|
123
|
+
# For use with equality tests
|
72
124
|
def hash
|
73
125
|
[self.class, endpoint, *path].hash
|
74
126
|
end
|
75
127
|
|
128
|
+
# expand_hash_conditions_for_aggregates assumes our hash keys can be
|
129
|
+
# converted to symbols, so this has to be implemented, but it doesn't
|
130
|
+
# really have to do anything useful.
|
131
|
+
# @return [NilClass] Just to avoid bombing out on expand_hash_conditions_for_aggregates
|
76
132
|
def to_sym
|
77
133
|
nil
|
78
134
|
end
|
79
135
|
|
136
|
+
# Delegate % to the KeyPath's endpoint, with a bit of special logic for stubs or functions.
|
137
|
+
# @param val The value to be supplied to the created/existing predicate
|
138
|
+
# @return [KeyPath] This KeyPath, with a predicate endpoint containing the given value
|
80
139
|
def %(val)
|
81
140
|
case endpoint
|
82
141
|
when Stub, Function
|
83
|
-
eq(val)
|
142
|
+
Array === val ? self.in(val) : self.eq(val)
|
84
143
|
self
|
85
144
|
else
|
86
145
|
endpoint % val
|
@@ -88,14 +147,19 @@ module Squeel
|
|
88
147
|
end
|
89
148
|
end
|
90
149
|
|
150
|
+
# @return [Array] The KeyPath's path, including its endpoint, as a single array.
|
91
151
|
def path_with_endpoint
|
92
152
|
path + [endpoint]
|
93
153
|
end
|
94
154
|
|
155
|
+
# Impleneted (and aliased to :to_str) to play nicely with ActiveRecord grouped calculations
|
95
156
|
def to_s
|
96
157
|
path.map(&:to_s).join('.') << ".#{endpoint}"
|
97
158
|
end
|
159
|
+
alias :to_str :to_s
|
98
160
|
|
161
|
+
# Appends to the KeyPath or delegates to the endpoint, as appropriate
|
162
|
+
# @return [KeyPath] The updated KeyPath
|
99
163
|
def method_missing(method_id, *args)
|
100
164
|
super if method_id == :to_ary
|
101
165
|
if endpoint.respond_to? method_id
|
@@ -118,6 +182,8 @@ module Squeel
|
|
118
182
|
|
119
183
|
private
|
120
184
|
|
185
|
+
# Raises a NoMethodError manually, bypassing #method_missing.
|
186
|
+
# Used by special-case operator overrides.
|
121
187
|
def no_method_error(method_id)
|
122
188
|
raise NoMethodError, "undefined method `#{method_id}' for #{self}:#{self.class}"
|
123
189
|
end
|
data/lib/squeel/nodes/nary.rb
CHANGED
@@ -1,33 +1,43 @@
|
|
1
1
|
module Squeel
|
2
2
|
module Nodes
|
3
|
+
# A node containing multiple children. Just the And node for now.
|
3
4
|
class Nary
|
4
5
|
include PredicateOperators
|
5
6
|
|
7
|
+
# @return [Array] This node's children
|
6
8
|
attr_reader :children
|
7
9
|
|
10
|
+
# Creates a new Nary node with the given array of children
|
11
|
+
# @param [Array] children The node's children
|
8
12
|
def initialize(children)
|
9
13
|
raise ArgumentError, '#{self.class} requires an array' unless Array === children
|
10
14
|
# We don't dup here, as incoming arrays should be created by the
|
11
|
-
#
|
15
|
+
# PredicateOperators#& method on other nodes. If you're creating And nodes
|
12
16
|
# manually, by sure that they're new arays.
|
13
17
|
@children = children
|
14
18
|
end
|
15
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.
|
16
23
|
def &(other)
|
17
24
|
@children << other
|
18
25
|
self
|
19
26
|
end
|
20
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
|
21
31
|
def -(other)
|
22
32
|
@children << Not.new(other)
|
23
33
|
self
|
24
34
|
end
|
25
35
|
|
36
|
+
# Object comparison
|
26
37
|
def eql?(other)
|
27
38
|
self.class == other.class &&
|
28
39
|
self.children == other.children
|
29
40
|
end
|
30
|
-
|
31
41
|
alias :== :eql?
|
32
42
|
|
33
43
|
end
|
data/lib/squeel/nodes/not.rb
CHANGED
@@ -2,18 +2,27 @@ require 'squeel/nodes/function'
|
|
2
2
|
|
3
3
|
module Squeel
|
4
4
|
module Nodes
|
5
|
+
# An Operation is basically a function with its operands limited, and rearranged,
|
6
|
+
# so Squeel implements it as such.
|
5
7
|
class Operation < Function
|
6
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
|
7
13
|
def initialize(left, operator, right)
|
8
14
|
super(operator, [left, right])
|
9
15
|
end
|
10
16
|
|
17
|
+
# An operation should probably call its "function" name an "operator", shouldn't it?
|
11
18
|
alias :operator :name
|
12
19
|
|
20
|
+
# @return The left operand
|
13
21
|
def left
|
14
22
|
args[0]
|
15
23
|
end
|
16
24
|
|
25
|
+
# @return The right operand
|
17
26
|
def right
|
18
27
|
args[1]
|
19
28
|
end
|
@@ -1,23 +1,39 @@
|
|
1
1
|
module Squeel
|
2
2
|
module Nodes
|
3
|
+
# Module containing Operation factory methods for inclusion in Squeel nodes
|
3
4
|
module Operators
|
4
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
|
5
9
|
def +(value)
|
6
10
|
Operation.new(self, :+, value)
|
7
11
|
end
|
8
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
|
9
16
|
def -(value)
|
10
17
|
Operation.new(self, :-, value)
|
11
18
|
end
|
12
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
|
13
23
|
def *(value)
|
14
24
|
Operation.new(self, :*, value)
|
15
25
|
end
|
16
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
|
17
30
|
def /(value)
|
18
31
|
Operation.new(self, :/, value)
|
19
32
|
end
|
20
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
|
21
37
|
def op(operator, value)
|
22
38
|
Operation.new(self, operator, value)
|
23
39
|
end
|
data/lib/squeel/nodes/or.rb
CHANGED
data/lib/squeel/nodes/order.rb
CHANGED
@@ -1,31 +1,49 @@
|
|
1
1
|
module Squeel
|
2
2
|
module Nodes
|
3
|
+
# A node that represents SQL orderings, such as "people.id DESC"
|
3
4
|
class Order
|
4
|
-
|
5
|
+
# @return The expression being ordered on. Might be an attribute, function, or operation
|
6
|
+
attr_reader :expr
|
5
7
|
|
8
|
+
# @return [Fixnum] 1 or -1, depending on ascending or descending direction, respectively
|
9
|
+
attr_reader :direction
|
10
|
+
|
11
|
+
# Create a new Order node with the given expression and direction
|
12
|
+
# @param expr The expression to order on
|
13
|
+
# @param [Fixnum] direction 1 or -1, depending on the desired sort direction
|
6
14
|
def initialize(expr, direction = 1)
|
7
15
|
raise ArgumentError, "Direction #{direction} is not valid. Must be -1 or 1." unless [-1,1].include? direction
|
8
16
|
@expr, @direction = expr, direction
|
9
17
|
end
|
10
18
|
|
19
|
+
# Set this node's direction to ascending
|
20
|
+
# @return [Order] This order node with an ascending direction
|
11
21
|
def asc
|
12
22
|
@direction = 1
|
13
23
|
self
|
14
24
|
end
|
15
25
|
|
26
|
+
# Set this node's direction to descending
|
27
|
+
# @return [Order] This order node with a descending direction
|
16
28
|
def desc
|
17
29
|
@direction = -1
|
18
30
|
self
|
19
31
|
end
|
20
32
|
|
33
|
+
# Whether or not this node represents an ascending order
|
34
|
+
# @return [Boolean] True if the order is ascending
|
21
35
|
def ascending?
|
22
36
|
@direction == 1
|
23
37
|
end
|
24
38
|
|
39
|
+
# Whether or not this node represents a descending order
|
40
|
+
# @return [Boolean] True if the order is descending
|
25
41
|
def descending?
|
26
42
|
@direction == -1
|
27
43
|
end
|
28
44
|
|
45
|
+
# Reverse the node's direction
|
46
|
+
# @return [Order] This node with a reversed direction
|
29
47
|
def reverse!
|
30
48
|
@direction = - @direction
|
31
49
|
self
|