squeel 1.0.9 → 1.0.11
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/CHANGELOG.md +10 -0
- data/lib/squeel.rb +5 -0
- data/lib/squeel/adapters/active_record/3.0/association_preload_extensions.rb +2 -2
- data/lib/squeel/adapters/active_record/3.0/relation_extensions.rb +98 -46
- data/lib/squeel/adapters/active_record/3.1/preloader_extensions.rb +2 -2
- data/lib/squeel/adapters/active_record/3.1/relation_extensions.rb +71 -85
- data/lib/squeel/adapters/active_record/base_extensions.rb +0 -19
- data/lib/squeel/adapters/active_record/relation_extensions.rb +6 -9
- data/lib/squeel/nodes/binary.rb +4 -0
- data/lib/squeel/nodes/function.rb +10 -0
- data/lib/squeel/nodes/nary.rb +1 -1
- data/lib/squeel/nodes/order.rb +11 -1
- data/lib/squeel/nodes/predicate.rb +2 -2
- data/lib/squeel/nodes/stub.rb +2 -0
- data/lib/squeel/nodes/unary.rb +4 -0
- data/lib/squeel/version.rb +1 -1
- data/lib/squeel/visitors.rb +10 -3
- data/lib/squeel/visitors/from_visitor.rb +6 -0
- data/lib/squeel/visitors/group_visitor.rb +6 -0
- data/lib/squeel/visitors/having_visitor.rb +9 -0
- data/lib/squeel/visitors/order_visitor.rb +20 -0
- data/lib/squeel/visitors/predicate_visitation.rb +126 -0
- data/lib/squeel/visitors/predicate_visitor.rb +3 -326
- data/lib/squeel/visitors/{symbol_visitor.rb → preload_visitor.rb} +4 -4
- data/lib/squeel/visitors/select_visitor.rb +7 -0
- data/lib/squeel/visitors/visitor.rb +244 -12
- data/lib/squeel/visitors/where_visitor.rb +8 -0
- data/spec/helpers/squeel_helper.rb +14 -1
- data/spec/squeel/adapters/active_record/relation_extensions_spec.rb +65 -49
- data/spec/squeel/nodes/as_spec.rb +20 -0
- data/spec/squeel/nodes/function_spec.rb +6 -0
- data/spec/squeel/nodes/grouping_spec.rb +6 -0
- data/spec/squeel/nodes/key_path_spec.rb +3 -3
- data/spec/squeel/nodes/operation_spec.rb +6 -0
- data/spec/squeel/nodes/order_spec.rb +6 -1
- data/spec/squeel/nodes/predicate_operators_spec.rb +1 -1
- data/spec/squeel/nodes/predicate_spec.rb +14 -1
- data/spec/squeel/nodes/sifter_spec.rb +2 -1
- data/spec/squeel/nodes/stub_spec.rb +4 -0
- data/spec/squeel/visitors/order_visitor_spec.rb +36 -0
- data/spec/squeel/visitors/{symbol_visitor_spec.rb → preload_visitor_spec.rb} +4 -3
- data/spec/squeel/visitors/select_visitor_spec.rb +26 -0
- data/spec/squeel/visitors/{attribute_visitor_spec.rb → visitor_spec.rb} +23 -37
- data/spec/support/models.rb +4 -0
- metadata +22 -10
- data/lib/squeel/visitors/attribute_visitor.rb +0 -214
@@ -1,10 +1,10 @@
|
|
1
|
-
require 'squeel/visitors/visitor'
|
2
|
-
|
3
1
|
module Squeel
|
4
2
|
module Visitors
|
5
|
-
class
|
3
|
+
class PreloadVisitor < Visitor
|
6
4
|
|
7
|
-
def initialize
|
5
|
+
def initialize(_ = nil)
|
6
|
+
# Unused. Just here to provide consistency in method signature
|
7
|
+
# among subclasses of Visitor
|
8
8
|
end
|
9
9
|
|
10
10
|
def accept(object, parent = nil)
|
@@ -61,14 +61,53 @@ module Squeel
|
|
61
61
|
@hash_context_depth > 0
|
62
62
|
end
|
63
63
|
|
64
|
+
# @return [Boolean] Whether the given value implies a context change
|
65
|
+
# @param v The value to consider
|
66
|
+
def implies_hash_context_shift?(v)
|
67
|
+
can_visit?(v)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Change context (by setting the new parent to the result of a #find or
|
71
|
+
# #traverse on the key), then accept the given value.
|
72
|
+
#
|
73
|
+
# @param k The hash key
|
74
|
+
# @param v The hash value
|
75
|
+
# @param parent The current parent object in the context
|
76
|
+
# @return The visited value
|
77
|
+
def visit_with_hash_context_shift(k, v, parent)
|
78
|
+
@hash_context_depth += 1
|
79
|
+
|
80
|
+
parent = case k
|
81
|
+
when Nodes::KeyPath
|
82
|
+
traverse(k, parent, true)
|
83
|
+
else
|
84
|
+
find(k, parent)
|
85
|
+
end
|
86
|
+
|
87
|
+
can_visit?(v) ? visit(v, parent || k) : v
|
88
|
+
ensure
|
89
|
+
@hash_context_depth -= 1
|
90
|
+
end
|
91
|
+
|
92
|
+
# If there is no context change, the default behavior is to return the
|
93
|
+
# value unchanged. Subclasses will alter this behavior as needed.
|
94
|
+
#
|
95
|
+
# @param k The hash key
|
96
|
+
# @param v The hash value
|
97
|
+
# @param parent The current parent object in the context
|
98
|
+
# @return The same value we just received.
|
99
|
+
def visit_without_hash_context_shift(k, v, parent)
|
100
|
+
v
|
101
|
+
end
|
102
|
+
|
64
103
|
# Important to avoid accidentally allowing the default ARel visitor's
|
65
104
|
# last_column quoting behavior (where a value is quoted as though it
|
66
105
|
# is of the type of the last visited column). This can wreak havoc with
|
67
106
|
# Functions and Operations.
|
68
107
|
#
|
69
108
|
# @param object The object to check
|
70
|
-
# @return [Boolean] Whether or not the ARel visitor will try to quote the
|
71
|
-
# not passed as an SqlLiteral.
|
109
|
+
# @return [Boolean] Whether or not the ARel visitor will try to quote the
|
110
|
+
# object if not passed as an SqlLiteral.
|
72
111
|
def quoted?(object)
|
73
112
|
case object
|
74
113
|
when Arel::Nodes::SqlLiteral, Bignum, Fixnum, Arel::SelectManager
|
@@ -117,16 +156,6 @@ module Squeel
|
|
117
156
|
retry
|
118
157
|
end
|
119
158
|
|
120
|
-
# Visit an array, which involves accepting any values we know how to
|
121
|
-
# accept, and skipping the rest.
|
122
|
-
#
|
123
|
-
# @param [Array] o The Array to visit
|
124
|
-
# @param parent The current parent object in the context
|
125
|
-
# @return [Array] The visited array
|
126
|
-
def visit_Array(o, parent)
|
127
|
-
o.map { |v| can_visit?(v) ? visit(v, parent) : v }.flatten
|
128
|
-
end
|
129
|
-
|
130
159
|
# Pass an object through the visitor unmodified. This is
|
131
160
|
# in order to allow objects that don't require modification
|
132
161
|
# to be handled by ARel directly.
|
@@ -139,6 +168,209 @@ module Squeel
|
|
139
168
|
end
|
140
169
|
alias :visit_Fixnum :visit_passthrough
|
141
170
|
alias :visit_Bignum :visit_passthrough
|
171
|
+
alias :visit_String :visit_passthrough
|
172
|
+
|
173
|
+
# Visit an array, which involves accepting any values we know how to
|
174
|
+
# accept, and skipping the rest.
|
175
|
+
#
|
176
|
+
# @param [Array] o The Array to visit
|
177
|
+
# @param parent The current parent object in the context
|
178
|
+
# @return [Array] The visited array
|
179
|
+
def visit_Array(o, parent)
|
180
|
+
o.map { |v| can_visit?(v) ? visit(v, parent) : v }.flatten
|
181
|
+
end
|
182
|
+
|
183
|
+
# Visit a Hash. This entails iterating through each key and value and
|
184
|
+
# visiting each value in turn.
|
185
|
+
#
|
186
|
+
# @param [Hash] o The Hash to visit
|
187
|
+
# @param parent The current parent object in the context
|
188
|
+
# @return [Array] An array of values for use in an ordering, grouping, etc.
|
189
|
+
def visit_Hash(o, parent)
|
190
|
+
o.map do |k, v|
|
191
|
+
if implies_hash_context_shift?(v)
|
192
|
+
visit_with_hash_context_shift(k, v, parent)
|
193
|
+
else
|
194
|
+
visit_without_hash_context_shift(k, v, parent)
|
195
|
+
end
|
196
|
+
end.flatten
|
197
|
+
end
|
198
|
+
|
199
|
+
# Visit a symbol. This will return an attribute named after the symbol
|
200
|
+
# against the current parent's contextualized table.
|
201
|
+
#
|
202
|
+
# @param [Symbol] o The symbol to visit
|
203
|
+
# @param parent The symbol's parent within the context
|
204
|
+
# @return [Arel::Attribute] An attribute on the contextualized parent
|
205
|
+
# table
|
206
|
+
def visit_Symbol(o, parent)
|
207
|
+
contextualize(parent)[o]
|
208
|
+
end
|
209
|
+
|
210
|
+
# Visit a stub. This will return an attribute named after the stub against
|
211
|
+
# the current parent's contextualized table.
|
212
|
+
#
|
213
|
+
# @param [Nodes::Stub] o The stub to visit
|
214
|
+
# @param parent The stub's parent within the context
|
215
|
+
# @return [Arel::Attribute] An attribute on the contextualized parent
|
216
|
+
# table
|
217
|
+
def visit_Squeel_Nodes_Stub(o, parent)
|
218
|
+
contextualize(parent)[o.to_s]
|
219
|
+
end
|
220
|
+
|
221
|
+
# Visit a keypath. This will traverse the keypath's "path", setting a new
|
222
|
+
# parent as though the keypath's endpoint was in a deeply-nested hash,
|
223
|
+
# then visit the endpoint with the new parent.
|
224
|
+
#
|
225
|
+
# @param [Nodes::KeyPath] o The keypath to visit
|
226
|
+
# @param parent The keypath's parent within the context
|
227
|
+
# @return The visited endpoint, with the parent from the KeyPath's path.
|
228
|
+
def visit_Squeel_Nodes_KeyPath(o, parent)
|
229
|
+
parent = traverse(o, parent)
|
230
|
+
|
231
|
+
visit(o.endpoint, parent)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Visit a Literal by converting it to an ARel SqlLiteral
|
235
|
+
#
|
236
|
+
# @param [Nodes::Literal] o The Literal to visit
|
237
|
+
# @param parent The parent object in the context (unused)
|
238
|
+
# @return [Arel::Nodes::SqlLiteral] An SqlLiteral
|
239
|
+
def visit_Squeel_Nodes_Literal(o, parent)
|
240
|
+
Arel.sql(o.expr)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Visit a Squeel As node, resulting in am ARel As node.
|
244
|
+
#
|
245
|
+
# @param [Nodes::As] The As node to visit
|
246
|
+
# @param parent The parent object in the context
|
247
|
+
# @return [Arel::Nodes::As] The resulting as node.
|
248
|
+
def visit_Squeel_Nodes_As(o, parent)
|
249
|
+
left = visit(o.left, parent)
|
250
|
+
# Some nodes, like Arel::SelectManager, have their own #as methods,
|
251
|
+
# with behavior that we don't want to clobber.
|
252
|
+
if left.respond_to?(:as)
|
253
|
+
left.as(o.right)
|
254
|
+
else
|
255
|
+
Arel::Nodes::As.new(left, o.right)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Visit a Squeel And node, returning an ARel Grouping containing an
|
260
|
+
# ARel And node.
|
261
|
+
#
|
262
|
+
# @param [Nodes::And] o The And node to visit
|
263
|
+
# @param parent The parent object in the context
|
264
|
+
# @return [Arel::Nodes::Grouping] A grouping node, containnig an ARel
|
265
|
+
# And node as its expression. All children will be visited before
|
266
|
+
# being passed to the And.
|
267
|
+
def visit_Squeel_Nodes_And(o, parent)
|
268
|
+
Arel::Nodes::Grouping.new(Arel::Nodes::And.new(visit(o.children, parent)))
|
269
|
+
end
|
270
|
+
|
271
|
+
# Visit a Squeel Or node, returning an ARel Or node.
|
272
|
+
#
|
273
|
+
# @param [Nodes::Or] o The Or node to visit
|
274
|
+
# @param parent The parent object in the context
|
275
|
+
# @return [Arel::Nodes::Or] An ARel Or node, with left and right sides visited
|
276
|
+
def visit_Squeel_Nodes_Or(o, parent)
|
277
|
+
Arel::Nodes::Grouping.new(Arel::Nodes::Or.new(visit(o.left, parent), (visit(o.right, parent))))
|
278
|
+
end
|
279
|
+
|
280
|
+
# Visit a Squeel Not node, returning an ARel Not node.
|
281
|
+
#
|
282
|
+
# @param [Nodes::Not] o The Not node to visit
|
283
|
+
# @param parent The parent object in the context
|
284
|
+
# @return [Arel::Nodes::Not] An ARel Not node, with expression visited
|
285
|
+
def visit_Squeel_Nodes_Not(o, parent)
|
286
|
+
Arel::Nodes::Not.new(visit(o.expr, parent))
|
287
|
+
end
|
288
|
+
|
289
|
+
# Visit a Squeel Grouping node, returning an ARel Grouping node.
|
290
|
+
#
|
291
|
+
# @param [Nodes::Grouping] o The Grouping node to visit
|
292
|
+
# @param parent The parent object in the context
|
293
|
+
# @return [Arel::Nodes::Grouping] An ARel Grouping node, with expression visited
|
294
|
+
def visit_Squeel_Nodes_Grouping(o, parent)
|
295
|
+
Arel::Nodes::Grouping.new(visit(o.expr, parent))
|
296
|
+
end
|
297
|
+
#
|
298
|
+
# Visit a Squeel function, returning an ARel NamedFunction node.
|
299
|
+
#
|
300
|
+
# @param [Nodes::Function] o The function node to visit
|
301
|
+
# @param parent The parent object in the context
|
302
|
+
# @return [Arel::Nodes::NamedFunction] A named function node. Function
|
303
|
+
# arguments are visited, if necessary, before being passed to the NamedFunction.
|
304
|
+
def visit_Squeel_Nodes_Function(o, parent)
|
305
|
+
args = o.args.map do |arg|
|
306
|
+
case arg
|
307
|
+
when Nodes::Function, Nodes::As, Nodes::Literal, Nodes::Grouping, Nodes::KeyPath, Nodes::KeyPath
|
308
|
+
visit(arg, parent)
|
309
|
+
when ActiveRecord::Relation
|
310
|
+
arg.arel.ast
|
311
|
+
when Symbol, Nodes::Stub
|
312
|
+
Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_s])
|
313
|
+
else
|
314
|
+
quote arg
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
Arel::Nodes::NamedFunction.new(o.name, args)
|
319
|
+
end
|
320
|
+
|
321
|
+
# Visit a Squeel operation node, convering it to an ARel InfixOperation
|
322
|
+
# (or subclass, as appropriate)
|
323
|
+
#
|
324
|
+
# @param [Nodes::Operation] o The Operation node to visit
|
325
|
+
# @param parent The parent object in the context
|
326
|
+
# @return [Arel::Nodes::InfixOperation] The InfixOperation (or Addition,
|
327
|
+
# Multiplication, etc) node, with both operands visited, if needed.
|
328
|
+
def visit_Squeel_Nodes_Operation(o, parent)
|
329
|
+
args = o.args.map do |arg|
|
330
|
+
case arg
|
331
|
+
when Nodes::Function, Nodes::As, Nodes::Literal, Nodes::Grouping, Nodes::KeyPath
|
332
|
+
visit(arg, parent)
|
333
|
+
when Symbol, Nodes::Stub
|
334
|
+
Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_s])
|
335
|
+
else
|
336
|
+
quote arg
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
op = case o.operator
|
341
|
+
when :+
|
342
|
+
Arel::Nodes::Addition.new(args[0], args[1])
|
343
|
+
when :-
|
344
|
+
Arel::Nodes::Subtraction.new(args[0], args[1])
|
345
|
+
when :*
|
346
|
+
Arel::Nodes::Multiplication.new(args[0], args[1])
|
347
|
+
when :/
|
348
|
+
Arel::Nodes::Division.new(args[0], args[1])
|
349
|
+
else
|
350
|
+
Arel::Nodes::InfixOperation.new(o.operator, args[0], args[1])
|
351
|
+
end
|
352
|
+
|
353
|
+
op
|
354
|
+
end
|
355
|
+
|
356
|
+
# Visit an ActiveRecord Relation, returning an Arel::SelectManager
|
357
|
+
# @param [ActiveRecord::Relation] o The Relation to visit
|
358
|
+
# @param parent The parent object in the context
|
359
|
+
# @return [Arel::SelectManager] The ARel select manager that represents
|
360
|
+
# the relation's query
|
361
|
+
def visit_ActiveRecord_Relation(o, parent)
|
362
|
+
o.arel
|
363
|
+
end
|
364
|
+
|
365
|
+
# Visit ActiveRecord::Base objects. These should be converted to their
|
366
|
+
# id before being used in a comparison.
|
367
|
+
#
|
368
|
+
# @param [ActiveRecord::Base] o The AR::Base object to visit
|
369
|
+
# @param parent The current parent object in the context
|
370
|
+
# @return [Fixnum] The id of the object
|
371
|
+
def visit_ActiveRecord_Base(o, parent)
|
372
|
+
o.id
|
373
|
+
end
|
142
374
|
|
143
375
|
end
|
144
376
|
end
|
@@ -22,4 +22,17 @@ module SqueelHelper
|
|
22
22
|
ActiveRecord::Associations::ClassMethods::JoinDependency.new(*args)
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
|
+
def activerecord_version_at_least(version_string)
|
27
|
+
required_version_parts = version_string.split('.', 3).map(&:to_i)
|
28
|
+
(0..2).each do |index|
|
29
|
+
required_version_parts[index] ||= 0
|
30
|
+
end
|
31
|
+
actual_version_parts = [
|
32
|
+
ActiveRecord::VERSION::MAJOR,
|
33
|
+
ActiveRecord::VERSION::MINOR,
|
34
|
+
ActiveRecord::VERSION::TINY
|
35
|
+
]
|
36
|
+
(actual_version_parts <=> required_version_parts) >= 0
|
37
|
+
end
|
38
|
+
end
|
@@ -5,46 +5,6 @@ module Squeel
|
|
5
5
|
module ActiveRecord
|
6
6
|
describe RelationExtensions do
|
7
7
|
|
8
|
-
describe '#predicate_visitor' do
|
9
|
-
|
10
|
-
it 'creates a predicate visitor with a Context for the relation' do
|
11
|
-
relation = Person.joins({
|
12
|
-
:children => {
|
13
|
-
:children => {
|
14
|
-
:parent => :parent
|
15
|
-
}
|
16
|
-
}
|
17
|
-
})
|
18
|
-
|
19
|
-
visitor = relation.predicate_visitor
|
20
|
-
|
21
|
-
visitor.should be_a Visitors::PredicateVisitor
|
22
|
-
table = visitor.contextualize(relation.join_dependency._join_parts.last)
|
23
|
-
table.table_alias.should eq 'parents_people_2'
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
describe '#attribute_visitor' do
|
29
|
-
|
30
|
-
it 'creates an attribute visitor with a Context for the relation' do
|
31
|
-
relation = Person.joins({
|
32
|
-
:children => {
|
33
|
-
:children => {
|
34
|
-
:parent => :parent
|
35
|
-
}
|
36
|
-
}
|
37
|
-
})
|
38
|
-
|
39
|
-
visitor = relation.attribute_visitor
|
40
|
-
|
41
|
-
visitor.should be_a Visitors::AttributeVisitor
|
42
|
-
table = visitor.contextualize(relation.join_dependency._join_parts.last)
|
43
|
-
table.table_alias.should eq 'parents_people_2'
|
44
|
-
end
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
8
|
describe 'finding by attribute' do
|
49
9
|
|
50
10
|
it 'returns nil when passed an empty string' do
|
@@ -452,11 +412,13 @@ module Squeel
|
|
452
412
|
end
|
453
413
|
|
454
414
|
it 'works with non-strings in group' do
|
455
|
-
|
456
|
-
|
457
|
-
|
415
|
+
if activerecord_version_at_least '3.2.7'
|
416
|
+
counts = Person.group{name.op('||', '-diddly')}.count
|
417
|
+
counts.should eq Person.group("name || '-diddly'").count
|
418
|
+
else
|
419
|
+
pending 'Unsupported in ActiveRecord < 3.2.7'
|
420
|
+
end
|
458
421
|
end
|
459
|
-
|
460
422
|
end
|
461
423
|
|
462
424
|
describe '#group' do
|
@@ -628,6 +590,16 @@ module Squeel
|
|
628
590
|
|
629
591
|
end
|
630
592
|
|
593
|
+
describe '#from' do
|
594
|
+
it 'creates froms with a block' do
|
595
|
+
expected = /SELECT "sub"."name" AS aliased_name FROM \(SELECT "people"."name" FROM "people"\s*\) sub/
|
596
|
+
block = Person.from{Person.select{name}.as('sub')}.
|
597
|
+
select{sub.name.as('aliased_name')}
|
598
|
+
sql = block.to_sql
|
599
|
+
sql.should match expected
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
631
603
|
describe '#build_where' do
|
632
604
|
|
633
605
|
it 'sanitizes SQL as usual with strings' do
|
@@ -672,6 +644,24 @@ module Squeel
|
|
672
644
|
@person.name.should eq 'bob'
|
673
645
|
end
|
674
646
|
|
647
|
+
it 'creates new records with equality predicates from has_many associations' do
|
648
|
+
if activerecord_version_at_least '3.1.0'
|
649
|
+
person = Person.first
|
650
|
+
article = person.articles_with_condition.new
|
651
|
+
article.person.should eq person
|
652
|
+
article.title.should eq 'Condition'
|
653
|
+
else
|
654
|
+
pending 'Unsupported on ActiveRecord < 3.1'
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
658
|
+
it 'creates new records with equality predicates from has_many :through associations' do
|
659
|
+
pending "When ActiveRecord supports this, we'll want to, too"
|
660
|
+
person = Person.first
|
661
|
+
comment = person.article_comments_with_first_post.new
|
662
|
+
comment.body.should eq 'first post'
|
663
|
+
end
|
664
|
+
|
675
665
|
it "maintains activerecord default scope functionality" do
|
676
666
|
PersonNamedBill.new.name.should eq 'Bill'
|
677
667
|
end
|
@@ -740,12 +730,24 @@ module Squeel
|
|
740
730
|
sql.should match /Bert/
|
741
731
|
end
|
742
732
|
|
743
|
-
it 'uses the given equality condition in the case of a conflicting where from a default scope
|
744
|
-
|
733
|
+
it 'uses the given equality condition in the case of a conflicting where from a default scope' do
|
734
|
+
if activerecord_version_at_least '3.1'
|
735
|
+
relation = PersonNamedBill.where{name == 'Ernie'}
|
736
|
+
sql = relation.to_sql
|
737
|
+
sql.should_not match /Bill/
|
738
|
+
sql.should match /Ernie/
|
739
|
+
else
|
740
|
+
pending 'Unsupported in ActiveRecord < 3.1'
|
741
|
+
end
|
742
|
+
end
|
743
|
+
|
744
|
+
it 'allows scopes to join/query a table through two different associations and uses the correct alias' do
|
745
|
+
relation = Person.with_article_title('hi').
|
746
|
+
with_article_condition_title('yo')
|
745
747
|
sql = relation.to_sql
|
746
|
-
sql.
|
747
|
-
sql.should match /
|
748
|
-
end
|
748
|
+
sql.should match /"articles"."title" = 'hi'/
|
749
|
+
sql.should match /"articles_with_conditions_people"."title" = 'yo'/
|
750
|
+
end
|
749
751
|
|
750
752
|
it "doesn't ruin everything when a scope returns nil" do
|
751
753
|
relation = Person.nil_scope
|
@@ -770,6 +772,20 @@ module Squeel
|
|
770
772
|
sql.scan(/"people"."id"/).should have(1).item
|
771
773
|
end
|
772
774
|
|
775
|
+
it 'merges scopes that contain functions' do
|
776
|
+
relation = PersonNamedBill.scoped.with_salary_equal_to(100)
|
777
|
+
sql = relation.to_sql
|
778
|
+
sql.should match /abs\("people"."salary"\) = 100/
|
779
|
+
end
|
780
|
+
|
781
|
+
it 'uses last equality when merging two scopes with identical function equalities' do
|
782
|
+
relation = PersonNamedBill.scoped.with_salary_equal_to(100).
|
783
|
+
with_salary_equal_to(200)
|
784
|
+
sql = relation.to_sql
|
785
|
+
sql.should_not match /abs\("people"."salary"\) = 100/
|
786
|
+
sql.should match /abs\("people"."salary"\) = 200/
|
787
|
+
end
|
788
|
+
|
773
789
|
end
|
774
790
|
|
775
791
|
describe '#to_a' do
|