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