squeel 0.5.0 → 0.5.5
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/README.rdoc +115 -39
- data/lib/squeel/adapters/active_record.rb +22 -5
- data/lib/squeel/adapters/active_record/3.0/association_preload.rb +15 -0
- data/lib/squeel/adapters/active_record/3.0/compat.rb +143 -0
- data/lib/squeel/adapters/active_record/3.0/context.rb +67 -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 +67 -0
- data/lib/squeel/adapters/active_record/join_association.rb +10 -56
- data/lib/squeel/adapters/active_record/join_dependency.rb +22 -7
- data/lib/squeel/adapters/active_record/preloader.rb +21 -0
- data/lib/squeel/adapters/active_record/relation.rb +84 -38
- data/lib/squeel/context.rb +38 -0
- data/lib/squeel/dsl.rb +1 -1
- data/lib/squeel/nodes/join.rb +18 -0
- data/lib/squeel/nodes/key_path.rb +2 -2
- data/lib/squeel/nodes/stub.rb +5 -1
- data/lib/squeel/version.rb +1 -1
- data/lib/squeel/visitors.rb +2 -2
- data/lib/squeel/visitors/{order_visitor.rb → attribute_visitor.rb} +1 -2
- data/lib/squeel/visitors/predicate_visitor.rb +13 -11
- data/lib/squeel/visitors/symbol_visitor.rb +48 -0
- data/spec/helpers/squeel_helper.rb +17 -1
- data/spec/spec_helper.rb +31 -0
- data/spec/squeel/adapters/active_record/context_spec.rb +50 -0
- data/spec/squeel/adapters/active_record/join_association_spec.rb +1 -1
- data/spec/squeel/adapters/active_record/join_depdendency_spec.rb +1 -1
- data/spec/squeel/adapters/active_record/relation_spec.rb +166 -25
- data/spec/squeel/dsl_spec.rb +6 -6
- data/spec/squeel/nodes/join_spec.rb +16 -3
- data/spec/squeel/nodes/stub_spec.rb +12 -0
- data/spec/squeel/visitors/{order_visitor_spec.rb → attribute_visitor_spec.rb} +4 -5
- data/spec/squeel/visitors/predicate_visitor_spec.rb +18 -6
- data/spec/squeel/visitors/symbol_visitor_spec.rb +42 -0
- data/squeel.gemspec +2 -2
- metadata +21 -13
- data/lib/squeel/contexts/join_dependency_context.rb +0 -74
- data/lib/squeel/visitors/select_visitor.rb +0 -103
- data/spec/squeel/contexts/join_dependency_context_spec.rb +0 -43
- data/spec/squeel/visitors/select_visitor_spec.rb +0 -115
data/lib/squeel/nodes/stub.rb
CHANGED
@@ -33,7 +33,11 @@ module Squeel
|
|
33
33
|
|
34
34
|
def method_missing(method_id, *args)
|
35
35
|
super if method_id == :to_ary
|
36
|
-
|
36
|
+
if (args.size == 1) && (Class === args[0])
|
37
|
+
KeyPath.new(self, Join.new(method_id, Arel::InnerJoin, args[0]))
|
38
|
+
else
|
39
|
+
KeyPath.new(self, method_id)
|
40
|
+
end
|
37
41
|
end
|
38
42
|
|
39
43
|
def ==(value)
|
data/lib/squeel/version.rb
CHANGED
data/lib/squeel/visitors.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
require 'squeel/visitors/predicate_visitor'
|
2
|
-
require 'squeel/visitors/
|
3
|
-
require 'squeel/visitors/
|
2
|
+
require 'squeel/visitors/attribute_visitor'
|
3
|
+
require 'squeel/visitors/symbol_visitor'
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'squeel/visitors/base'
|
2
|
-
require 'squeel/contexts/join_dependency_context'
|
3
2
|
|
4
3
|
module Squeel
|
5
4
|
module Visitors
|
@@ -24,11 +23,7 @@ module Squeel
|
|
24
23
|
end
|
25
24
|
|
26
25
|
def visit_Array(o, parent)
|
27
|
-
|
28
|
-
sanitize_sql(o, parent)
|
29
|
-
else
|
30
|
-
o.map { |v| can_accept?(v) ? accept(v, parent) : v }.flatten
|
31
|
-
end
|
26
|
+
o.map { |v| can_accept?(v) ? accept(v, parent) : v }.flatten
|
32
27
|
end
|
33
28
|
|
34
29
|
def visit_Squeel_Nodes_KeyPath(o, parent)
|
@@ -37,13 +32,16 @@ module Squeel
|
|
37
32
|
accept(o.endpoint, parent)
|
38
33
|
end
|
39
34
|
|
35
|
+
def visit_Squeel_Nodes_Stub(o, parent)
|
36
|
+
contextualize(parent)[o.symbol]
|
37
|
+
end
|
38
|
+
|
40
39
|
def visit_Squeel_Nodes_Predicate(o, parent)
|
41
40
|
value = o.value
|
42
|
-
|
43
|
-
when Nodes::Function
|
44
|
-
value = accept(value, parent)
|
45
|
-
when Nodes::KeyPath
|
41
|
+
if Nodes::KeyPath === value
|
46
42
|
value = can_accept?(value.endpoint) ? accept(value, parent) : contextualize(traverse(value, parent))[value.endpoint.to_sym]
|
43
|
+
else
|
44
|
+
value = accept(value, parent) if can_accept?(value)
|
47
45
|
end
|
48
46
|
if Nodes::Function === o.expr
|
49
47
|
accept(o.expr, parent).send(o.method_name, value)
|
@@ -68,6 +66,10 @@ module Squeel
|
|
68
66
|
Arel::Nodes::NamedFunction.new(o.name, args, o.alias)
|
69
67
|
end
|
70
68
|
|
69
|
+
def visit_ActiveRecord_Relation(o, parent)
|
70
|
+
o.arel
|
71
|
+
end
|
72
|
+
|
71
73
|
def visit_Squeel_Nodes_Operation(o, parent)
|
72
74
|
args = o.args.map do |arg|
|
73
75
|
case arg
|
@@ -114,7 +116,7 @@ module Squeel
|
|
114
116
|
when Hash, Nodes::Predicate, Nodes::Unary, Nodes::Binary, Nodes::Nary
|
115
117
|
true
|
116
118
|
when Nodes::KeyPath
|
117
|
-
can_accept?(v.endpoint)
|
119
|
+
can_accept?(v.endpoint) && !(Nodes::Stub === v.endpoint)
|
118
120
|
when Array
|
119
121
|
(!v.empty? && v.all? {|val| can_accept?(val)})
|
120
122
|
else
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'squeel/visitors/base'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Visitors
|
5
|
+
class SymbolVisitor < Base
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
end
|
9
|
+
|
10
|
+
def accept(object, parent = nil)
|
11
|
+
visit(object, parent)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def visit_Array(o, parent)
|
17
|
+
o.map {|e| accept(e, parent)}.flatten
|
18
|
+
end
|
19
|
+
|
20
|
+
def visit_Hash(o, parent)
|
21
|
+
{}.tap do |hash|
|
22
|
+
o.each do |key, value|
|
23
|
+
hash[accept(key, parent)] = accept(value, parent)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def visit_Symbol(o, parent)
|
29
|
+
o
|
30
|
+
end
|
31
|
+
|
32
|
+
def visit_Squeel_Nodes_Stub(o, parent)
|
33
|
+
o.symbol
|
34
|
+
end
|
35
|
+
|
36
|
+
def visit_Squeel_Nodes_KeyPath(o, parent)
|
37
|
+
o.path_with_endpoint.reverse.map(&:to_sym).inject do |hash, key|
|
38
|
+
{key => hash}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def visit_Squeel_Nodes_Join(o, parent)
|
43
|
+
o.name
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -1,5 +1,21 @@
|
|
1
1
|
module SqueelHelper
|
2
2
|
def dsl(&block)
|
3
|
-
Squeel::DSL.
|
3
|
+
Squeel::DSL.eval(&block)
|
4
|
+
end
|
5
|
+
|
6
|
+
def queries_for
|
7
|
+
$queries_executed = []
|
8
|
+
yield
|
9
|
+
$queries_executed
|
10
|
+
ensure
|
11
|
+
%w{ BEGIN COMMIT }.each { |x| $queries_executed.delete(x) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def new_join_dependency(*args)
|
15
|
+
if defined?(ActiveRecord::Associations::JoinDependency)
|
16
|
+
ActiveRecord::Associations::JoinDependency.new(*args)
|
17
|
+
else
|
18
|
+
ActiveRecord::Associations::ClassMethods::JoinDependency.new(*args)
|
19
|
+
end
|
4
20
|
end
|
5
21
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,6 +2,30 @@ require 'machinist/active_record'
|
|
2
2
|
require 'sham'
|
3
3
|
require 'faker'
|
4
4
|
|
5
|
+
module ActiveRecord
|
6
|
+
class SQLCounter
|
7
|
+
IGNORED_SQL = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/,
|
8
|
+
/SELECT name\s+FROM sqlite_master\s+WHERE type = 'table' AND NOT name = 'sqlite_sequence'/]
|
9
|
+
|
10
|
+
# FIXME: this needs to be refactored so specific database can add their own
|
11
|
+
# ignored SQL. This ignored SQL is for Oracle.
|
12
|
+
IGNORED_SQL.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
$queries_executed = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(name, start, finish, message_id, values)
|
19
|
+
sql = values[:sql]
|
20
|
+
|
21
|
+
unless 'CACHE' == values[:name]
|
22
|
+
$queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
|
27
|
+
end
|
28
|
+
|
5
29
|
Dir[File.expand_path('../{helpers,support,blueprints}/*.rb', __FILE__)].each do |f|
|
6
30
|
require f
|
7
31
|
end
|
@@ -23,6 +47,13 @@ RSpec.configure do |config|
|
|
23
47
|
config.include SqueelHelper
|
24
48
|
end
|
25
49
|
|
50
|
+
RSpec::Matchers.define :be_like do |expected|
|
51
|
+
match do |actual|
|
52
|
+
actual.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip ==
|
53
|
+
expected.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
26
57
|
require 'squeel'
|
27
58
|
|
28
59
|
Squeel.configure do |config|
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
describe Context do
|
7
|
+
before do
|
8
|
+
@jd = new_join_dependency(Person, {
|
9
|
+
:children => {
|
10
|
+
:children => {
|
11
|
+
:parent => :parent
|
12
|
+
}
|
13
|
+
}
|
14
|
+
}, [])
|
15
|
+
@c = Context.new(@jd)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'finds associations' do
|
19
|
+
last_association = @jd.join_parts.last
|
20
|
+
next_to_last_association = @jd.join_parts[-2]
|
21
|
+
|
22
|
+
@c.find(:parent, next_to_last_association).should eq last_association
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'contextualizes join parts with the proper alias' do
|
26
|
+
table = @c.contextualize @jd.join_parts.last
|
27
|
+
table.table_alias.should eq 'parents_people_2'
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'contextualizes symbols as a generic table' do
|
31
|
+
table = @c.contextualize :table
|
32
|
+
table.name.should eq 'table'
|
33
|
+
table.table_alias.should be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'contextualizes polymorphic Join nodes to the arel_table of their klass' do
|
37
|
+
table = @c.contextualize Nodes::Join.new(:notable, Arel::InnerJoin, Article)
|
38
|
+
table.name.should eq 'articles'
|
39
|
+
table.table_alias.should be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'contextualizes non-polymorphic Join nodes to the table for their name' do
|
43
|
+
table = @c.contextualize Nodes::Join.new(:notes, Arel::InnerJoin)
|
44
|
+
table.name.should eq 'notes'
|
45
|
+
table.table_alias.should be_nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -5,7 +5,7 @@ module Squeel
|
|
5
5
|
|
6
6
|
describe '#predicate_visitor' do
|
7
7
|
|
8
|
-
it 'creates a predicate visitor with a
|
8
|
+
it 'creates a predicate visitor with a Context for the relation' do
|
9
9
|
relation = Person.joins({
|
10
10
|
:children => {
|
11
11
|
:children => {
|
@@ -23,9 +23,9 @@ module Squeel
|
|
23
23
|
|
24
24
|
end
|
25
25
|
|
26
|
-
describe '#
|
26
|
+
describe '#attribute_visitor' do
|
27
27
|
|
28
|
-
it 'creates an
|
28
|
+
it 'creates an attribute visitor with a Context for the relation' do
|
29
29
|
relation = Person.joins({
|
30
30
|
:children => {
|
31
31
|
:children => {
|
@@ -34,29 +34,9 @@ module Squeel
|
|
34
34
|
}
|
35
35
|
})
|
36
36
|
|
37
|
-
visitor = relation.
|
37
|
+
visitor = relation.attribute_visitor
|
38
38
|
|
39
|
-
visitor.should be_a Visitors::
|
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 '#select_visitor' do
|
47
|
-
|
48
|
-
it 'creates a select visitor with a JoinDependencyContext for the relation' do
|
49
|
-
relation = Person.joins({
|
50
|
-
:children => {
|
51
|
-
:children => {
|
52
|
-
:parent => :parent
|
53
|
-
}
|
54
|
-
}
|
55
|
-
})
|
56
|
-
|
57
|
-
visitor = relation.select_visitor
|
58
|
-
|
59
|
-
visitor.should be_a Visitors::SelectVisitor
|
39
|
+
visitor.should be_a Visitors::AttributeVisitor
|
60
40
|
table = visitor.contextualize(relation.join_dependency.join_parts.last)
|
61
41
|
table.table_alias.should eq 'parents_people_2'
|
62
42
|
end
|
@@ -209,6 +189,116 @@ module Squeel
|
|
209
189
|
|
210
190
|
end
|
211
191
|
|
192
|
+
describe '#includes' do
|
193
|
+
|
194
|
+
it 'builds options with a block' do
|
195
|
+
standard = Person.includes(:children => :children).where(:children => {:children => {:name => 'bob'}})
|
196
|
+
block = Person.includes{{children => children}}.where(:children => {:children => {:name => 'bob'}})
|
197
|
+
block.debug_sql.should eq standard.debug_sql
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'eager loads multiple top-level associations with a block' do
|
201
|
+
standard = Person.includes(:children, :articles, :comments).where(:children => {:name => 'bob'})
|
202
|
+
block = Person.includes{[children, articles, comments]}.where(:children => {:name => 'bob'})
|
203
|
+
block.debug_sql.should eq standard.debug_sql
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'eager loads polymorphic belongs_to associations' do
|
207
|
+
relation = Note.includes{notable(Article)}.where{{notable(Article) => {title => 'hey'}}}
|
208
|
+
relation.debug_sql.should match /"notes"."notable_type" = 'Article'/
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'eager loads multiple polymorphic belongs_to associations' do
|
212
|
+
relation = Note.includes{[notable(Article), notable(Person)]}.
|
213
|
+
where{{notable(Article) => {title => 'hey'}}}.
|
214
|
+
where{{notable(Person) => {name => 'joe'}}}
|
215
|
+
relation.debug_sql.should match /"notes"."notable_type" = 'Article'/
|
216
|
+
relation.debug_sql.should match /"notes"."notable_type" = 'Person'/
|
217
|
+
end
|
218
|
+
|
219
|
+
it "only includes once, even if two join types are used" do
|
220
|
+
relation = Person.includes(:articles.inner, :articles.outer).where(:articles => {:title => 'hey'})
|
221
|
+
relation.debug_sql.scan("JOIN").size.should eq 1
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'includes a keypath' do
|
225
|
+
relation = Note.includes{notable(Article).person.children}.where{notable(Article).person.children.name == 'Ernie'}
|
226
|
+
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"/
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
describe '#preload' do
|
232
|
+
|
233
|
+
it 'builds options with a block' do
|
234
|
+
relation = Person.preload{children}
|
235
|
+
queries_for {relation.all}.should have(2).items
|
236
|
+
queries_for {relation.first.children}.should have(0).items
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'builds options with a keypath' do
|
240
|
+
relation = Person.preload{articles.comments}
|
241
|
+
queries_for {relation.all}.should have(3).items
|
242
|
+
queries_for {relation.first.articles.first.comments}.should have(0).items
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'builds options with a hash' do
|
246
|
+
relation = Person.preload{{
|
247
|
+
articles => {
|
248
|
+
comments => person
|
249
|
+
}
|
250
|
+
}}
|
251
|
+
|
252
|
+
queries_for {relation.all}.should have(4).items
|
253
|
+
|
254
|
+
queries_for {
|
255
|
+
relation.first.articles
|
256
|
+
relation.first.articles.first.comments
|
257
|
+
relation.first.articles.first.comments.first.person
|
258
|
+
}.should have(0).items
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
describe '#eager_load' do
|
264
|
+
|
265
|
+
it 'builds options with a block' do
|
266
|
+
standard = Person.eager_load(:children => :children)
|
267
|
+
block = Person.eager_load{{children => children}}
|
268
|
+
block.debug_sql.should eq standard.debug_sql
|
269
|
+
queries_for {block.all}.should have(1).item
|
270
|
+
queries_for {block.first.children}.should have(0).items
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'eager loads multiple top-level associations with a block' do
|
274
|
+
standard = Person.eager_load(:children, :articles, :comments)
|
275
|
+
block = Person.eager_load{[children, articles, comments]}
|
276
|
+
block.debug_sql.should eq standard.debug_sql
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'eager loads polymorphic belongs_to associations' do
|
280
|
+
relation = Note.eager_load{notable(Article)}
|
281
|
+
relation.debug_sql.should match /"notes"."notable_type" = 'Article'/
|
282
|
+
end
|
283
|
+
|
284
|
+
it 'eager loads multiple polymorphic belongs_to associations' do
|
285
|
+
relation = Note.eager_load{[notable(Article), notable(Person)]}
|
286
|
+
relation.debug_sql.should match /"notes"."notable_type" = 'Article'/
|
287
|
+
relation.debug_sql.should match /"notes"."notable_type" = 'Person'/
|
288
|
+
end
|
289
|
+
|
290
|
+
it "only eager_load once, even if two join types are used" do
|
291
|
+
relation = Person.eager_load(:articles.inner, :articles.outer)
|
292
|
+
relation.debug_sql.scan("JOIN").size.should eq 1
|
293
|
+
end
|
294
|
+
|
295
|
+
it 'eager_load a keypath' do
|
296
|
+
relation = Note.eager_load{notable(Article).person.children}
|
297
|
+
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"/
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
301
|
+
|
212
302
|
describe '#select' do
|
213
303
|
|
214
304
|
it 'accepts options from a block' do
|
@@ -251,6 +341,16 @@ module Squeel
|
|
251
341
|
|
252
342
|
end
|
253
343
|
|
344
|
+
describe '#group' do
|
345
|
+
|
346
|
+
it 'builds options with a block' do
|
347
|
+
standard = Person.group(:name)
|
348
|
+
block = Person.group{name}
|
349
|
+
block.to_sql.should eq standard.to_sql
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|
353
|
+
|
254
354
|
describe '#where' do
|
255
355
|
|
256
356
|
it 'builds options with a block' do
|
@@ -284,6 +384,23 @@ module Squeel
|
|
284
384
|
relation.first.id.should eq 1
|
285
385
|
end
|
286
386
|
|
387
|
+
it 'maps conditions onto their proper table with multiple polymorphic joins' do
|
388
|
+
relation = Note.joins{[notable(Article).outer, notable(Person).outer]}
|
389
|
+
people_notes = relation.where{notable(Person).salary > 30000}
|
390
|
+
article_notes = relation.where{notable(Article).title =~ '%'}
|
391
|
+
people_and_article_notes = relation.where{(notable(Person).salary > 30000) | (notable(Article).title =~ '%')}
|
392
|
+
people_notes.should have(10).items
|
393
|
+
article_notes.should have(30).items
|
394
|
+
people_and_article_notes.should have(40).items
|
395
|
+
end
|
396
|
+
|
397
|
+
it 'allows a subquery on the value side of a predicate' do
|
398
|
+
old_and_busted = Person.where(:name => ['Aric Smith', 'Gladyce Kulas'])
|
399
|
+
new_hotness = Person.where{name.in(Person.select{name}.where{name.in(['Aric Smith', 'Gladyce Kulas'])})}
|
400
|
+
new_hotness.should have(2).items
|
401
|
+
old_and_busted.to_a.should eq new_hotness.to_a
|
402
|
+
end
|
403
|
+
|
287
404
|
end
|
288
405
|
|
289
406
|
describe '#joins' do
|
@@ -305,11 +422,22 @@ module Squeel
|
|
305
422
|
relation.to_sql.should match /"notes"."notable_type" = 'Article'/
|
306
423
|
end
|
307
424
|
|
425
|
+
it 'joins multiple polymorphic belongs_to associations' do
|
426
|
+
relation = Note.joins{[notable(Article), notable(Person)]}
|
427
|
+
relation.to_sql.should match /"notes"."notable_type" = 'Article'/
|
428
|
+
relation.to_sql.should match /"notes"."notable_type" = 'Person'/
|
429
|
+
end
|
430
|
+
|
308
431
|
it "only joins once, even if two join types are used" do
|
309
432
|
relation = Person.joins(:articles.inner, :articles.outer)
|
310
433
|
relation.to_sql.scan("JOIN").size.should eq 1
|
311
434
|
end
|
312
435
|
|
436
|
+
it 'joins a keypath' do
|
437
|
+
relation = Note.joins{notable(Article).person.children}
|
438
|
+
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"/
|
439
|
+
end
|
440
|
+
|
313
441
|
end
|
314
442
|
|
315
443
|
describe '#having' do
|
@@ -347,6 +475,19 @@ module Squeel
|
|
347
475
|
|
348
476
|
end
|
349
477
|
|
478
|
+
describe '#reorder' do
|
479
|
+
before do
|
480
|
+
@standard = Person.order(:name)
|
481
|
+
end
|
482
|
+
|
483
|
+
it 'builds options with a block' do
|
484
|
+
block = Person.reorder{id}
|
485
|
+
block.to_sql.should_not eq @standard.to_sql
|
486
|
+
block.to_sql.should match /ORDER BY "people"."id"/
|
487
|
+
end
|
488
|
+
|
489
|
+
end
|
490
|
+
|
350
491
|
describe '#build_where' do
|
351
492
|
|
352
493
|
it 'sanitizes SQL as usual with strings' do
|