squeel 0.5.5 → 0.6.0
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.
- 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
|