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.
- data/.gitignore +4 -0
- data/.yardopts +3 -0
- data/Gemfile +13 -0
- data/LICENSE +20 -0
- data/README.md +398 -0
- data/Rakefile +19 -0
- data/lib/core_ext/hash.rb +13 -0
- data/lib/core_ext/symbol.rb +39 -0
- data/lib/squeel/adapters/active_record/3.0/association_preload.rb +15 -0
- data/lib/squeel/adapters/active_record/3.0/compat.rb +142 -0
- data/lib/squeel/adapters/active_record/3.0/context.rb +66 -0
- data/lib/squeel/adapters/active_record/3.0/join_association.rb +54 -0
- data/lib/squeel/adapters/active_record/3.0/join_dependency.rb +84 -0
- data/lib/squeel/adapters/active_record/3.0/relation.rb +327 -0
- data/lib/squeel/adapters/active_record/context.rb +66 -0
- data/lib/squeel/adapters/active_record/join_association.rb +44 -0
- data/lib/squeel/adapters/active_record/join_dependency.rb +83 -0
- data/lib/squeel/adapters/active_record/preloader.rb +21 -0
- data/lib/squeel/adapters/active_record/relation.rb +351 -0
- data/lib/squeel/adapters/active_record.rb +28 -0
- data/lib/squeel/configuration.rb +54 -0
- data/lib/squeel/constants.rb +24 -0
- data/lib/squeel/context.rb +67 -0
- data/lib/squeel/dsl.rb +86 -0
- data/lib/squeel/nodes/aliasing.rb +13 -0
- data/lib/squeel/nodes/and.rb +9 -0
- data/lib/squeel/nodes/as.rb +14 -0
- data/lib/squeel/nodes/binary.rb +32 -0
- data/lib/squeel/nodes/function.rb +66 -0
- data/lib/squeel/nodes/join.rb +113 -0
- data/lib/squeel/nodes/key_path.rb +192 -0
- data/lib/squeel/nodes/nary.rb +45 -0
- data/lib/squeel/nodes/not.rb +9 -0
- data/lib/squeel/nodes/operation.rb +32 -0
- data/lib/squeel/nodes/operators.rb +43 -0
- data/lib/squeel/nodes/or.rb +9 -0
- data/lib/squeel/nodes/order.rb +53 -0
- data/lib/squeel/nodes/predicate.rb +71 -0
- data/lib/squeel/nodes/predicate_operators.rb +29 -0
- data/lib/squeel/nodes/stub.rb +125 -0
- data/lib/squeel/nodes/unary.rb +28 -0
- data/lib/squeel/nodes.rb +17 -0
- data/lib/squeel/predicate_methods.rb +14 -0
- data/lib/squeel/version.rb +3 -0
- data/lib/squeel/visitors/attribute_visitor.rb +191 -0
- data/lib/squeel/visitors/base.rb +112 -0
- data/lib/squeel/visitors/predicate_visitor.rb +319 -0
- data/lib/squeel/visitors/symbol_visitor.rb +48 -0
- data/lib/squeel/visitors.rb +3 -0
- data/lib/squeel.rb +28 -0
- data/lib/squeel_rbg.rb +5 -0
- data/spec/blueprints/articles.rb +5 -0
- data/spec/blueprints/comments.rb +5 -0
- data/spec/blueprints/notes.rb +3 -0
- data/spec/blueprints/people.rb +4 -0
- data/spec/blueprints/tags.rb +3 -0
- data/spec/console.rb +22 -0
- data/spec/core_ext/symbol_spec.rb +75 -0
- data/spec/helpers/squeel_helper.rb +21 -0
- data/spec/spec_helper.rb +66 -0
- data/spec/squeel/adapters/active_record/context_spec.rb +44 -0
- data/spec/squeel/adapters/active_record/join_association_spec.rb +18 -0
- data/spec/squeel/adapters/active_record/join_dependency_spec.rb +66 -0
- data/spec/squeel/adapters/active_record/relation_spec.rb +627 -0
- data/spec/squeel/dsl_spec.rb +92 -0
- data/spec/squeel/nodes/function_spec.rb +149 -0
- data/spec/squeel/nodes/join_spec.rb +47 -0
- data/spec/squeel/nodes/key_path_spec.rb +100 -0
- data/spec/squeel/nodes/operation_spec.rb +149 -0
- data/spec/squeel/nodes/operators_spec.rb +87 -0
- data/spec/squeel/nodes/order_spec.rb +30 -0
- data/spec/squeel/nodes/predicate_operators_spec.rb +88 -0
- data/spec/squeel/nodes/predicate_spec.rb +50 -0
- data/spec/squeel/nodes/stub_spec.rb +198 -0
- data/spec/squeel/visitors/attribute_visitor_spec.rb +142 -0
- data/spec/squeel/visitors/predicate_visitor_spec.rb +342 -0
- data/spec/squeel/visitors/symbol_visitor_spec.rb +42 -0
- data/spec/support/schema.rb +104 -0
- data/squeel.gemspec +43 -0
- metadata +246 -0
@@ -0,0 +1,319 @@
|
|
1
|
+
require 'squeel/visitors/base'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Visitors
|
5
|
+
class PredicateVisitor < Base
|
6
|
+
|
7
|
+
TRUE_SQL = Arel.sql('1=1').freeze
|
8
|
+
FALSE_SQL = Arel.sql('1=0').freeze
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# Visit a Hash. This entails iterating through each key and value and
|
13
|
+
# visiting each value in turn.
|
14
|
+
#
|
15
|
+
# @param [Hash] o The Hash to visit
|
16
|
+
# @param parent The current parent object in the context
|
17
|
+
# @return [Array] An array of values for use in a where or having clause
|
18
|
+
def visit_Hash(o, parent)
|
19
|
+
predicates = o.map do |k, v|
|
20
|
+
if implies_context_change?(v)
|
21
|
+
visit_with_context_change(k, v, parent)
|
22
|
+
else
|
23
|
+
visit_without_context_change(k, v, parent)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
predicates.flatten!
|
28
|
+
|
29
|
+
if predicates.size > 1
|
30
|
+
Arel::Nodes::Grouping.new(Arel::Nodes::And.new predicates)
|
31
|
+
else
|
32
|
+
predicates.first
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Visit an array, which involves accepting any values we know how to
|
37
|
+
# accept, and skipping the rest.
|
38
|
+
#
|
39
|
+
# @param [Array] o The Array to visit
|
40
|
+
# @param parent The current parent object in the context
|
41
|
+
# @return [Array] The visited array
|
42
|
+
def visit_Array(o, parent)
|
43
|
+
o.map { |v| can_accept?(v) ? accept(v, parent) : v }.flatten
|
44
|
+
end
|
45
|
+
|
46
|
+
# Visit ActiveRecord::Base objects. These should be converted to their
|
47
|
+
# id before being used in a comparison.
|
48
|
+
#
|
49
|
+
# @param [ActiveRecord::Base] o The AR::Base object to visit
|
50
|
+
# @param parent The current parent object in the context
|
51
|
+
# @return [Fixnum] The id of the object
|
52
|
+
def visit_ActiveRecord_Base(o, parent)
|
53
|
+
o.id
|
54
|
+
end
|
55
|
+
|
56
|
+
# Visit a KeyPath by traversing the path and then visiting the endpoint.
|
57
|
+
#
|
58
|
+
# @param [Nodes::KeyPath] o The KeyPath to visit
|
59
|
+
# @param parent The parent object in the context
|
60
|
+
# @return The visited endpoint, in the context of the KeyPath's path
|
61
|
+
def visit_Squeel_Nodes_KeyPath(o, parent)
|
62
|
+
parent = traverse(o, parent)
|
63
|
+
|
64
|
+
accept(o.endpoint, parent)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Visit a Stub by converting it to an ARel attribute
|
68
|
+
#
|
69
|
+
# @param [Nodes::Stub] o The Stub to visit
|
70
|
+
# @param parent The parent object in the context
|
71
|
+
# @return [Arel::Attribute] An attribute of the parent table with the
|
72
|
+
# Stub's column
|
73
|
+
def visit_Squeel_Nodes_Stub(o, parent)
|
74
|
+
contextualize(parent)[o.symbol]
|
75
|
+
end
|
76
|
+
|
77
|
+
# Visit a Squeel predicate, converting it into an ARel predicate
|
78
|
+
#
|
79
|
+
# @param [Nodes::Predicate] o The predicate to visit
|
80
|
+
# @param parent The parent object in the context
|
81
|
+
# @return An ARel predicate node
|
82
|
+
# (Arel::Nodes::Equality, Arel::Nodes::Matches, etc)
|
83
|
+
def visit_Squeel_Nodes_Predicate(o, parent)
|
84
|
+
value = o.value
|
85
|
+
|
86
|
+
if Array === value && value.empty? && [:in, :not_in].include?(o.method_name)
|
87
|
+
# Special case, in/not_in with empty arrays should be false/true respectively
|
88
|
+
return o.method_name == :in ? FALSE_SQL : TRUE_SQL
|
89
|
+
end
|
90
|
+
|
91
|
+
if Nodes::KeyPath === value
|
92
|
+
value = can_accept?(value.endpoint) ? accept(value, parent) : contextualize(traverse(value, parent))[value.endpoint.to_sym]
|
93
|
+
else
|
94
|
+
value = accept(value, parent) if can_accept?(value)
|
95
|
+
end
|
96
|
+
|
97
|
+
case o.expr
|
98
|
+
when Nodes::Stub
|
99
|
+
accept(o.expr, parent).send(o.method_name, value)
|
100
|
+
when Nodes::Function
|
101
|
+
accept(o.expr, parent).send(o.method_name, quote(value))
|
102
|
+
else
|
103
|
+
contextualize(parent)[o.expr].send(o.method_name, value)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Visit a Squeel function, returning an ARel NamedFunction node.
|
108
|
+
#
|
109
|
+
# @param [Nodes::Function] o The function node to visit
|
110
|
+
# @param parent The parent object in the context
|
111
|
+
# @return [Arel::Nodes::NamedFunction] A named function node. Function
|
112
|
+
# arguments are visited, if necessary, before being passed to the NamedFunction.
|
113
|
+
def visit_Squeel_Nodes_Function(o, parent)
|
114
|
+
args = o.args.map do |arg|
|
115
|
+
case arg
|
116
|
+
when Nodes::Function
|
117
|
+
accept(arg, parent)
|
118
|
+
when ActiveRecord::Relation
|
119
|
+
arg.arel.ast
|
120
|
+
when Nodes::KeyPath
|
121
|
+
can_accept?(arg.endpoint) ? accept(arg, parent) : contextualize(traverse(arg, parent))[arg.endpoint.to_sym]
|
122
|
+
when Symbol, Nodes::Stub
|
123
|
+
Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
|
124
|
+
else
|
125
|
+
quote arg
|
126
|
+
end
|
127
|
+
end
|
128
|
+
Arel::Nodes::NamedFunction.new(o.name, args, o.alias)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Visit an ActiveRecord Relation, returning an Arel::SelectManager
|
132
|
+
# @param [ActiveRecord::Relation] o The Relation to visit
|
133
|
+
# @param parent The parent object in the context
|
134
|
+
# @return [Arel::SelectManager] The ARel select manager that represents
|
135
|
+
# the relation's query
|
136
|
+
def visit_ActiveRecord_Relation(o, parent)
|
137
|
+
o.arel
|
138
|
+
end
|
139
|
+
|
140
|
+
# Visit a Squeel operation node, convering it to an ARel InfixOperation
|
141
|
+
# (or subclass, as appropriate)
|
142
|
+
#
|
143
|
+
# @param [Nodes::Operation] o The Operation node to visit
|
144
|
+
# @param parent The parent object in the context
|
145
|
+
# @return [Arel::Nodes::InfixOperation] The InfixOperation (or Addition,
|
146
|
+
# Multiplication, etc) node, with both operands visited, if needed.
|
147
|
+
def visit_Squeel_Nodes_Operation(o, parent)
|
148
|
+
args = o.args.map do |arg|
|
149
|
+
case arg
|
150
|
+
when Nodes::Function
|
151
|
+
accept(arg, parent)
|
152
|
+
when Nodes::KeyPath
|
153
|
+
can_accept?(arg.endpoint) ? accept(arg, parent) : contextualize(traverse(arg, parent))[arg.endpoint.to_sym]
|
154
|
+
when Symbol, Nodes::Stub
|
155
|
+
Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
|
156
|
+
else
|
157
|
+
quote arg
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
op = case o.operator
|
162
|
+
when :+
|
163
|
+
Arel::Nodes::Addition.new(args[0], args[1])
|
164
|
+
when :-
|
165
|
+
Arel::Nodes::Subtraction.new(args[0], args[1])
|
166
|
+
when :*
|
167
|
+
Arel::Nodes::Multiplication.new(args[0], args[1])
|
168
|
+
when :/
|
169
|
+
Arel::Nodes::Division.new(args[0], args[1])
|
170
|
+
else
|
171
|
+
Arel::Nodes::InfixOperation(o.operator, args[0], args[1])
|
172
|
+
end
|
173
|
+
o.alias ? op.as(o.alias) : op
|
174
|
+
end
|
175
|
+
|
176
|
+
# Visit a Squeel And node, returning an ARel Grouping containing an
|
177
|
+
# ARel And node.
|
178
|
+
#
|
179
|
+
# @param [Nodes::And] o The And node to visit
|
180
|
+
# @param parent The parent object in the context
|
181
|
+
# @return [Arel::Nodes::Grouping] A grouping node, containnig an ARel
|
182
|
+
# And node as its expression. All children will be visited before
|
183
|
+
# being passed to the And.
|
184
|
+
def visit_Squeel_Nodes_And(o, parent)
|
185
|
+
Arel::Nodes::Grouping.new(Arel::Nodes::And.new(accept(o.children, parent)))
|
186
|
+
end
|
187
|
+
|
188
|
+
# Visit a Squeel Or node, returning an ARel Or node.
|
189
|
+
#
|
190
|
+
# @param [Nodes::Or] o The Or node to visit
|
191
|
+
# @param parent The parent object in the context
|
192
|
+
# @return [Arel::Nodes::Or] An ARel Or node, with left and right sides visited
|
193
|
+
def visit_Squeel_Nodes_Or(o, parent)
|
194
|
+
accept(o.left, parent).or(accept(o.right, parent))
|
195
|
+
end
|
196
|
+
|
197
|
+
def visit_Squeel_Nodes_Not(o, parent)
|
198
|
+
accept(o.expr, parent).not
|
199
|
+
end
|
200
|
+
|
201
|
+
# @return [Boolean] Whether the given value implies a context change
|
202
|
+
# @param v The value to consider
|
203
|
+
def implies_context_change?(v)
|
204
|
+
case v
|
205
|
+
when Hash, Nodes::Predicate, Nodes::Unary, Nodes::Binary, Nodes::Nary
|
206
|
+
true
|
207
|
+
when Nodes::KeyPath
|
208
|
+
can_accept?(v.endpoint) && !(Nodes::Stub === v.endpoint)
|
209
|
+
else
|
210
|
+
false
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Change context (by setting the new parent to the result of a #find or
|
215
|
+
# #traverse on the key), then accept the given value.
|
216
|
+
#
|
217
|
+
# @param k The hash key
|
218
|
+
# @param v The hash value
|
219
|
+
# @param parent The current parent object in the context
|
220
|
+
# @return The visited value
|
221
|
+
def visit_with_context_change(k, v, parent)
|
222
|
+
parent = case k
|
223
|
+
when Nodes::KeyPath
|
224
|
+
traverse(k, parent, true)
|
225
|
+
else
|
226
|
+
find(k, parent)
|
227
|
+
end
|
228
|
+
|
229
|
+
case v
|
230
|
+
when Hash, Nodes::KeyPath, Nodes::Predicate, Nodes::Unary, Nodes::Binary, Nodes::Nary
|
231
|
+
accept(v, parent || k)
|
232
|
+
when Array
|
233
|
+
v.map {|val| accept(val, parent || k)}
|
234
|
+
else
|
235
|
+
raise ArgumentError, <<-END
|
236
|
+
Hashes, Predicates, and arrays of visitables as values imply that their
|
237
|
+
corresponding keys are a parent. This didn't work out so well in the case
|
238
|
+
of key = #{k} and value = #{v}"
|
239
|
+
END
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Create a predicate for a given key/value pair. If the value is
|
244
|
+
# a Symbol, Stub, or KeyPath, it's converted to a table.column for
|
245
|
+
# the predicate value.
|
246
|
+
#
|
247
|
+
# @param k The hash key
|
248
|
+
# @param v The hash value
|
249
|
+
# @param parent The current parent object in the context
|
250
|
+
# @return An ARel predicate
|
251
|
+
def visit_without_context_change(k, v, parent)
|
252
|
+
case v
|
253
|
+
when Nodes::Stub, Symbol
|
254
|
+
v = contextualize(parent)[v.to_sym]
|
255
|
+
when Nodes::KeyPath # If we could accept the endpoint, we wouldn't be here
|
256
|
+
v = contextualize(traverse(v, parent))[v.endpoint.to_sym]
|
257
|
+
end
|
258
|
+
|
259
|
+
case k
|
260
|
+
when Nodes::Predicate
|
261
|
+
accept(k % quote_for_node(k.expr, v), parent)
|
262
|
+
when Nodes::Function
|
263
|
+
arel_predicate_for(accept(k, parent), quote(v), parent)
|
264
|
+
when Nodes::KeyPath
|
265
|
+
accept(k % quote_for_node(k.endpoint, v), parent)
|
266
|
+
else
|
267
|
+
attr_name = k.to_s
|
268
|
+
attribute = if attr_name.include?('.')
|
269
|
+
table_name, attr_name = attr_name.split(/\./, 2)
|
270
|
+
Arel::Table.new(table_name.to_sym, :engine => engine)[attr_name.to_sym]
|
271
|
+
else
|
272
|
+
contextualize(parent)[k.to_sym]
|
273
|
+
end
|
274
|
+
arel_predicate_for(attribute, v, parent)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Determine whether to use IN or equality testing for a predicate,
|
279
|
+
# based on its value class, then return the appropriate predicate.
|
280
|
+
#
|
281
|
+
# @param attribute The ARel attribute (or function/operation) the
|
282
|
+
# predicate will be created for
|
283
|
+
# @param value The value to be compared against
|
284
|
+
# @return [Arel::Nodes::Node] An ARel predicate node
|
285
|
+
def arel_predicate_for(attribute, value, parent)
|
286
|
+
value = can_accept?(value) ? accept(value, parent) : value
|
287
|
+
if [Array, Range, Arel::SelectManager].include?(value.class)
|
288
|
+
if Array === value && value.empty?
|
289
|
+
FALSE_SQL
|
290
|
+
else
|
291
|
+
attribute.in(value)
|
292
|
+
end
|
293
|
+
else
|
294
|
+
attribute.eq(value)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# Function nodes require us to do the quoting before the ARel
|
299
|
+
# visitor gets a chance to try, because we want to avoid having our
|
300
|
+
# values quoted as a type of the last visited column. Otherwise, we
|
301
|
+
# can end up with annoyances like having "joe" quoted to 0, if the
|
302
|
+
# last visited column was of an integer type.
|
303
|
+
#
|
304
|
+
# @param node The node we (might) be quoting for
|
305
|
+
# @param v The value to (possibly) quote
|
306
|
+
def quote_for_node(node, v)
|
307
|
+
case node
|
308
|
+
when Nodes::Function
|
309
|
+
quote(v)
|
310
|
+
when Nodes::Predicate
|
311
|
+
Nodes::Function === node.expr ? quote(v) : v
|
312
|
+
else
|
313
|
+
v
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'squeel/visitors/base'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Visitors
|
5
|
+
class SymbolVisitor < Base
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
end
|
9
|
+
|
10
|
+
def accept(object, parent = nil)
|
11
|
+
visit(object, parent)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def visit_Array(o, parent)
|
17
|
+
o.map {|e| accept(e, parent)}.flatten
|
18
|
+
end
|
19
|
+
|
20
|
+
def visit_Hash(o, parent)
|
21
|
+
{}.tap do |hash|
|
22
|
+
o.each do |key, value|
|
23
|
+
hash[accept(key, parent)] = accept(value, parent)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def visit_Symbol(o, parent)
|
29
|
+
o
|
30
|
+
end
|
31
|
+
|
32
|
+
def visit_Squeel_Nodes_Stub(o, parent)
|
33
|
+
o.symbol
|
34
|
+
end
|
35
|
+
|
36
|
+
def visit_Squeel_Nodes_KeyPath(o, parent)
|
37
|
+
o.path_with_endpoint.reverse.map(&:to_sym).inject do |hash, key|
|
38
|
+
{key => hash}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def visit_Squeel_Nodes_Join(o, parent)
|
43
|
+
o._name
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/squeel.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'squeel/configuration'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
|
5
|
+
extend Configuration
|
6
|
+
|
7
|
+
# Prevent warnings on the console when doing things some might describe as "evil"
|
8
|
+
def self.evil_things
|
9
|
+
original_verbosity = $VERBOSE
|
10
|
+
$VERBOSE = nil
|
11
|
+
yield
|
12
|
+
ensure
|
13
|
+
$VERBOSE = original_verbosity
|
14
|
+
end
|
15
|
+
|
16
|
+
# Set up initial predicate aliases
|
17
|
+
Constants::PREDICATE_ALIASES.each do |original, aliases|
|
18
|
+
aliases.each do |aliaz|
|
19
|
+
alias_predicate aliaz, original
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'squeel/nodes'
|
26
|
+
require 'squeel/dsl'
|
27
|
+
require 'squeel/visitors'
|
28
|
+
require 'squeel/adapters/active_record'
|
data/spec/console.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Bundler.setup
|
2
|
+
require 'machinist/active_record'
|
3
|
+
require 'sham'
|
4
|
+
require 'faker'
|
5
|
+
|
6
|
+
Dir[File.expand_path('../../spec/{helpers,support,blueprints}/*.rb', __FILE__)].each do |f|
|
7
|
+
require f
|
8
|
+
end
|
9
|
+
|
10
|
+
Sham.define do
|
11
|
+
name { Faker::Name.name }
|
12
|
+
title { Faker::Lorem.sentence }
|
13
|
+
body { Faker::Lorem.paragraph }
|
14
|
+
salary {|index| 30000 + (index * 1000)}
|
15
|
+
tag_name { Faker::Lorem.words(3).join(' ') }
|
16
|
+
note { Faker::Lorem.words(7).join(' ') }
|
17
|
+
end
|
18
|
+
|
19
|
+
Schema.create
|
20
|
+
|
21
|
+
require 'squeel'
|
22
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Symbol do
|
4
|
+
|
5
|
+
Squeel::Constants::PREDICATES.each do |method_name|
|
6
|
+
it "creates #{method_name} predicates with no value" do
|
7
|
+
predicate = :attribute.send(method_name)
|
8
|
+
predicate.expr.should eq :attribute
|
9
|
+
predicate.method_name.should eq method_name
|
10
|
+
predicate.value?.should be_false
|
11
|
+
end
|
12
|
+
|
13
|
+
it "creates #{method_name} predicates with a value" do
|
14
|
+
predicate = :attribute.send(method_name, 'value')
|
15
|
+
predicate.expr.should eq :attribute
|
16
|
+
predicate.method_name.should eq method_name
|
17
|
+
predicate.value.should eq 'value'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Squeel::Constants::PREDICATE_ALIASES.each do |method_name, aliases|
|
22
|
+
aliases.each do |aliaz|
|
23
|
+
['', '_any', '_all'].each do |suffix|
|
24
|
+
it "creates #{method_name.to_s + suffix} predicates with no value using the alias #{aliaz.to_s + suffix}" do
|
25
|
+
predicate = :attribute.send(aliaz.to_s + suffix)
|
26
|
+
predicate.expr.should eq :attribute
|
27
|
+
predicate.method_name.should eq "#{method_name}#{suffix}".to_sym
|
28
|
+
predicate.value?.should be_false
|
29
|
+
end
|
30
|
+
|
31
|
+
it "creates #{method_name.to_s + suffix} predicates with a value using the alias #{aliaz.to_s + suffix}" do
|
32
|
+
predicate = :attribute.send((aliaz.to_s + suffix), 'value')
|
33
|
+
predicate.expr.should eq :attribute
|
34
|
+
predicate.method_name.should eq "#{method_name}#{suffix}".to_sym
|
35
|
+
predicate.value.should eq 'value'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'creates ascending orders' do
|
42
|
+
order = :attribute.asc
|
43
|
+
order.should be_ascending
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'creates descending orders' do
|
47
|
+
order = :attribute.desc
|
48
|
+
order.should be_descending
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'creates functions' do
|
52
|
+
function = :function.func
|
53
|
+
function.should be_a Squeel::Nodes::Function
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'creates inner joins' do
|
57
|
+
join = :join.inner
|
58
|
+
join.should be_a Squeel::Nodes::Join
|
59
|
+
join._type.should eq Arel::InnerJoin
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'creates outer joins' do
|
63
|
+
join = :join.outer
|
64
|
+
join.should be_a Squeel::Nodes::Join
|
65
|
+
join._type.should eq Arel::OuterJoin
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'creates as nodes' do
|
69
|
+
as = :column.as('other_name')
|
70
|
+
as.should be_a Squeel::Nodes::As
|
71
|
+
as.left.should eq :column
|
72
|
+
as.right.should eq 'other_name'
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SqueelHelper
|
2
|
+
def dsl(&block)
|
3
|
+
Squeel::DSL.eval(&block)
|
4
|
+
end
|
5
|
+
|
6
|
+
def queries_for
|
7
|
+
$queries_executed = []
|
8
|
+
yield
|
9
|
+
$queries_executed
|
10
|
+
ensure
|
11
|
+
%w{ BEGIN COMMIT }.each { |x| $queries_executed.delete(x) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def new_join_dependency(*args)
|
15
|
+
if defined?(ActiveRecord::Associations::JoinDependency)
|
16
|
+
ActiveRecord::Associations::JoinDependency.new(*args)
|
17
|
+
else
|
18
|
+
ActiveRecord::Associations::ClassMethods::JoinDependency.new(*args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'machinist/active_record'
|
2
|
+
require 'sham'
|
3
|
+
require 'faker'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
class SQLCounter
|
7
|
+
IGNORED_SQL = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/,
|
8
|
+
/SELECT name\s+FROM sqlite_master\s+WHERE type = 'table' AND NOT name = 'sqlite_sequence'/]
|
9
|
+
|
10
|
+
# FIXME: this needs to be refactored so specific database can add their own
|
11
|
+
# ignored SQL. This ignored SQL is for Oracle.
|
12
|
+
IGNORED_SQL.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
$queries_executed = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(name, start, finish, message_id, values)
|
19
|
+
sql = values[:sql]
|
20
|
+
|
21
|
+
unless 'CACHE' == values[:name]
|
22
|
+
$queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
|
27
|
+
end
|
28
|
+
|
29
|
+
Dir[File.expand_path('../{helpers,support,blueprints}/*.rb', __FILE__)].each do |f|
|
30
|
+
require f
|
31
|
+
end
|
32
|
+
|
33
|
+
Sham.define do
|
34
|
+
name { Faker::Name.name }
|
35
|
+
title { Faker::Lorem.sentence }
|
36
|
+
body { Faker::Lorem.paragraph }
|
37
|
+
salary {|index| 30000 + (index * 1000)}
|
38
|
+
tag_name { Faker::Lorem.words(3).join(' ') }
|
39
|
+
note { Faker::Lorem.words(7).join(' ') }
|
40
|
+
end
|
41
|
+
|
42
|
+
RSpec.configure do |config|
|
43
|
+
config.before(:suite) do
|
44
|
+
puts '=' * 80
|
45
|
+
puts "Running specs against ActiveRecord #{ActiveRecord::VERSION::STRING} and ARel #{Arel::VERSION}..."
|
46
|
+
puts '=' * 80
|
47
|
+
Schema.create
|
48
|
+
end
|
49
|
+
config.before(:all) { Sham.reset(:before_all) }
|
50
|
+
config.before(:each) { Sham.reset(:before_each) }
|
51
|
+
|
52
|
+
config.include SqueelHelper
|
53
|
+
end
|
54
|
+
|
55
|
+
RSpec::Matchers.define :be_like do |expected|
|
56
|
+
match do |actual|
|
57
|
+
actual.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip ==
|
58
|
+
expected.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
require 'squeel'
|
63
|
+
|
64
|
+
Squeel.configure do |config|
|
65
|
+
config.load_core_extensions :hash, :symbol
|
66
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
describe Context do
|
7
|
+
before do
|
8
|
+
@jd = new_join_dependency(Person, {
|
9
|
+
:children => {
|
10
|
+
:children => {
|
11
|
+
:parent => :parent
|
12
|
+
}
|
13
|
+
}
|
14
|
+
}, [])
|
15
|
+
@c = Context.new(@jd)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'contextualizes join parts with the proper alias' do
|
19
|
+
table = @c.contextualize @jd.join_parts.last
|
20
|
+
table.table_alias.should eq 'parents_people_2'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'contextualizes symbols as a generic table' do
|
24
|
+
table = @c.contextualize :table
|
25
|
+
table.name.should eq 'table'
|
26
|
+
table.table_alias.should be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'contextualizes polymorphic Join nodes to the arel_table of their klass' do
|
30
|
+
table = @c.contextualize Nodes::Join.new(:notable, Arel::InnerJoin, Article)
|
31
|
+
table.name.should eq 'articles'
|
32
|
+
table.table_alias.should be_nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'contextualizes non-polymorphic Join nodes to the table for their name' do
|
36
|
+
table = @c.contextualize Nodes::Join.new(:notes, Arel::InnerJoin)
|
37
|
+
table.name.should eq 'notes'
|
38
|
+
table.table_alias.should be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Squeel
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
describe JoinAssociation do
|
5
|
+
before do
|
6
|
+
@jd = new_join_dependency(Note, {}, [])
|
7
|
+
@notable = Note.reflect_on_association(:notable)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'accepts a 4th parameter to set a polymorphic class' do
|
11
|
+
join_association = JoinAssociation.new(@notable, @jd, @jd.join_base, Article)
|
12
|
+
join_association.reflection.klass.should eq Article
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|