sayso-meta_where 1.0.4.001

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/CHANGELOG +90 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE +20 -0
  6. data/README.rdoc +346 -0
  7. data/Rakefile +11 -0
  8. data/lib/core_ext/hash.rb +5 -0
  9. data/lib/core_ext/symbol.rb +39 -0
  10. data/lib/core_ext/symbol_operators.rb +48 -0
  11. data/lib/meta_where/association_reflection.rb +51 -0
  12. data/lib/meta_where/column.rb +31 -0
  13. data/lib/meta_where/compound.rb +20 -0
  14. data/lib/meta_where/condition.rb +32 -0
  15. data/lib/meta_where/condition_operators.rb +19 -0
  16. data/lib/meta_where/function.rb +108 -0
  17. data/lib/meta_where/join_dependency.rb +101 -0
  18. data/lib/meta_where/join_type.rb +43 -0
  19. data/lib/meta_where/not.rb +13 -0
  20. data/lib/meta_where/relation.rb +296 -0
  21. data/lib/meta_where/utility.rb +51 -0
  22. data/lib/meta_where/version.rb +3 -0
  23. data/lib/meta_where/visitors/attribute.rb +58 -0
  24. data/lib/meta_where/visitors/predicate.rb +149 -0
  25. data/lib/meta_where/visitors/visitor.rb +47 -0
  26. data/lib/meta_where.rb +51 -0
  27. data/meta_where.gemspec +48 -0
  28. data/test/fixtures/companies.yml +17 -0
  29. data/test/fixtures/company.rb +7 -0
  30. data/test/fixtures/data_type.rb +3 -0
  31. data/test/fixtures/data_types.yml +15 -0
  32. data/test/fixtures/developer.rb +7 -0
  33. data/test/fixtures/developers.yml +55 -0
  34. data/test/fixtures/developers_projects.yml +25 -0
  35. data/test/fixtures/fixed_bid_project.rb +2 -0
  36. data/test/fixtures/invalid_company.rb +4 -0
  37. data/test/fixtures/invalid_developer.rb +4 -0
  38. data/test/fixtures/note.rb +3 -0
  39. data/test/fixtures/notes.yml +95 -0
  40. data/test/fixtures/people.yml +14 -0
  41. data/test/fixtures/person.rb +4 -0
  42. data/test/fixtures/project.rb +7 -0
  43. data/test/fixtures/projects.yml +29 -0
  44. data/test/fixtures/schema.rb +53 -0
  45. data/test/fixtures/time_and_materials_project.rb +2 -0
  46. data/test/helper.rb +33 -0
  47. data/test/test_base.rb +21 -0
  48. data/test/test_relations.rb +476 -0
  49. metadata +184 -0
@@ -0,0 +1,7 @@
1
+ class Project < ActiveRecord::Base
2
+ has_and_belongs_to_many :developers
3
+ has_many :notes, :as => :notable
4
+
5
+ default_scope where(:name.not_eq => nil)
6
+ scope :hours_lte_100, where(:estimated_hours.lte => 100)
7
+ end
@@ -0,0 +1,29 @@
1
+ y2k:
2
+ estimated_hours: 1000
3
+ name : Y2K Software Updates
4
+ id : 1
5
+ type : FixedBidProject
6
+
7
+ virus:
8
+ estimated_hours: 80
9
+ name : Virus
10
+ id : 2
11
+ type : FixedBidProject
12
+
13
+ awesome:
14
+ estimated_hours: 100
15
+ name : Do something awesome
16
+ id : 3
17
+ type : FixedBidProject
18
+
19
+ metasearch:
20
+ estimated_hours: 100
21
+ name : MetaSearch Development
22
+ id : 4
23
+ type : TimeAndMaterialsProject
24
+
25
+ another:
26
+ estimated_hours: 120
27
+ name : Another Project
28
+ id : 5
29
+ type : TimeAndMaterialsProject
@@ -0,0 +1,53 @@
1
+ ActiveRecord::Schema.define do
2
+
3
+ create_table "companies", :force => true do |t|
4
+ t.string "name"
5
+ t.datetime "created_at"
6
+ t.datetime "updated_at"
7
+ end
8
+
9
+ create_table "developers", :force => true do |t|
10
+ t.integer "company_id"
11
+ t.string "name"
12
+ t.integer "salary"
13
+ t.boolean "slacker"
14
+ end
15
+
16
+ create_table "projects", :force => true do |t|
17
+ t.string "name"
18
+ t.string "type"
19
+ t.float "estimated_hours"
20
+ end
21
+
22
+ create_table "developers_projects", :id => false, :force => true do |t|
23
+ t.integer "developer_id"
24
+ t.integer "project_id"
25
+ end
26
+
27
+ create_table "notes", :force => true do |t|
28
+ t.string "notable_type"
29
+ t.integer "notable_id"
30
+ t.string "note"
31
+ end
32
+
33
+ create_table "data_types", :force => true do |t|
34
+ t.integer "company_id"
35
+ t.string "str"
36
+ t.text "txt"
37
+ t.integer "int"
38
+ t.float "flt"
39
+ t.decimal "dec"
40
+ t.datetime "dtm"
41
+ t.timestamp "tms"
42
+ t.time "tim"
43
+ t.date "dat"
44
+ t.binary "bin"
45
+ t.boolean "bln"
46
+ end
47
+
48
+ create_table "people", :force => true do |t|
49
+ t.integer "parent_id"
50
+ t.string "name"
51
+ end
52
+
53
+ end
@@ -0,0 +1,2 @@
1
+ class TimeAndMaterialsProject < Project
2
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,33 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ Bundler.setup
4
+ require 'test/unit'
5
+ require 'shoulda'
6
+ require 'active_record'
7
+ require 'active_record/fixtures'
8
+ require 'active_support/time'
9
+ require 'meta_where'
10
+
11
+ MetaWhere.operator_overload!
12
+
13
+ FIXTURES_PATH = File.join(File.dirname(__FILE__), 'fixtures')
14
+
15
+ Time.zone = 'Eastern Time (US & Canada)'
16
+
17
+ ActiveRecord::Base.establish_connection(
18
+ :adapter => defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3',
19
+ :database => ':memory:'
20
+ )
21
+
22
+ dep = defined?(ActiveSupport::Dependencies) ? ActiveSupport::Dependencies : ::Dependencies
23
+ dep.autoload_paths.unshift FIXTURES_PATH
24
+
25
+ ActiveRecord::Base.silence do
26
+ ActiveRecord::Migration.verbose = false
27
+ load File.join(FIXTURES_PATH, 'schema.rb')
28
+ end
29
+
30
+ Fixtures.create_fixtures(FIXTURES_PATH, ActiveRecord::Base.connection.tables)
31
+
32
+ class Test::Unit::TestCase
33
+ end
data/test/test_base.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'helper'
2
+
3
+ class TestBase < Test::Unit::TestCase
4
+ should "raise nothing when an association's conditions hash doesn't use MetaWhere" do
5
+ assert_nothing_raised do
6
+ Company.all
7
+ end
8
+ end
9
+
10
+ should "raise an exception when MetaWhere::Columns are in :conditions of an association" do
11
+ assert_raises MetaWhere::MetaWhereInAssociationError do
12
+ InvalidCompany.all
13
+ end
14
+ end
15
+
16
+ should "raise an exception when MetaWhere::Conditions are in :conditions of an association" do
17
+ assert_raises MetaWhere::MetaWhereInAssociationError do
18
+ InvalidDeveloper.all
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,476 @@
1
+ require 'helper'
2
+
3
+ class TestRelations < Test::Unit::TestCase
4
+ context "A company relation" do
5
+ setup do
6
+ @r = Company.scoped
7
+ end
8
+
9
+ should "behave as expected with one-level hash params" do
10
+ results = @r.where(:name => 'Initech')
11
+ assert_equal 1, results.size
12
+ assert_equal results.first, Company.find_by_name('Initech')
13
+ end
14
+
15
+ should "behave as expected with nested hash params" do
16
+ results = @r.where(
17
+ :developers => {
18
+ :name => 'Peter Gibbons',
19
+ :notes => {
20
+ :note => 'A straight shooter with upper management written all over him.'
21
+ }
22
+ }
23
+ )
24
+ assert_raises ActiveRecord::StatementInvalid do
25
+ results.all
26
+ end
27
+ results = results.joins(:developers => :notes)
28
+ assert_equal 1, results.size
29
+ assert_equal results.first, Company.find_by_name('Initech')
30
+ end
31
+
32
+ should "allow selection of join type in association joins" do
33
+ assert_match /INNER JOIN/, @r.joins(:developers.inner).to_sql
34
+ assert_match /LEFT OUTER JOIN/, @r.joins(:developers.outer).to_sql
35
+ end
36
+
37
+ should "only join once even if two join types are used" do
38
+ assert_equal 1, @r.joins(:developers.inner, :developers.outer).to_sql.scan("JOIN").size
39
+ end
40
+
41
+ should "allow SQL functions via Symbol#func" do
42
+ assert_equal @r.where(:name.in => ['Initech', 'Mission Data']), @r.joins(:developers).group('companies.id').having(:developers => {:count.func(:id).gt => 2}).all
43
+ end
44
+
45
+ should "allow SQL functions via Symbol#[]" do
46
+ assert_equal @r.where(:name.in => ['Initech', 'Mission Data']), @r.joins(:developers).group('companies.id').having(:developers => {:count[:id].gt => 2}).all
47
+ end
48
+
49
+ should "allow SQL functions in select clause" do
50
+ assert_equal [3,2,3], @r.joins(:developers).group('companies.id').select(:count[Developer.arel_table[:id]].as(:developers_count)).map {|c| c.developers_count}
51
+ end
52
+
53
+ should "allow operators on MetaWhere::Function objects" do
54
+ assert_equal @r.where(:name.in => ['Initech', 'Mission Data']), @r.joins(:developers).group('companies.id').having(:developers => [:count[:id] > 2]).all
55
+ end
56
+
57
+ should "join multiple parameters to an SQL function with commas" do
58
+ assert_match /concat\("companies"."id","companies"."name"\) LIKE '%blah%'/, @r.where(:concat[:id,:name].matches => '%blah%').to_sql
59
+ end
60
+
61
+ should "create new records with values from equality predicates" do
62
+ assert_equal "New Company",
63
+ @r.where(:name => 'New Company').new.name
64
+ assert_equal "New Company",
65
+ @r.where(:name.eq => 'New Company').new.name
66
+ assert_equal "New Company",
67
+ @r.where(:name.eq % 'New Company').new.name
68
+ end
69
+
70
+ should "create new records with values from equality predicates through an association with a scope applied" do
71
+ assert_equal "New Developer",
72
+ Company.new.developers.new_dev.build.name
73
+ end
74
+
75
+ should "create new records with values from equality predicates using last supplied predicate" do
76
+ assert_equal "Newer Company",
77
+ @r.where(:name => 'New Company').where(:name => 'Newer Company').new.name
78
+ assert_equal "Newer Company",
79
+ @r.where(:name.eq => 'New Company').where(:name.eq => 'Newer Company').new.name
80
+ assert_equal "Newer Company",
81
+ @r.where(:name.eq % 'New Company').where(:name.eq % 'Newer Company').new.name
82
+ end
83
+
84
+ should "behave as expected with SQL interpolation" do
85
+ results = @r.where('name like ?', '%tech')
86
+ assert_equal 1, results.size
87
+ assert_equal results.first, Company.find_by_name('Initech')
88
+ end
89
+
90
+ should "behave as expected with mixed hash and SQL interpolation" do
91
+ results = @r.where('name like ?', '%tech').where(:created_at => 100.years.ago..Time.now)
92
+ assert_equal 1, results.size
93
+ assert_equal results.first, Company.find_by_name('Initech')
94
+ end
95
+
96
+ should "behave as expected with empty arrays" do
97
+ none = @r.where("3 = 1").all
98
+ assert_equal none, @r.where(:name => []).all
99
+ assert_equal none, @r.where(:name.in => []).all
100
+ end
101
+
102
+ should "allow multiple condition params in a single where" do
103
+ results = @r.where(['name like ?', '%tech'], :created_at => 100.years.ago..Time.now)
104
+ assert_equal 1, results.size
105
+ assert_equal results.first, Company.find_by_name('Initech')
106
+ end
107
+
108
+ should "allow predicate method selection on hash keys" do
109
+ assert_equal @r.where(:name.eq => 'Initech').all, @r.where(:name => 'Initech').all
110
+ assert_equal @r.where(:name.matches => 'Mission%').all, @r.where('name LIKE ?', 'Mission%').all
111
+ end
112
+
113
+ should "allow operators to select predicate methods" do
114
+ assert_equal @r.where(:name ^ 'Initech').all, @r.where('name != ?', 'Initech').all
115
+ assert_equal @r.where(:id + [1,3]).all, @r.where('id IN (?)', [1,3]).all
116
+ assert_equal @r.where(:name =~ 'Advanced%').all, @r.where('name LIKE ?', 'Advanced%').all
117
+ end
118
+
119
+ should "use % 'substitution' for hash key predicate methods" do
120
+ assert_equal @r.where(:name.like % 'Advanced%').all, @r.where('name LIKE ?', 'Advanced%').all
121
+ end
122
+
123
+ should "handle *_any predicates by creating or conditions" do
124
+ assert_match %r{ OR }, @r.where(:name.matches_any => ['%e%', '%a%']).to_sql
125
+ end
126
+
127
+ should "handle *_all predicates by creating AND conditions" do
128
+ assert_match %r{ AND }, @r.where(:name.matches_all => ['%e%', '%a%']).to_sql
129
+ end
130
+
131
+ should "allow | and & for compound predicates" do
132
+ assert_equal @r.where(:name.like % 'Advanced%' | :name.like % 'Init%').all,
133
+ @r.where('name LIKE ? OR name LIKE ?', 'Advanced%', 'Init%').all
134
+ assert_equal @r.where(:name.like % 'Mission%' & :name.like % '%Data').all,
135
+ @r.where('name LIKE ? AND name LIKE ?', 'Mission%', '%Data').all
136
+ end
137
+
138
+ should "nest AND/OR conditions with parentheses" do
139
+ query = @r.where({
140
+ :developers => {:name.like => '%e%'}
141
+ } & (:name.like % 'Advanced%' | :name.like % 'Init%')
142
+ )
143
+ assert_match /WHERE "developers"."name" LIKE '%e%' AND \("companies"."name" LIKE 'Advanced%' OR "companies"."name" LIKE 'Init%'\)/,
144
+ query.to_sql
145
+ end
146
+
147
+ should "create an AND NOT on binary minus between conditions" do
148
+ assert_equal @r.where(:name.like % 'Mission%' - :name.like % '%Data').all,
149
+ @r.where('name LIKE ? AND name NOT LIKE ?', 'Mission%', '%Data').all
150
+ end
151
+
152
+ should "create a NOT on unary minus on condition" do
153
+ assert_equal @r.where(-(:name.like % '%Data')).all,
154
+ @r.where('name NOT LIKE ?', '%Data').all
155
+ end
156
+
157
+ should "allow nested conditions hashes to have array values" do
158
+ assert_equal @r.joins(:data_types).where(:data_types => {:dec => 2..5}).all,
159
+ @r.joins(:data_types).where(:data_types => [:dec >= 2, :dec <= 5]).all
160
+ end
161
+
162
+ should "allow nested conditions hashes to have MetaWhere::Condition values" do
163
+ assert_equal @r.joins(:data_types).where(:data_types => {:dec.gt => 2}).all,
164
+ @r.joins(:data_types).where(:data_types => :dec > 2).all
165
+ end
166
+
167
+ should "allow nested conditions hashes to have MetaWhere::And values" do
168
+ assert_equal @r.joins(:data_types).where(:data_types => {:dec => 2..5}).all,
169
+ @r.joins(:data_types).where(:data_types => ((:dec >= 2) & (:dec <= 5))).all
170
+ end
171
+
172
+ should "allow nested conditions hashes to have MetaWhere::Or values" do
173
+ assert_equal @r.joins(:data_types).where(:data_types => [:dec.gteq % 2 | :bln.eq % true]).all,
174
+ @r.joins(:data_types).where(:data_types => ((:dec >= 2) | (:bln >> true))).all
175
+ end
176
+
177
+ should "allow nested conditions hashes to have MetaWhere::Not values" do
178
+ assert_equal @r.joins(:data_types).where(:data_types => [:dec.gteq % 2 - :bln.eq % true]).all,
179
+ @r.joins(:data_types).where(:data_types => ((:dec >= 2) - (:bln >> true))).all
180
+ end
181
+
182
+ should "allow combinations of options that no sane developer would ever try to use" do
183
+ assert_equal @r.find_all_by_name('Initech'),
184
+ @r.joins(:data_types, :developers => [:projects, :notes]).
185
+ where(
186
+ {
187
+ :data_types => [:dec > 3, {:bln.eq => true}]
188
+ } &
189
+ {
190
+ :developers => {
191
+ :name.like => 'Peter Gibbons'
192
+ }
193
+ } &
194
+ {
195
+ :developers => {
196
+ :projects => {
197
+ :estimated_hours.gteq => 1000
198
+ },
199
+ :notes => [:note.matches % '%straight shooter%']
200
+ }
201
+ }
202
+ ).uniq
203
+ end
204
+
205
+ should "allow ordering by attributes in ascending order" do
206
+ last_created = @r.all.sort {|a, b| a.created_at <=> b.created_at}.last
207
+ assert_equal last_created, @r.order(:created_at.asc).last
208
+ end
209
+
210
+ should "allow ordering by attributes in descending order" do
211
+ last_created = @r.all.sort {|a, b| a.created_at <=> b.created_at}.last
212
+ assert_equal last_created, @r.order(:created_at.desc).first
213
+ end
214
+
215
+ should "allow ordering by attributes on nested associations" do
216
+ highest_paying = Developer.order(:salary.desc).first.company
217
+ assert_equal highest_paying, @r.joins(:developers).order(:developers => :salary.desc).first
218
+ end
219
+
220
+ context "with eager-loaded developers" do
221
+ setup do
222
+ @r = @r.includes(:developers).where(:developers => {:name => 'Ernie Miller'})
223
+ end
224
+
225
+ should "return the expected result" do
226
+ assert_equal Company.where(:name => 'Mission Data'), @r.all
227
+ end
228
+
229
+ should "generate debug SQL with the joins in place" do
230
+ assert_match /LEFT OUTER JOIN "developers"/, @r.debug_sql
231
+ end
232
+ end
233
+ end
234
+
235
+ context "A relation from an STI class" do
236
+ setup do
237
+ @r = TimeAndMaterialsProject.scoped
238
+ end
239
+
240
+ should "return results from the designated class only" do
241
+ assert_equal 2, @r.size
242
+ assert @r.all? {|r| r.is_a?(TimeAndMaterialsProject)}
243
+ end
244
+
245
+ should "inherit the default scope of the parent class" do
246
+ assert_match /IS NOT NULL/, @r.to_sql
247
+ end
248
+
249
+ should "allow use of scopes in the parent class" do
250
+ assert_equal 1, @r.hours_lte_100.size
251
+ assert_equal 'MetaSearch Development', @r.hours_lte_100.first.name
252
+ end
253
+ end
254
+
255
+ context "A merged relation with a different base class" do
256
+ setup do
257
+ @r = Developer.where(:salary.gteq % 70000) & Company.where(:name.matches % 'Initech')
258
+ end
259
+
260
+ should "keep the table of the second relation intact in the query" do
261
+ assert_match /#{Company.quoted_table_name}."name"/, @r.to_sql
262
+ end
263
+
264
+ should "return expected results" do
265
+ assert_equal ['Peter Gibbons', 'Michael Bolton'], @r.all.map(&:name)
266
+ end
267
+ end
268
+
269
+ context "A merged relation with a different base class and a MetaWhere::JoinType in joins" do
270
+ setup do
271
+ @r = Developer.where(:salary.gteq % 70000) & Company.where(:name.matches % 'Initech').joins(:data_types.outer)
272
+ end
273
+
274
+ should "merge the JoinType under the association for the merged relation" do
275
+ assert_match /LEFT OUTER JOIN #{DataType.quoted_table_name} ON #{DataType.quoted_table_name}."company_id" = #{Company.quoted_table_name}."id"/,
276
+ @r.to_sql
277
+ end
278
+ end
279
+
280
+ context "A merged relation with with a different base class and an alternate association" do
281
+ setup do
282
+ @r = Company.scoped.merge(Developer.where(:salary.gt => 70000), :slackers)
283
+ end
284
+
285
+ should "use the proper association" do
286
+ assert_match Company.joins(:slackers).where(:slackers => {:salary.gt => 70000}).to_sql,
287
+ @r.to_sql
288
+ end
289
+
290
+ should "return expected results" do
291
+ assert_equal ['Initech', 'Advanced Optical Solutions'], @r.all.map(&:name)
292
+ end
293
+ end
294
+
295
+ context "A Person relation" do
296
+ setup do
297
+ @r = Person.scoped
298
+ end
299
+
300
+ context "with self-referencing joins" do
301
+ setup do
302
+ @r = @r.where(:children => {:children => {:name => 'Jacob'}}).joins(:children => :children)
303
+ end
304
+
305
+ should "join the table multiple times with aliases" do
306
+ assert_equal 2, @r.to_sql.scan('INNER JOIN').size
307
+ assert_match /INNER JOIN "people" "children_people"/, @r.to_sql
308
+ assert_match /INNER JOIN "people" "children_people_2"/, @r.to_sql
309
+ end
310
+
311
+ should "place the condition on the correct join" do
312
+ assert_match /"children_people_2"."name" = 'Jacob'/, @r.to_sql
313
+ end
314
+
315
+ should "return the expected result" do
316
+ assert_equal Person.where(:name => 'Abraham'), @r.all
317
+ end
318
+ end
319
+
320
+ context "with self-referencing joins on parent and children" do
321
+ setup do
322
+ @r = @r.where(:children => {:children => {:parent => {:parent => {:name => 'Abraham'}}}}).
323
+ joins(:children => {:children => {:parent => :parent}})
324
+ end
325
+
326
+ should "join the table multiple times with aliases" do
327
+ assert_equal 4, @r.to_sql.scan('INNER JOIN').size
328
+ assert_match /INNER JOIN "people" "children_people"/, @r.to_sql
329
+ assert_match /INNER JOIN "people" "children_people_2"/, @r.to_sql
330
+ assert_match /INNER JOIN "people" "parents_people"/, @r.to_sql
331
+ assert_match /INNER JOIN "people" "parents_people_2"/, @r.to_sql
332
+ end
333
+
334
+ should "place the condition on the correct join" do
335
+ assert_match /"parents_people_2"."name" = 'Abraham'/, @r.to_sql
336
+ end
337
+
338
+ should "return the expected result" do
339
+ assert_equal Person.where(:name => 'Abraham'), @r.all
340
+ end
341
+ end
342
+ end
343
+
344
+ context "A Developer relation" do
345
+ setup do
346
+ @r = Developer.scoped
347
+ end
348
+
349
+
350
+ should "create new records with multiple values from equality predicates" do
351
+ object = @r.where(:name => 'New Developer', :salary => 10000).new
352
+ assert_equal "New Developer", object.name
353
+ assert_equal 10000, object.salary
354
+ end
355
+
356
+ should "allow a hash with another relation as a value" do
357
+ query = @r.where(:company_id => Company.where(:name.matches => '%i%'))
358
+ assert_match /IN \(1, 2, 3\)/, query.to_sql
359
+ assert_same_elements Developer.all, query.all
360
+ end
361
+
362
+ should "merge multiple conditions on the same column and predicate with ANDs" do
363
+ assert_match /"developers"."name" = 'blah' AND "developers"."name" = 'blah2'/,
364
+ @r.where(:name => 'blah').where(:name => 'blah2').to_sql
365
+ assert_match /"developers"."name" LIKE '%blah%' AND "developers"."name" LIKE '%blah2%'/,
366
+ @r.where(:name.matches => '%blah%').where(:name.matches => '%blah2%').to_sql
367
+ end
368
+
369
+ should "merge multiple conditions on the same column but different predicate with ANDs" do
370
+ assert_match /"developers"."name" = 'blah' AND "developers"."name" LIKE '%blah2%'/,
371
+ @r.where(:name => 'blah').where(:name.matches => '%blah2%').to_sql
372
+ end
373
+
374
+ context "with eager-loaded companies" do
375
+ setup do
376
+ @r = @r.includes(:company)
377
+ end
378
+
379
+ should "eager load companies" do
380
+ assert_equal true, @r.all.first.company.loaded?
381
+ end
382
+ end
383
+
384
+ context "with eager-loaded companies and an order value" do
385
+ setup do
386
+ @r = @r.includes(:company).order(:companies => :name.asc)
387
+ end
388
+
389
+ should "eager load companies" do
390
+ assert_equal true, @r.all.first.company.loaded?
391
+ end
392
+ end
393
+ end
394
+
395
+ context "A relation" do
396
+ should "allow conditions on a belongs_to polymorphic association with an object" do
397
+ dev = Developer.first
398
+ assert_equal dev, Note.where(:notable.type(Developer) => dev).first.notable
399
+ end
400
+
401
+ should "allow conditions on a belongs_to association with an object" do
402
+ company = Company.first
403
+ assert_same_elements Developer.where(:company_id => company.id),
404
+ Developer.where(:company => company).all
405
+ end
406
+
407
+ should "allow conditions on a has_and_belongs_to_many association with an object" do
408
+ project = Project.first
409
+ assert_same_elements Developer.joins(:projects).where(:projects => {:id => project.id}),
410
+ Developer.joins(:projects).where(:projects => project)
411
+ end
412
+
413
+ should "not allow an object of the wrong class to be passed to a non-polymorphic association" do
414
+ company = Company.first
415
+ assert_raise ArgumentError do
416
+ Project.where(:developers => company).all
417
+ end
418
+ end
419
+
420
+ should "allow multiple AR objects on the value side of an association condition" do
421
+ projects = [Project.first, Project.last]
422
+ assert_same_elements Developer.joins(:projects).where(:projects => {:id => projects.map(&:id)}),
423
+ Developer.joins(:projects).where(:projects => projects)
424
+ end
425
+
426
+ should "allow multiple different kinds of AR objects on the value side of a polymorphic belongs_to" do
427
+ dev1 = Developer.first
428
+ dev2 = Developer.last
429
+ project = Project.first
430
+ company = Company.first
431
+ assert_same_elements Note.where(
432
+ {:notable_type => project.class.base_class.name, :notable_id => project.id} |
433
+ {:notable_type => dev1.class.base_class.name, :notable_id => [dev1.id, dev2.id]} |
434
+ {:notable_type => company.class.base_class.name, :notable_id => company.id}
435
+ ),
436
+ Note.where(:notable => [dev1, dev2, project, company]).all
437
+ end
438
+
439
+ should "allow an AR object on the value side of a polymorphic has_many condition" do
440
+ note = Note.first
441
+ peter = Developer.first
442
+ assert_equal [peter],
443
+ Developer.joins(:notes).where(:notes => note).all
444
+ end
445
+
446
+ should "allow a join of a polymorphic belongs_to relation with a type specified" do
447
+ dev = Developer.first
448
+ company = Company.first
449
+ assert_equal [company.notes.first],
450
+ Note.joins(:notable.type(Company) => :developers).where(:notable => {:developers => dev}).all
451
+ end
452
+
453
+ should "allow selection of a specific polymorphic join by name in the where clause" do
454
+ dev = Developer.first
455
+ company = Company.first
456
+ project = Project.first
457
+ dev_note = dev.notes.first
458
+ company_note = company.notes.first
459
+ project_note = project.notes.first
460
+ # Have to use outer joins since one inner join will cause remaining rows to be missing
461
+ # This is pretty convoluted, and way beyond the normal use case for polymorphic belongs_to
462
+ # joins anyway.
463
+ @r = Note.joins(:notable.type(Company).outer => :notes.outer, :notable.type(Developer).outer => :notes.outer, :notable.type(Project).outer => :notes.outer)
464
+ assert_equal [dev_note],
465
+ @r.where(:notable.type(Developer) => {:notes => dev_note}).all
466
+ assert_equal [company_note],
467
+ @r.where(:notable.type(Company) => {:notes => company_note}).all
468
+ assert_equal [project_note],
469
+ @r.where(:notable.type(Project) => {:notes => project_note}).all
470
+ end
471
+
472
+ should "maintain belongs_to conditions in a polymorphic join" do
473
+ assert_match /1=1/, Note.joins(:notable.type(Company)).to_sql
474
+ end
475
+ end
476
+ end