maksar-meta_where 1.0.4

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 (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