maksar-meta_where 1.0.4

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 +343 -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.rb +51 -0
  12. data/lib/meta_where/association_reflection.rb +51 -0
  13. data/lib/meta_where/column.rb +31 -0
  14. data/lib/meta_where/compound.rb +20 -0
  15. data/lib/meta_where/condition.rb +32 -0
  16. data/lib/meta_where/condition_operators.rb +19 -0
  17. data/lib/meta_where/function.rb +108 -0
  18. data/lib/meta_where/join_dependency.rb +105 -0
  19. data/lib/meta_where/join_type.rb +43 -0
  20. data/lib/meta_where/not.rb +13 -0
  21. data/lib/meta_where/relation.rb +290 -0
  22. data/lib/meta_where/utility.rb +51 -0
  23. data/lib/meta_where/version.rb +3 -0
  24. data/lib/meta_where/visitors/attribute.rb +58 -0
  25. data/lib/meta_where/visitors/predicate.rb +149 -0
  26. data/lib/meta_where/visitors/visitor.rb +52 -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 +5 -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 +455 -0
  49. metadata +173 -0
@@ -0,0 +1,14 @@
1
+ grandpa:
2
+ name : Abraham
3
+ id : 1
4
+ parent_id: nil
5
+
6
+ father:
7
+ name : Isaac
8
+ id : 2
9
+ parent_id: 1
10
+
11
+ son:
12
+ name : Jacob
13
+ id : 3
14
+ parent_id: 2
@@ -0,0 +1,4 @@
1
+ class Person < ActiveRecord::Base
2
+ belongs_to :parent, :class_name => 'Person', :foreign_key => :parent_id
3
+ has_many :children, :class_name => 'Person', :foreign_key => :parent_id
4
+ end
@@ -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
@@ -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
@@ -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,455 @@
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 using last supplied predicate" do
71
+ assert_equal "Newer Company",
72
+ @r.where(:name => 'New Company').where(:name => 'Newer Company').new.name
73
+ assert_equal "Newer Company",
74
+ @r.where(:name.eq => 'New Company').where(:name.eq => 'Newer Company').new.name
75
+ assert_equal "Newer Company",
76
+ @r.where(:name.eq % 'New Company').where(:name.eq % 'Newer Company').new.name
77
+ end
78
+
79
+ should "behave as expected with SQL interpolation" do
80
+ results = @r.where('name like ?', '%tech')
81
+ assert_equal 1, results.size
82
+ assert_equal results.first, Company.find_by_name('Initech')
83
+ end
84
+
85
+ should "behave as expected with mixed hash and SQL interpolation" do
86
+ results = @r.where('name like ?', '%tech').where(:created_at => 100.years.ago..Time.now)
87
+ assert_equal 1, results.size
88
+ assert_equal results.first, Company.find_by_name('Initech')
89
+ end
90
+
91
+ should "behave as expected with empty arrays" do
92
+ none = @r.where("3 = 1").all
93
+ assert_equal none, @r.where(:name => []).all
94
+ assert_equal none, @r.where(:name.in => []).all
95
+ end
96
+
97
+ should "allow multiple condition params in a single where" do
98
+ results = @r.where(['name like ?', '%tech'], :created_at => 100.years.ago..Time.now)
99
+ assert_equal 1, results.size
100
+ assert_equal results.first, Company.find_by_name('Initech')
101
+ end
102
+
103
+ should "allow predicate method selection on hash keys" do
104
+ assert_equal @r.where(:name.eq => 'Initech').all, @r.where(:name => 'Initech').all
105
+ assert_equal @r.where(:name.matches => 'Mission%').all, @r.where('name LIKE ?', 'Mission%').all
106
+ end
107
+
108
+ should "allow operators to select predicate methods" do
109
+ assert_equal @r.where(:name ^ 'Initech').all, @r.where('name != ?', 'Initech').all
110
+ assert_equal @r.where(:id + [1,3]).all, @r.where('id IN (?)', [1,3]).all
111
+ assert_equal @r.where(:name =~ 'Advanced%').all, @r.where('name LIKE ?', 'Advanced%').all
112
+ end
113
+
114
+ should "use % 'substitution' for hash key predicate methods" do
115
+ assert_equal @r.where(:name.like % 'Advanced%').all, @r.where('name LIKE ?', 'Advanced%').all
116
+ end
117
+
118
+ should "handle *_any predicates by creating or conditions" do
119
+ assert_match %r{ OR }, @r.where(:name.matches_any => ['%e%', '%a%']).to_sql
120
+ end
121
+
122
+ should "handle *_all predicates by creating AND conditions" do
123
+ assert_match %r{ AND }, @r.where(:name.matches_all => ['%e%', '%a%']).to_sql
124
+ end
125
+
126
+ should "allow | and & for compound predicates" do
127
+ assert_equal @r.where(:name.like % 'Advanced%' | :name.like % 'Init%').all,
128
+ @r.where('name LIKE ? OR name LIKE ?', 'Advanced%', 'Init%').all
129
+ assert_equal @r.where(:name.like % 'Mission%' & :name.like % '%Data').all,
130
+ @r.where('name LIKE ? AND name LIKE ?', 'Mission%', '%Data').all
131
+ end
132
+
133
+ should "create an AND NOT on binary minus between conditions" do
134
+ assert_equal @r.where(:name.like % 'Mission%' - :name.like % '%Data').all,
135
+ @r.where('name LIKE ? AND name NOT LIKE ?', 'Mission%', '%Data').all
136
+ end
137
+
138
+ should "create a NOT on unary minus on condition" do
139
+ assert_equal @r.where(-(:name.like % '%Data')).all,
140
+ @r.where('name NOT LIKE ?', '%Data').all
141
+ end
142
+
143
+ should "allow nested conditions hashes to have array values" do
144
+ assert_equal @r.joins(:data_types).where(:data_types => {:dec => 2..5}).all,
145
+ @r.joins(:data_types).where(:data_types => [:dec >= 2, :dec <= 5]).all
146
+ end
147
+
148
+ should "allow nested conditions hashes to have MetaWhere::Condition values" do
149
+ assert_equal @r.joins(:data_types).where(:data_types => {:dec.gt => 2}).all,
150
+ @r.joins(:data_types).where(:data_types => :dec > 2).all
151
+ end
152
+
153
+ should "allow nested conditions hashes to have MetaWhere::And values" do
154
+ assert_equal @r.joins(:data_types).where(:data_types => {:dec => 2..5}).all,
155
+ @r.joins(:data_types).where(:data_types => ((:dec >= 2) & (:dec <= 5))).all
156
+ end
157
+
158
+ should "allow nested conditions hashes to have MetaWhere::Or values" do
159
+ assert_equal @r.joins(:data_types).where(:data_types => [:dec.gteq % 2 | :bln.eq % true]).all,
160
+ @r.joins(:data_types).where(:data_types => ((:dec >= 2) | (:bln >> true))).all
161
+ end
162
+
163
+ should "allow nested conditions hashes to have MetaWhere::Not values" do
164
+ assert_equal @r.joins(:data_types).where(:data_types => [:dec.gteq % 2 - :bln.eq % true]).all,
165
+ @r.joins(:data_types).where(:data_types => ((:dec >= 2) - (:bln >> true))).all
166
+ end
167
+
168
+ should "allow combinations of options that no sane developer would ever try to use" do
169
+ assert_equal @r.find_all_by_name('Initech'),
170
+ @r.joins(:data_types, :developers => [:projects, :notes]).
171
+ where(
172
+ {
173
+ :data_types => [:dec > 3, {:bln.eq => true}]
174
+ } &
175
+ {
176
+ :developers => {
177
+ :name.like => 'Peter Gibbons'
178
+ }
179
+ } &
180
+ {
181
+ :developers => {
182
+ :projects => {
183
+ :estimated_hours.gteq => 1000
184
+ },
185
+ :notes => [:note.matches % '%straight shooter%']
186
+ }
187
+ }
188
+ ).uniq
189
+ end
190
+
191
+ should "allow ordering by attributes in ascending order" do
192
+ last_created = @r.all.sort {|a, b| a.created_at <=> b.created_at}.last
193
+ assert_equal last_created, @r.order(:created_at.asc).last
194
+ end
195
+
196
+ should "allow ordering by attributes in descending order" do
197
+ last_created = @r.all.sort {|a, b| a.created_at <=> b.created_at}.last
198
+ assert_equal last_created, @r.order(:created_at.desc).first
199
+ end
200
+
201
+ should "allow ordering by attributes on nested associations" do
202
+ highest_paying = Developer.order(:salary.desc).first.company
203
+ assert_equal highest_paying, @r.joins(:developers).order(:developers => :salary.desc).first
204
+ end
205
+
206
+ context "with eager-loaded developers" do
207
+ setup do
208
+ @r = @r.includes(:developers).where(:developers => {:name => 'Ernie Miller'})
209
+ end
210
+
211
+ should "return the expected result" do
212
+ assert_equal Company.where(:name => 'Mission Data'), @r.all
213
+ end
214
+
215
+ should "generate debug SQL with the joins in place" do
216
+ assert_match /LEFT OUTER JOIN "developers"/, @r.debug_sql
217
+ end
218
+ end
219
+ end
220
+
221
+ context "A relation from an STI class" do
222
+ setup do
223
+ @r = TimeAndMaterialsProject.scoped
224
+ end
225
+
226
+ should "return results from the designated class only" do
227
+ assert_equal 2, @r.size
228
+ assert @r.all? {|r| r.is_a?(TimeAndMaterialsProject)}
229
+ end
230
+
231
+ should "inherit the default scope of the parent class" do
232
+ assert_match /IS NOT NULL/, @r.to_sql
233
+ end
234
+
235
+ should "allow use of scopes in the parent class" do
236
+ assert_equal 1, @r.hours_lte_100.size
237
+ assert_equal 'MetaSearch Development', @r.hours_lte_100.first.name
238
+ end
239
+ end
240
+
241
+ context "A merged relation with a different base class" do
242
+ setup do
243
+ @r = Developer.where(:salary.gteq % 70000) & Company.where(:name.matches % 'Initech')
244
+ end
245
+
246
+ should "keep the table of the second relation intact in the query" do
247
+ assert_match /#{Company.quoted_table_name}."name"/, @r.to_sql
248
+ end
249
+
250
+ should "return expected results" do
251
+ assert_equal ['Peter Gibbons', 'Michael Bolton'], @r.all.map(&:name)
252
+ end
253
+ end
254
+
255
+ context "A merged relation with a different base class and a MetaWhere::JoinType in joins" do
256
+ setup do
257
+ @r = Developer.where(:salary.gteq % 70000) & Company.where(:name.matches % 'Initech').joins(:data_types.outer)
258
+ end
259
+
260
+ should "merge the JoinType under the association for the merged relation" do
261
+ assert_match /LEFT OUTER JOIN #{DataType.quoted_table_name} ON #{DataType.quoted_table_name}."company_id" = #{Company.quoted_table_name}."id"/,
262
+ @r.to_sql
263
+ end
264
+ end
265
+
266
+ context "A merged relation with with a different base class and an alternate association" do
267
+ setup do
268
+ @r = Company.scoped.merge(Developer.where(:salary.gt => 70000), :slackers)
269
+ end
270
+
271
+ should "use the proper association" do
272
+ assert_match Company.joins(:slackers).where(:slackers => {:salary.gt => 70000}).to_sql,
273
+ @r.to_sql
274
+ end
275
+
276
+ should "return expected results" do
277
+ assert_equal ['Initech', 'Advanced Optical Solutions'], @r.all.map(&:name)
278
+ end
279
+ end
280
+
281
+ context "A Person relation" do
282
+ setup do
283
+ @r = Person.scoped
284
+ end
285
+
286
+ context "with self-referencing joins" do
287
+ setup do
288
+ @r = @r.where(:children => {:children => {:name => 'Jacob'}}).joins(:children => :children)
289
+ end
290
+
291
+ should "join the table multiple times with aliases" do
292
+ assert_equal 2, @r.to_sql.scan('INNER JOIN').size
293
+ assert_match /INNER JOIN "people" "children_people"/, @r.to_sql
294
+ assert_match /INNER JOIN "people" "children_people_2"/, @r.to_sql
295
+ end
296
+
297
+ should "place the condition on the correct join" do
298
+ assert_match /"children_people_2"."name" = 'Jacob'/, @r.to_sql
299
+ end
300
+
301
+ should "return the expected result" do
302
+ assert_equal Person.where(:name => 'Abraham'), @r.all
303
+ end
304
+ end
305
+
306
+ context "with self-referencing joins on parent and children" do
307
+ setup do
308
+ @r = @r.where(:children => {:children => {:parent => {:parent => {:name => 'Abraham'}}}}).
309
+ joins(:children => {:children => {:parent => :parent}})
310
+ end
311
+
312
+ should "join the table multiple times with aliases" do
313
+ assert_equal 4, @r.to_sql.scan('INNER JOIN').size
314
+ assert_match /INNER JOIN "people" "children_people"/, @r.to_sql
315
+ assert_match /INNER JOIN "people" "children_people_2"/, @r.to_sql
316
+ assert_match /INNER JOIN "people" "parents_people"/, @r.to_sql
317
+ assert_match /INNER JOIN "people" "parents_people_2"/, @r.to_sql
318
+ end
319
+
320
+ should "place the condition on the correct join" do
321
+ assert_match /"parents_people_2"."name" = 'Abraham'/, @r.to_sql
322
+ end
323
+
324
+ should "return the expected result" do
325
+ assert_equal Person.where(:name => 'Abraham'), @r.all
326
+ end
327
+ end
328
+ end
329
+
330
+ context "A Developer relation" do
331
+ setup do
332
+ @r = Developer.scoped
333
+ end
334
+
335
+ should "allow a hash with another relation as a value" do
336
+ query = @r.where(:company_id => Company.where(:name.matches => '%i%'))
337
+ assert_match /IN \(1, 2, 3\)/, query.to_sql
338
+ assert_same_elements Developer.all, query.all
339
+ end
340
+
341
+ should "merge multiple conditions on the same column and predicate with ORs" do
342
+ assert_match /"developers"."name" = 'blah' OR "developers"."name" = 'blah2'/,
343
+ @r.where(:name => 'blah').where(:name => 'blah2').to_sql
344
+ assert_match /"developers"."name" LIKE '%blah%' OR "developers"."name" LIKE '%blah2%'/,
345
+ @r.where(:name.matches => '%blah%').where(:name.matches => '%blah2%').to_sql
346
+ end
347
+
348
+ should "merge multiple conditions on the same column but different predicate with ANDs" do
349
+ assert_match /"developers"."name" = 'blah' AND "developers"."name" LIKE '%blah2%'/,
350
+ @r.where(:name => 'blah').where(:name.matches => '%blah2%').to_sql
351
+ end
352
+
353
+ context "with eager-loaded companies" do
354
+ setup do
355
+ @r = @r.includes(:company)
356
+ end
357
+
358
+ should "eager load companies" do
359
+ assert_equal true, @r.all.first.company.loaded?
360
+ end
361
+ end
362
+
363
+ context "with eager-loaded companies and an order value" do
364
+ setup do
365
+ @r = @r.includes(:company).order(:companies => :name.asc)
366
+ end
367
+
368
+ should "eager load companies" do
369
+ assert_equal true, @r.all.first.company.loaded?
370
+ end
371
+ end
372
+ end
373
+
374
+ context "A relation" do
375
+ should "allow conditions on a belongs_to polymorphic association with an object" do
376
+ dev = Developer.first
377
+ assert_equal dev, Note.where(:notable.type(Developer) => dev).first.notable
378
+ end
379
+
380
+ should "allow conditions on a belongs_to association with an object" do
381
+ company = Company.first
382
+ assert_same_elements Developer.where(:company_id => company.id),
383
+ Developer.where(:company => company).all
384
+ end
385
+
386
+ should "allow conditions on a has_and_belongs_to_many association with an object" do
387
+ project = Project.first
388
+ assert_same_elements Developer.joins(:projects).where(:projects => {:id => project.id}),
389
+ Developer.joins(:projects).where(:projects => project)
390
+ end
391
+
392
+ should "not allow an object of the wrong class to be passed to a non-polymorphic association" do
393
+ company = Company.first
394
+ assert_raise ArgumentError do
395
+ Project.where(:developers => company).all
396
+ end
397
+ end
398
+
399
+ should "allow multiple AR objects on the value side of an association condition" do
400
+ projects = [Project.first, Project.last]
401
+ assert_same_elements Developer.joins(:projects).where(:projects => {:id => projects.map(&:id)}),
402
+ Developer.joins(:projects).where(:projects => projects)
403
+ end
404
+
405
+ should "allow multiple different kinds of AR objects on the value side of a polymorphic belongs_to" do
406
+ dev1 = Developer.first
407
+ dev2 = Developer.last
408
+ project = Project.first
409
+ company = Company.first
410
+ assert_same_elements Note.where(
411
+ {:notable_type => project.class.base_class.name, :notable_id => project.id} |
412
+ {:notable_type => dev1.class.base_class.name, :notable_id => [dev1.id, dev2.id]} |
413
+ {:notable_type => company.class.base_class.name, :notable_id => company.id}
414
+ ),
415
+ Note.where(:notable => [dev1, dev2, project, company]).all
416
+ end
417
+
418
+ should "allow an AR object on the value side of a polymorphic has_many condition" do
419
+ note = Note.first
420
+ peter = Developer.first
421
+ assert_equal [peter],
422
+ Developer.joins(:notes).where(:notes => note).all
423
+ end
424
+
425
+ should "allow a join of a polymorphic belongs_to relation with a type specified" do
426
+ dev = Developer.first
427
+ company = Company.first
428
+ assert_equal [company.notes.first],
429
+ Note.joins(:notable.type(Company) => :developers).where(:notable => {:developers => dev}).all
430
+ end
431
+
432
+ should "allow selection of a specific polymorphic join by name in the where clause" do
433
+ dev = Developer.first
434
+ company = Company.first
435
+ project = Project.first
436
+ dev_note = dev.notes.first
437
+ company_note = company.notes.first
438
+ project_note = project.notes.first
439
+ # Have to use outer joins since one inner join will cause remaining rows to be missing
440
+ # This is pretty convoluted, and way beyond the normal use case for polymorphic belongs_to
441
+ # joins anyway.
442
+ @r = Note.joins(:notable.type(Company).outer => :notes.outer, :notable.type(Developer).outer => :notes.outer, :notable.type(Project).outer => :notes.outer)
443
+ assert_equal [dev_note],
444
+ @r.where(:notable.type(Developer) => {:notes => dev_note}).all
445
+ assert_equal [company_note],
446
+ @r.where(:notable.type(Company) => {:notes => company_note}).all
447
+ assert_equal [project_note],
448
+ @r.where(:notable.type(Project) => {:notes => project_note}).all
449
+ end
450
+
451
+ should "maintain belongs_to conditions in a polymorphic join" do
452
+ assert_match /1=1/, Note.joins(:notable.type(Company)).to_sql
453
+ end
454
+ end
455
+ end