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
@@ -0,0 +1,66 @@
1
+ module Squeel
2
+ module Adapters
3
+ module ActiveRecord
4
+ describe JoinDependency do
5
+ before do
6
+ @jd = new_join_dependency(Person, {}, [])
7
+ end
8
+
9
+ it 'joins with symbols' do
10
+ @jd.send(:build, :articles => :comments)
11
+ @jd.join_associations.should have(2).associations
12
+ @jd.join_associations.each do |association|
13
+ association.join_type.should eq Arel::InnerJoin
14
+ end
15
+ end
16
+
17
+ it 'joins has_many :through associations' do
18
+ @jd.send(:build, :authored_article_comments)
19
+ @jd.join_associations.should have(1).association
20
+ @jd.join_associations.first.table_name.should eq 'comments'
21
+ end
22
+
23
+ it 'joins with stubs' do
24
+ @jd.send(:build, Nodes::Stub.new(:articles) => Nodes::Stub.new(:comments))
25
+ @jd.join_associations.should have(2).associations
26
+ @jd.join_associations.each do |association|
27
+ association.join_type.should eq Arel::InnerJoin
28
+ end
29
+ @jd.join_associations[0].table_name.should eq 'articles'
30
+ @jd.join_associations[1].table_name.should eq 'comments'
31
+ end
32
+
33
+ it 'joins with key paths' do
34
+ @jd.send(:build, dsl{children.children.parent})
35
+ @jd.join_associations.should have(3).associations
36
+ @jd.join_associations.each do |association|
37
+ association.join_type.should eq Arel::InnerJoin
38
+ end
39
+ @jd.join_associations[0].aliased_table_name.should eq 'children_people'
40
+ @jd.join_associations[1].aliased_table_name.should eq 'children_people_2'
41
+ @jd.join_associations[2].aliased_table_name.should eq 'parents_people'
42
+ end
43
+
44
+ it 'joins with key paths as keys' do
45
+ @jd.send(:build, dsl{{children.parent => parent}})
46
+ @jd.join_associations.should have(3).associations
47
+ @jd.join_associations.each do |association|
48
+ association.join_type.should eq Arel::InnerJoin
49
+ end
50
+ @jd.join_associations[0].aliased_table_name.should eq 'children_people'
51
+ @jd.join_associations[1].aliased_table_name.should eq 'parents_people'
52
+ @jd.join_associations[2].aliased_table_name.should eq 'parents_people_2'
53
+ end
54
+
55
+ it 'joins using outer joins' do
56
+ @jd.send(:build, :articles.outer => :comments.outer)
57
+ @jd.join_associations.should have(2).associations
58
+ @jd.join_associations.each do |association|
59
+ association.join_type.should eq Arel::OuterJoin
60
+ end
61
+ end
62
+
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,627 @@
1
+ module Squeel
2
+ module Adapters
3
+ module ActiveRecord
4
+ describe Relation do
5
+
6
+ describe '#predicate_visitor' do
7
+
8
+ it 'creates a predicate visitor with a Context for the relation' do
9
+ relation = Person.joins({
10
+ :children => {
11
+ :children => {
12
+ :parent => :parent
13
+ }
14
+ }
15
+ })
16
+
17
+ visitor = relation.predicate_visitor
18
+
19
+ visitor.should be_a Visitors::PredicateVisitor
20
+ table = visitor.contextualize(relation.join_dependency.join_parts.last)
21
+ table.table_alias.should eq 'parents_people_2'
22
+ end
23
+
24
+ end
25
+
26
+ describe '#attribute_visitor' do
27
+
28
+ it 'creates an attribute visitor with a Context for the relation' do
29
+ relation = Person.joins({
30
+ :children => {
31
+ :children => {
32
+ :parent => :parent
33
+ }
34
+ }
35
+ })
36
+
37
+ visitor = relation.attribute_visitor
38
+
39
+ visitor.should be_a Visitors::AttributeVisitor
40
+ table = visitor.contextualize(relation.join_dependency.join_parts.last)
41
+ table.table_alias.should eq 'parents_people_2'
42
+ end
43
+
44
+ end
45
+
46
+ describe '#build_arel' do
47
+
48
+ it 'joins associations' do
49
+ relation = Person.joins({
50
+ :children => {
51
+ :children => {
52
+ :parent => :parent
53
+ }
54
+ }
55
+ })
56
+
57
+ arel = relation.build_arel
58
+
59
+ relation.join_dependency.join_associations.should have(4).items
60
+ arel.to_sql.should match /INNER JOIN "people" "parents_people_2" ON "parents_people_2"."id" = "parents_people"."parent_id"/
61
+ end
62
+
63
+ it 'joins associations with custom join types' do
64
+ relation = Person.joins({
65
+ :children.outer => {
66
+ :children => {
67
+ :parent => :parent.outer
68
+ }
69
+ }
70
+ })
71
+
72
+ arel = relation.build_arel
73
+
74
+ relation.join_dependency.join_associations.should have(4).items
75
+ arel.to_sql.should match /LEFT OUTER JOIN "people" "children_people"/
76
+ arel.to_sql.should match /LEFT OUTER JOIN "people" "parents_people_2" ON "parents_people_2"."id" = "parents_people"."parent_id"/
77
+ end
78
+
79
+ it 'only joins an association once, even if two overlapping joins_values hashes are given' do
80
+ relation = Person.joins({
81
+ :children => {
82
+ :children => {
83
+ :parent => :parent
84
+ }
85
+ }
86
+ }).joins({
87
+ :children => {
88
+ :children => {
89
+ :children => :parent
90
+ }
91
+ }
92
+ })
93
+
94
+ arel = relation.build_arel
95
+ relation.join_dependency.join_associations.should have(6).items
96
+ arel.to_sql.should match /INNER JOIN "people" "parents_people_3" ON "parents_people_3"."id" = "children_people_3"."parent_id"/
97
+ end
98
+
99
+ it 'visits wheres with a PredicateVisitor, converting them to ARel nodes' do
100
+ relation = Person.where(:name.matches => '%bob%')
101
+ arel = relation.build_arel
102
+ arel.to_sql.should match /"people"."name" LIKE '%bob%'/
103
+ end
104
+
105
+ it 'maps wheres inside a hash to their appropriate association table' do
106
+ relation = Person.joins({
107
+ :children => {
108
+ :children => {
109
+ :parent => :parent
110
+ }
111
+ }
112
+ }).where({
113
+ :children => {
114
+ :children => {
115
+ :parent => {
116
+ :parent => { :name => 'bob' }
117
+ }
118
+ }
119
+ }
120
+ })
121
+
122
+ arel = relation.build_arel
123
+
124
+ arel.to_sql.should match /"parents_people_2"."name" = 'bob'/
125
+ end
126
+
127
+ it 'combines multiple conditions of the same type against the same column with AND' do
128
+ relation = Person.where(:name.matches => '%bob%')
129
+ relation = relation.where(:name.matches => '%joe%')
130
+ arel = relation.build_arel
131
+ arel.to_sql.should match /"people"."name" LIKE '%bob%' AND "people"."name" LIKE '%joe%'/
132
+ end
133
+
134
+ it 'handles ORs between predicates' do
135
+ relation = Person.joins{articles}.where{(name =~ 'Joe%') | (articles.title =~ 'Hello%')}
136
+ arel = relation.build_arel
137
+ arel.to_sql.should match /OR/
138
+ end
139
+
140
+ it 'maintains groupings as given' do
141
+ relation = Person.where(dsl{(name == 'Ernie') | ((name =~ 'Bob%') & (name =~ '%by'))})
142
+ arel = relation.build_arel
143
+ arel.to_sql.should match /"people"."name" = 'Ernie' OR \("people"."name" LIKE 'Bob%' AND "people"."name" LIKE '%by'\)/
144
+ end
145
+
146
+ it 'maps havings inside a hash to their appropriate association table' do
147
+ relation = Person.joins({
148
+ :children => {
149
+ :children => {
150
+ :parent => :parent
151
+ }
152
+ }
153
+ }).having({
154
+ :children => {
155
+ :children => {
156
+ :parent => {
157
+ :parent => {:name => 'joe'}
158
+ }
159
+ }
160
+ }
161
+ })
162
+
163
+ arel = relation.build_arel
164
+
165
+ arel.to_sql.should match /HAVING "parents_people_2"."name" = 'joe'/
166
+ end
167
+
168
+ it 'maps orders inside a hash to their appropriate association table' do
169
+ relation = Person.joins({
170
+ :children => {
171
+ :children => {
172
+ :parent => :parent
173
+ }
174
+ }
175
+ }).order({
176
+ :children => {
177
+ :children => {
178
+ :parent => {
179
+ :parent => :id.asc
180
+ }
181
+ }
182
+ }
183
+ })
184
+
185
+ arel = relation.build_arel
186
+
187
+ arel.to_sql.should match /ORDER BY "parents_people_2"."id" ASC/
188
+ end
189
+
190
+ end
191
+
192
+ describe '#to_sql' do
193
+ it 'casts a non-acceptable value for a Function key properly in a hash' do
194
+ relation = Person.joins(:children).where(:children => {:coalesce.func(:name, 'Mr. No-name') => 'Ernie'})
195
+ relation.to_sql.should match /'Ernie'/
196
+ end
197
+
198
+ it 'casts a non-acceptable value for a Predicate containing a Function expr properly' do
199
+ relation = Person.joins(:children).where(:children => {:coalesce.func(:name, 'Mr. No-name').eq => 'Ernie'})
200
+ relation.to_sql.should match /'Ernie'/
201
+ end
202
+
203
+ it 'casts a non-acceptable value for a KeyPath with a Function endpoint properly' do
204
+ relation = Person.joins(:children).where{{children.coalesce(:name, 'Mr. No-name') => 'Ernie'}}
205
+ relation.to_sql.should match /'Ernie'/
206
+ end
207
+
208
+ it 'casts a non-acceptable value for a KeyPath with a Predicate endpoint containing a Function expr properly' do
209
+ relation = Person.joins(:children).where{{children.coalesce(:name, 'Mr. No-name').eq => 'Ernie'}}
210
+ relation.to_sql.should match /'Ernie'/
211
+ end
212
+
213
+ it 'casts a non-acceptable value for a Function with a Predicate endpoint containing a Function expr properly' do
214
+ relation = Person.joins(:children).where{children.coalesce(:name, 'Mr. No-name') == 'Ernie'}
215
+ relation.to_sql.should match /'Ernie'/
216
+ end
217
+ end
218
+
219
+ describe '#includes' do
220
+
221
+ it 'builds options with a block' do
222
+ standard = Person.includes(:children => :children).where(:children => {:children => {:name => 'bob'}})
223
+ block = Person.includes{{children => children}}.where(:children => {:children => {:name => 'bob'}})
224
+ block.debug_sql.should eq standard.debug_sql
225
+ end
226
+
227
+ it 'eager loads multiple top-level associations with a block' do
228
+ standard = Person.includes(:children, :articles, :comments).where(:children => {:name => 'bob'})
229
+ block = Person.includes{[children, articles, comments]}.where(:children => {:name => 'bob'})
230
+ block.debug_sql.should eq standard.debug_sql
231
+ end
232
+
233
+ it 'eager loads polymorphic belongs_to associations' do
234
+ relation = Note.includes{notable(Article)}.where{{notable(Article) => {title => 'hey'}}}
235
+ relation.debug_sql.should match /"notes"."notable_type" = 'Article'/
236
+ end
237
+
238
+ it 'eager loads multiple polymorphic belongs_to associations' do
239
+ relation = Note.includes{[notable(Article), notable(Person)]}.
240
+ where{{notable(Article) => {title => 'hey'}}}.
241
+ where{{notable(Person) => {name => 'joe'}}}
242
+ relation.debug_sql.should match /"notes"."notable_type" = 'Article'/
243
+ relation.debug_sql.should match /"notes"."notable_type" = 'Person'/
244
+ end
245
+
246
+ it "only includes once, even if two join types are used" do
247
+ relation = Person.includes(:articles.inner, :articles.outer).where(:articles => {:title => 'hey'})
248
+ relation.debug_sql.scan("JOIN").size.should eq 1
249
+ end
250
+
251
+ it 'includes a keypath' do
252
+ relation = Note.includes{notable(Article).person.children}.where{notable(Article).person.children.name == 'Ernie'}
253
+ relation.debug_sql.should match /SELECT "notes".* FROM "notes" LEFT OUTER JOIN "articles" ON "articles"."id" = "notes"."notable_id" AND "notes"."notable_type" = 'Article' LEFT OUTER JOIN "people" ON "people"."id" = "articles"."person_id" LEFT OUTER JOIN "people" "children_people" ON "children_people"."parent_id" = "people"."id"/
254
+ end
255
+
256
+ end
257
+
258
+ describe '#preload' do
259
+
260
+ it 'builds options with a block' do
261
+ relation = Person.preload{children}
262
+ queries_for {relation.all}.should have(2).items
263
+ queries_for {relation.first.children}.should have(0).items
264
+ end
265
+
266
+ it 'builds options with a keypath' do
267
+ relation = Person.preload{articles.comments}
268
+ queries_for {relation.all}.should have(3).items
269
+ queries_for {relation.first.articles.first.comments}.should have(0).items
270
+ end
271
+
272
+ it 'builds options with a hash' do
273
+ relation = Person.preload{{
274
+ articles => {
275
+ comments => person
276
+ }
277
+ }}
278
+
279
+ queries_for {relation.all}.should have(4).items
280
+
281
+ queries_for {
282
+ relation.first.articles
283
+ relation.first.articles.first.comments
284
+ relation.first.articles.first.comments.first.person
285
+ }.should have(0).items
286
+ end
287
+
288
+ end
289
+
290
+ describe '#eager_load' do
291
+
292
+ it 'builds options with a block' do
293
+ standard = Person.eager_load(:children => :children)
294
+ block = Person.eager_load{{children => children}}
295
+ block.debug_sql.should eq standard.debug_sql
296
+ queries_for {block.all}.should have(1).item
297
+ queries_for {block.first.children}.should have(0).items
298
+ end
299
+
300
+ it 'eager loads multiple top-level associations with a block' do
301
+ standard = Person.eager_load(:children, :articles, :comments)
302
+ block = Person.eager_load{[children, articles, comments]}
303
+ block.debug_sql.should eq standard.debug_sql
304
+ end
305
+
306
+ it 'eager loads polymorphic belongs_to associations' do
307
+ relation = Note.eager_load{notable(Article)}
308
+ relation.debug_sql.should match /"notes"."notable_type" = 'Article'/
309
+ end
310
+
311
+ it 'eager loads multiple polymorphic belongs_to associations' do
312
+ relation = Note.eager_load{[notable(Article), notable(Person)]}
313
+ relation.debug_sql.should match /"notes"."notable_type" = 'Article'/
314
+ relation.debug_sql.should match /"notes"."notable_type" = 'Person'/
315
+ end
316
+
317
+ it "only eager_load once, even if two join types are used" do
318
+ relation = Person.eager_load(:articles.inner, :articles.outer)
319
+ relation.debug_sql.scan("JOIN").size.should eq 1
320
+ end
321
+
322
+ it 'eager_load a keypath' do
323
+ relation = Note.eager_load{notable(Article).person.children}
324
+ relation.debug_sql.should match /SELECT "notes".* FROM "notes" LEFT OUTER JOIN "articles" ON "articles"."id" = "notes"."notable_id" AND "notes"."notable_type" = 'Article' LEFT OUTER JOIN "people" ON "people"."id" = "articles"."person_id" LEFT OUTER JOIN "people" "children_people" ON "children_people"."parent_id" = "people"."id"/
325
+ end
326
+
327
+ end
328
+
329
+ describe '#select' do
330
+
331
+ it 'accepts options from a block' do
332
+ standard = Person.select(:id)
333
+ block = Person.select {id}
334
+ block.to_sql.should eq standard.to_sql
335
+ end
336
+
337
+ it 'falls back to Array#select behavior with a block that has an arity' do
338
+ people = Person.select{|p| p.name =~ /John/}
339
+ people.should have(1).person
340
+ people.first.name.should eq 'Miss Cameron Johnson'
341
+ end
342
+
343
+ it 'behaves as normal with standard parameters' do
344
+ people = Person.select(:id)
345
+ people.should have(332).people
346
+ expect { people.first.name }.to raise_error ActiveModel::MissingAttributeError
347
+ end
348
+
349
+ it 'allows a function in the select values via Symbol#func' do
350
+ relation = Person.select(:max.func(:id).as('max_id'))
351
+ relation.first.max_id.should eq 332
352
+ end
353
+
354
+ it 'allows a function in the select values via block' do
355
+ relation = Person.select{max(id).as(max_id)}
356
+ relation.first.max_id.should eq 332
357
+ end
358
+
359
+ it 'allows an operation in the select values via block' do
360
+ relation = Person.select{[id, (id + 1).as('id_plus_one')]}.where('id_plus_one = 2')
361
+ relation.first.id.should eq 1
362
+ end
363
+
364
+ it 'allows custom operators in the select values via block' do
365
+ relation = Person.select{name.op('||', '-diddly').as(flanderized_name)}
366
+ relation.first.flanderized_name.should eq 'Aric Smith-diddly'
367
+ end
368
+
369
+ end
370
+
371
+ describe '#group' do
372
+
373
+ it 'builds options with a block' do
374
+ standard = Person.group(:name)
375
+ block = Person.group{name}
376
+ block.to_sql.should eq standard.to_sql
377
+ end
378
+
379
+ end
380
+
381
+ describe '#where' do
382
+
383
+ it 'builds options with a block' do
384
+ standard = Person.where(:name => 'bob')
385
+ block = Person.where{{name => 'bob'}}
386
+ block.to_sql.should eq standard.to_sql
387
+ end
388
+
389
+ it 'builds compound conditions with a block' do
390
+ block = Person.where{(name == 'bob') & (salary == 100000)}
391
+ block.to_sql.should match /"people"."name" = 'bob'/
392
+ block.to_sql.should match /AND/
393
+ block.to_sql.should match /"people"."salary" = 100000/
394
+ end
395
+
396
+ it 'allows mixing hash and operator syntax inside a block' do
397
+ block = Person.joins(:comments).
398
+ where{(name == 'bob') & {comments => (body == 'First post!')}}
399
+ block.to_sql.should match /"people"."name" = 'bob'/
400
+ block.to_sql.should match /AND/
401
+ block.to_sql.should match /"comments"."body" = 'First post!'/
402
+ end
403
+
404
+ it 'allows a condition on a function via block' do
405
+ relation = Person.where{coalesce(nil,id) == 5}
406
+ relation.first.id.should eq 5
407
+ end
408
+
409
+ it 'allows a condition on an operation via block' do
410
+ relation = Person.where{(id + 1) == 2}
411
+ relation.first.id.should eq 1
412
+ end
413
+
414
+ it 'maps conditions onto their proper table with multiple polymorphic joins' do
415
+ relation = Note.joins{[notable(Article).outer, notable(Person).outer]}
416
+ people_notes = relation.where{notable(Person).salary > 30000}
417
+ article_notes = relation.where{notable(Article).title =~ '%'}
418
+ people_and_article_notes = relation.where{(notable(Person).salary > 30000) | (notable(Article).title =~ '%')}
419
+ people_notes.should have(10).items
420
+ article_notes.should have(30).items
421
+ people_and_article_notes.should have(40).items
422
+ end
423
+
424
+ it 'allows a subquery on the value side of a predicate' do
425
+ old_and_busted = Person.where(:name => ['Aric Smith', 'Gladyce Kulas'])
426
+ new_hotness = Person.where{name.in(Person.select{name}.where{name.in(['Aric Smith', 'Gladyce Kulas'])})}
427
+ new_hotness.should have(2).items
428
+ old_and_busted.to_a.should eq new_hotness.to_a
429
+ end
430
+
431
+ end
432
+
433
+ describe '#joins' do
434
+
435
+ it 'builds options with a block' do
436
+ standard = Person.joins(:children => :children)
437
+ block = Person.joins{{children => children}}
438
+ block.to_sql.should eq standard.to_sql
439
+ end
440
+
441
+ it 'accepts multiple top-level associations with a block' do
442
+ standard = Person.joins(:children, :articles, :comments)
443
+ block = Person.joins{[children, articles, comments]}
444
+ block.to_sql.should eq standard.to_sql
445
+ end
446
+
447
+ it 'joins has_many :through associations' do
448
+ relation = Person.joins(:authored_article_comments)
449
+ relation.first.authored_article_comments.first.should eq Comment.first
450
+ end
451
+
452
+ it 'joins polymorphic belongs_to associations' do
453
+ relation = Note.joins{notable(Article)}
454
+ relation.to_sql.should match /"notes"."notable_type" = 'Article'/
455
+ end
456
+
457
+ it 'joins multiple polymorphic belongs_to associations' do
458
+ relation = Note.joins{[notable(Article), notable(Person)]}
459
+ relation.to_sql.should match /"notes"."notable_type" = 'Article'/
460
+ relation.to_sql.should match /"notes"."notable_type" = 'Person'/
461
+ end
462
+
463
+ it "only joins once, even if two join types are used" do
464
+ relation = Person.joins(:articles.inner, :articles.outer)
465
+ relation.to_sql.scan("JOIN").size.should eq 1
466
+ end
467
+
468
+ it 'joins a keypath' do
469
+ relation = Note.joins{notable(Article).person.children}
470
+ relation.to_sql.should match /SELECT "notes".* FROM "notes" INNER JOIN "articles" ON "articles"."id" = "notes"."notable_id" AND "notes"."notable_type" = 'Article' INNER JOIN "people" ON "people"."id" = "articles"."person_id" INNER JOIN "people" "children_people" ON "children_people"."parent_id" = "people"."id"/
471
+ end
472
+
473
+ end
474
+
475
+ describe '#having' do
476
+
477
+ it 'builds options with a block' do
478
+ standard = Person.having(:name => 'bob')
479
+ block = Person.having{{name => 'bob'}}
480
+ block.to_sql.should eq standard.to_sql
481
+ end
482
+
483
+ it 'allows complex conditions on aggregate columns' do
484
+ relation = Person.group(:parent_id).having{salary == max(salary)}
485
+ relation.first.name.should eq 'Gladyce Kulas'
486
+ end
487
+
488
+ it 'allows a condition on a function via block' do
489
+ relation = Person.group(:id).having{coalesce(nil,id) == 5}
490
+ relation.first.id.should eq 5
491
+ end
492
+
493
+ it 'allows a condition on an operation via block' do
494
+ relation = Person.group(:id).having{(id + 1) == 2}
495
+ relation.first.id.should eq 1
496
+ end
497
+
498
+ end
499
+
500
+ describe '#order' do
501
+
502
+ it 'builds options with a block' do
503
+ standard = Person.order(:name)
504
+ block = Person.order{name}
505
+ block.to_sql.should eq standard.to_sql
506
+ end
507
+
508
+ end
509
+
510
+ describe '#reorder' do
511
+ before do
512
+ @standard = Person.order(:name)
513
+ end
514
+
515
+ it 'builds options with a block' do
516
+ block = Person.reorder{id}
517
+ block.to_sql.should_not eq @standard.to_sql
518
+ block.to_sql.should match /ORDER BY "people"."id"/
519
+ end
520
+
521
+ end
522
+
523
+ describe '#build_where' do
524
+
525
+ it 'sanitizes SQL as usual with strings' do
526
+ wheres = Person.where('name like ?', '%bob%').where_values
527
+ wheres.should eq ["name like '%bob%'"]
528
+ end
529
+
530
+ it 'sanitizes SQL as usual with strings and hash substitution' do
531
+ wheres = Person.where('name like :name', :name => '%bob%').where_values
532
+ wheres.should eq ["name like '%bob%'"]
533
+ end
534
+
535
+ it 'sanitizes SQL as usual with arrays' do
536
+ wheres = Person.where(['name like ?', '%bob%']).where_values
537
+ wheres.should eq ["name like '%bob%'"]
538
+ end
539
+
540
+ it 'adds hash where values without converting to ARel predicates' do
541
+ wheres = Person.where({:name => 'bob'}).where_values
542
+ wheres.should eq [{:name => 'bob'}]
543
+ end
544
+
545
+ end
546
+
547
+ describe '#debug_sql' do
548
+
549
+ it 'returns the query that would be run against the database, even if eager loading' do
550
+ relation = Person.includes(:comments, :articles).
551
+ where(:comments => {:body => 'First post!'}).
552
+ where(:articles => {:title => 'Hello, world!'})
553
+ relation.debug_sql.should_not eq relation.to_sql
554
+ relation.debug_sql.should match /SELECT "people"."id" AS t0_r0/
555
+ end
556
+
557
+ end
558
+
559
+ describe '#where_values_hash' do
560
+
561
+ it 'creates new records with equality predicates from wheres' do
562
+ @person = Person.where(:name => 'bob', :parent_id => 3).new
563
+ @person.parent_id.should eq 3
564
+ @person.name.should eq 'bob'
565
+ end
566
+
567
+ it 'uses the last supplied equality predicate in where_values when creating new records' do
568
+ @person = Person.where(:name => 'bob', :parent_id => 3).where(:name => 'joe').new
569
+ @person.parent_id.should eq 3
570
+ @person.name.should eq 'joe'
571
+ end
572
+
573
+ it 'creates through a join model' do
574
+ Article.transaction do
575
+ article = Article.first
576
+ person = article.commenters.create(:name => 'Ernie Miller')
577
+ person.should be_persisted
578
+ person.comments.should have(1).comment
579
+ person.comments.first.article.should eq article
580
+ raise ::ActiveRecord::Rollback
581
+ end
582
+ end
583
+
584
+ end
585
+
586
+ describe '#merge' do
587
+
588
+ it 'merges relations with the same base' do
589
+ relation = Person.where{name == 'bob'}.merge(Person.where{salary == 100000})
590
+ sql = relation.to_sql
591
+ sql.should match /"people"."name" = 'bob'/
592
+ sql.should match /"people"."salary" = 100000/
593
+ end
594
+
595
+ it 'merges relations with a different base' do
596
+ relation = Person.where{name == 'bob'}.joins(:articles).merge(Article.where{title == 'Hello world!'})
597
+ sql = relation.to_sql
598
+ sql.should match /INNER JOIN "articles" ON "articles"."person_id" = "people"."id"/
599
+ sql.should match /"people"."name" = 'bob'/
600
+ sql.should match /"articles"."title" = 'Hello world!'/
601
+ end
602
+
603
+ it 'does not break hm:t with conditions' do
604
+ relation = Person.first.condition_article_comments
605
+ sql = relation.scoped.to_sql
606
+ sql.should match /"articles"."title" = 'Condition'/
607
+ end
608
+
609
+ end
610
+
611
+ describe '#to_a' do
612
+
613
+ it 'eager-loads associations with dependent conditions' do
614
+ relation = Person.includes(:comments, :articles).
615
+ where{{comments => {body => 'First post!'}}}
616
+ relation.size.should be 1
617
+ person = relation.first
618
+ person.name.should eq 'Gladyce Kulas'
619
+ person.comments.loaded?.should be true
620
+ end
621
+
622
+ end
623
+
624
+ end
625
+ end
626
+ end
627
+ end