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.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/CHANGELOG +90 -0
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.rdoc +343 -0
- data/Rakefile +11 -0
- data/lib/core_ext/hash.rb +5 -0
- data/lib/core_ext/symbol.rb +39 -0
- data/lib/core_ext/symbol_operators.rb +48 -0
- data/lib/meta_where.rb +51 -0
- data/lib/meta_where/association_reflection.rb +51 -0
- data/lib/meta_where/column.rb +31 -0
- data/lib/meta_where/compound.rb +20 -0
- data/lib/meta_where/condition.rb +32 -0
- data/lib/meta_where/condition_operators.rb +19 -0
- data/lib/meta_where/function.rb +108 -0
- data/lib/meta_where/join_dependency.rb +105 -0
- data/lib/meta_where/join_type.rb +43 -0
- data/lib/meta_where/not.rb +13 -0
- data/lib/meta_where/relation.rb +290 -0
- data/lib/meta_where/utility.rb +51 -0
- data/lib/meta_where/version.rb +3 -0
- data/lib/meta_where/visitors/attribute.rb +58 -0
- data/lib/meta_where/visitors/predicate.rb +149 -0
- data/lib/meta_where/visitors/visitor.rb +52 -0
- data/meta_where.gemspec +48 -0
- data/test/fixtures/companies.yml +17 -0
- data/test/fixtures/company.rb +7 -0
- data/test/fixtures/data_type.rb +3 -0
- data/test/fixtures/data_types.yml +15 -0
- data/test/fixtures/developer.rb +5 -0
- data/test/fixtures/developers.yml +55 -0
- data/test/fixtures/developers_projects.yml +25 -0
- data/test/fixtures/fixed_bid_project.rb +2 -0
- data/test/fixtures/invalid_company.rb +4 -0
- data/test/fixtures/invalid_developer.rb +4 -0
- data/test/fixtures/note.rb +3 -0
- data/test/fixtures/notes.yml +95 -0
- data/test/fixtures/people.yml +14 -0
- data/test/fixtures/person.rb +4 -0
- data/test/fixtures/project.rb +7 -0
- data/test/fixtures/projects.yml +29 -0
- data/test/fixtures/schema.rb +53 -0
- data/test/fixtures/time_and_materials_project.rb +2 -0
- data/test/helper.rb +33 -0
- data/test/test_base.rb +21 -0
- data/test/test_relations.rb +455 -0
- metadata +173 -0
@@ -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
|
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,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
|