squeel 1.1.1 → 1.2.1
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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +3 -0
- data/.travis.yml +36 -4
- data/CHANGELOG.md +15 -0
- data/Gemfile +1 -1
- data/README.md +47 -6
- data/Rakefile +14 -2
- data/lib/squeel.rb +9 -1
- data/lib/squeel/adapters/active_record.rb +0 -1
- data/lib/squeel/adapters/active_record/3.0/join_dependency_extensions.rb +1 -0
- data/lib/squeel/adapters/active_record/3.0/relation_extensions.rb +12 -1
- data/lib/squeel/adapters/active_record/3.1/join_dependency_extensions.rb +1 -0
- data/lib/squeel/adapters/active_record/3.2/join_dependency_extensions.rb +1 -0
- data/lib/squeel/adapters/active_record/4.0/join_dependency_extensions.rb +1 -0
- data/lib/squeel/adapters/active_record/4.0/relation_extensions.rb +92 -0
- data/lib/squeel/adapters/active_record/4.1/compat.rb +15 -0
- data/lib/squeel/adapters/active_record/4.1/context.rb +88 -0
- data/lib/squeel/adapters/active_record/4.1/preloader_extensions.rb +31 -0
- data/lib/squeel/adapters/active_record/4.1/reflection_extensions.rb +37 -0
- data/lib/squeel/adapters/active_record/4.1/relation_extensions.rb +307 -0
- data/lib/squeel/adapters/active_record/4.2/compat.rb +1 -0
- data/lib/squeel/adapters/active_record/4.2/context.rb +1 -0
- data/lib/squeel/adapters/active_record/4.2/preloader_extensions.rb +1 -0
- data/lib/squeel/adapters/active_record/4.2/relation_extensions.rb +108 -0
- data/lib/squeel/adapters/active_record/context.rb +7 -7
- data/lib/squeel/adapters/active_record/join_dependency_extensions.rb +9 -13
- data/lib/squeel/adapters/active_record/relation_extensions.rb +38 -8
- data/lib/squeel/core_ext/symbol.rb +3 -3
- data/lib/squeel/dsl.rb +1 -1
- data/lib/squeel/nodes.rb +1 -0
- data/lib/squeel/nodes/as.rb +12 -0
- data/lib/squeel/nodes/join.rb +8 -4
- data/lib/squeel/nodes/key_path.rb +10 -1
- data/lib/squeel/nodes/node.rb +21 -0
- data/lib/squeel/nodes/stub.rb +8 -4
- data/lib/squeel/nodes/subquery_join.rb +44 -0
- data/lib/squeel/version.rb +1 -1
- data/lib/squeel/visitors.rb +2 -0
- data/lib/squeel/visitors/enumeration_visitor.rb +101 -0
- data/lib/squeel/visitors/order_visitor.rb +9 -2
- data/lib/squeel/visitors/predicate_visitor.rb +11 -0
- data/lib/squeel/visitors/preload_visitor.rb +12 -0
- data/lib/squeel/visitors/visitor.rb +89 -13
- data/spec/config.travis.yml +13 -0
- data/spec/config.yml +12 -0
- data/spec/console.rb +3 -12
- data/spec/core_ext/symbol_spec.rb +3 -3
- data/spec/helpers/squeel_helper.rb +8 -5
- data/spec/spec_helper.rb +4 -16
- data/spec/squeel/adapters/active_record/context_spec.rb +8 -4
- data/spec/squeel/adapters/active_record/join_dependency_extensions_spec.rb +123 -38
- data/spec/squeel/adapters/active_record/relation_extensions_spec.rb +350 -124
- data/spec/squeel/core_ext/symbol_spec.rb +3 -3
- data/spec/squeel/nodes/join_spec.rb +4 -4
- data/spec/squeel/nodes/stub_spec.rb +3 -3
- data/spec/squeel/nodes/subquery_join_spec.rb +46 -0
- data/spec/squeel/visitors/order_visitor_spec.rb +3 -3
- data/spec/squeel/visitors/predicate_visitor_spec.rb +69 -36
- data/spec/squeel/visitors/select_visitor_spec.rb +1 -1
- data/spec/squeel/visitors/visitor_spec.rb +7 -6
- data/spec/support/models.rb +99 -15
- data/spec/support/schema.rb +109 -4
- data/squeel.gemspec +8 -6
- metadata +89 -107
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/spec/blueprints/articles.rb +0 -5
- data/spec/blueprints/comments.rb +0 -5
- data/spec/blueprints/notes.rb +0 -3
- data/spec/blueprints/people.rb +0 -4
- data/spec/blueprints/tags.rb +0 -3
@@ -3,66 +3,151 @@ require 'spec_helper'
|
|
3
3
|
module Squeel
|
4
4
|
module Adapters
|
5
5
|
module ActiveRecord
|
6
|
-
describe JoinDependencyExtensions do
|
7
|
-
before do
|
8
|
-
@jd = new_join_dependency(Person, {}, [])
|
9
|
-
end
|
10
|
-
|
6
|
+
describe "JoinDependencyExtensions" do
|
11
7
|
it 'joins with symbols' do
|
12
|
-
@jd
|
13
|
-
|
14
|
-
|
15
|
-
|
8
|
+
@jd = new_join_dependency(Person, { :articles => :comments }, [])
|
9
|
+
|
10
|
+
if activerecord_version_at_least('4.2.0')
|
11
|
+
@jd.join_constraints([]).should have(2).join_info
|
12
|
+
@jd.join_constraints([]).map(&:joins).flatten.each do |join|
|
13
|
+
join.class.should eq Squeel::InnerJoin
|
14
|
+
end
|
15
|
+
elsif activerecord_version_at_least('4.1.0')
|
16
|
+
@jd.join_constraints([]).should have(2).joins
|
17
|
+
@jd.join_constraints([]).each do |join|
|
18
|
+
join.class.should eq Squeel::InnerJoin
|
19
|
+
end
|
20
|
+
else
|
21
|
+
@jd.join_associations.should have(2).associations
|
22
|
+
@jd.join_associations.each do |association|
|
23
|
+
association.join_type.should eq Squeel::InnerJoin
|
24
|
+
end
|
16
25
|
end
|
17
26
|
end
|
18
27
|
|
19
28
|
it 'joins has_many :through associations' do
|
20
|
-
@jd
|
21
|
-
|
22
|
-
|
29
|
+
@jd = new_join_dependency(Person, :authored_article_comments, [])
|
30
|
+
|
31
|
+
if activerecord_version_at_least('4.2.0')
|
32
|
+
@jd.join_constraints([]).should have(1).join_info
|
33
|
+
@jd.join_root.children.first.table_name.should eq 'comments'
|
34
|
+
elsif activerecord_version_at_least('4.1.0')
|
35
|
+
@jd.join_constraints([]).should have(2).joins
|
36
|
+
@jd.join_root.children.first.table_name.should eq 'comments'
|
37
|
+
else
|
38
|
+
@jd.join_associations.should have(1).association
|
39
|
+
@jd.join_associations.first.table_name.should eq 'comments'
|
40
|
+
end
|
23
41
|
end
|
24
42
|
|
25
43
|
it 'joins with stubs' do
|
26
|
-
@jd
|
27
|
-
|
28
|
-
|
29
|
-
|
44
|
+
@jd = new_join_dependency(Person, { Squeel::Nodes::Stub.new(:articles) => Squeel::Nodes::Stub.new(:comments) }, [])
|
45
|
+
|
46
|
+
if activerecord_version_at_least('4.2.0')
|
47
|
+
@jd.join_constraints([]).should have(2).join_info
|
48
|
+
@jd.join_constraints([]).map(&:joins).flatten.each do |join|
|
49
|
+
join.class.should eq Squeel::InnerJoin
|
50
|
+
end
|
51
|
+
@jd.join_root.children.first.table_name.should eq 'articles'
|
52
|
+
@jd.join_root.children.first.children.first.table_name.should eq 'comments'
|
53
|
+
elsif activerecord_version_at_least('4.1.0')
|
54
|
+
@jd.join_constraints([]).should have(2).joins
|
55
|
+
@jd.join_constraints([]).each do |join|
|
56
|
+
join.class.should eq Squeel::InnerJoin
|
57
|
+
end
|
58
|
+
@jd.join_root.children.first.table_name.should eq 'articles'
|
59
|
+
@jd.join_root.children.first.children.first.table_name.should eq 'comments'
|
60
|
+
else
|
61
|
+
@jd.join_associations.should have(2).associations
|
62
|
+
@jd.join_associations.each do |association|
|
63
|
+
association.join_type.should eq Squeel::InnerJoin
|
64
|
+
end
|
65
|
+
@jd.join_associations[0].table_name.should eq 'articles'
|
66
|
+
@jd.join_associations[1].table_name.should eq 'comments'
|
30
67
|
end
|
31
|
-
@jd.join_associations[0].table_name.should eq 'articles'
|
32
|
-
@jd.join_associations[1].table_name.should eq 'comments'
|
33
68
|
end
|
34
69
|
|
35
70
|
it 'joins with key paths' do
|
36
|
-
@jd
|
37
|
-
|
38
|
-
|
39
|
-
|
71
|
+
@jd = new_join_dependency(Person, dsl{ children.children.parent }, [])
|
72
|
+
|
73
|
+
if activerecord_version_at_least('4.2.0')
|
74
|
+
@jd.join_constraints([]).should have(3).join_info
|
75
|
+
@jd.join_constraints([]).map(&:joins).flatten.each do |join|
|
76
|
+
join.class.should eq Squeel::InnerJoin
|
77
|
+
end
|
78
|
+
(children_people = @jd.join_root.children.first).aliased_table_name.should eq 'children_people'
|
79
|
+
(children_people2 = children_people.children.first).aliased_table_name.should eq 'children_people_2'
|
80
|
+
children_people2.children.first.aliased_table_name.should eq 'parents_people'
|
81
|
+
elsif activerecord_version_at_least('4.1.0')
|
82
|
+
@jd.join_constraints([]).should have(3).joins
|
83
|
+
@jd.join_constraints([]).each do |join|
|
84
|
+
join.class.should eq Squeel::InnerJoin
|
85
|
+
end
|
86
|
+
(children_people = @jd.join_root.children.first).aliased_table_name.should eq 'children_people'
|
87
|
+
(children_people2 = children_people.children.first).aliased_table_name.should eq 'children_people_2'
|
88
|
+
children_people2.children.first.aliased_table_name.should eq 'parents_people'
|
89
|
+
else
|
90
|
+
@jd.join_associations.should have(3).associations
|
91
|
+
@jd.join_associations.each do |association|
|
92
|
+
association.join_type.should eq Squeel::InnerJoin
|
93
|
+
end
|
94
|
+
@jd.join_associations[0].aliased_table_name.should eq 'children_people'
|
95
|
+
@jd.join_associations[1].aliased_table_name.should eq 'children_people_2'
|
96
|
+
@jd.join_associations[2].aliased_table_name.should eq 'parents_people'
|
40
97
|
end
|
41
|
-
@jd.join_associations[0].aliased_table_name.should eq 'children_people'
|
42
|
-
@jd.join_associations[1].aliased_table_name.should eq 'children_people_2'
|
43
|
-
@jd.join_associations[2].aliased_table_name.should eq 'parents_people'
|
44
98
|
end
|
45
99
|
|
46
100
|
it 'joins with key paths as keys' do
|
47
|
-
@jd
|
48
|
-
|
49
|
-
|
50
|
-
|
101
|
+
@jd = new_join_dependency(Person, dsl{ { children.parent => parent } }, [])
|
102
|
+
|
103
|
+
if activerecord_version_at_least('4.2.0')
|
104
|
+
@jd.join_constraints([]).should have(3).join_info
|
105
|
+
@jd.join_constraints([]).map(&:joins).flatten.each do |join|
|
106
|
+
join.class.should eq Squeel::InnerJoin
|
107
|
+
end
|
108
|
+
(children_people = @jd.join_root.children.first).aliased_table_name.should eq 'children_people'
|
109
|
+
(parents_people = children_people.children.first).aliased_table_name.should eq 'parents_people'
|
110
|
+
parents_people.children.first.aliased_table_name.should eq 'parents_people_2'
|
111
|
+
elsif activerecord_version_at_least('4.1.0')
|
112
|
+
@jd.join_constraints([]).should have(3).joins
|
113
|
+
@jd.join_constraints([]).each do |join|
|
114
|
+
join.class.should eq Squeel::InnerJoin
|
115
|
+
end
|
116
|
+
(children_people = @jd.join_root.children.first).aliased_table_name.should eq 'children_people'
|
117
|
+
(parents_people = children_people.children.first).aliased_table_name.should eq 'parents_people'
|
118
|
+
parents_people.children.first.aliased_table_name.should eq 'parents_people_2'
|
119
|
+
else
|
120
|
+
@jd.join_associations.should have(3).associations
|
121
|
+
@jd.join_associations.each do |association|
|
122
|
+
association.join_type.should eq Squeel::InnerJoin
|
123
|
+
end
|
124
|
+
@jd.join_associations[0].aliased_table_name.should eq 'children_people'
|
125
|
+
@jd.join_associations[1].aliased_table_name.should eq 'parents_people'
|
126
|
+
@jd.join_associations[2].aliased_table_name.should eq 'parents_people_2'
|
51
127
|
end
|
52
|
-
@jd.join_associations[0].aliased_table_name.should eq 'children_people'
|
53
|
-
@jd.join_associations[1].aliased_table_name.should eq 'parents_people'
|
54
|
-
@jd.join_associations[2].aliased_table_name.should eq 'parents_people_2'
|
55
128
|
end
|
56
129
|
|
57
130
|
it 'joins using outer joins' do
|
58
|
-
@jd
|
59
|
-
|
60
|
-
|
61
|
-
|
131
|
+
@jd = new_join_dependency(Person, { :articles.outer => :comments.outer }, [])
|
132
|
+
|
133
|
+
if activerecord_version_at_least('4.2.0')
|
134
|
+
@jd.join_constraints([]).should have(2).join_info
|
135
|
+
@jd.join_constraints([]).map(&:joins).flatten.each do |join|
|
136
|
+
join.class.should eq Squeel::OuterJoin
|
137
|
+
end
|
138
|
+
elsif activerecord_version_at_least('4.1.0')
|
139
|
+
@jd.join_constraints([]).should have(2).joins
|
140
|
+
@jd.join_constraints([]).each do |join|
|
141
|
+
join.class.should eq Squeel::OuterJoin
|
142
|
+
end
|
143
|
+
else
|
144
|
+
@jd.join_associations.should have(2).associations
|
145
|
+
@jd.join_associations.each do |association|
|
146
|
+
association.join_type.should eq Squeel::OuterJoin
|
147
|
+
end
|
62
148
|
end
|
63
149
|
end
|
64
|
-
|
65
150
|
end
|
66
151
|
end
|
67
152
|
end
|
68
|
-
end
|
153
|
+
end
|
@@ -15,8 +15,21 @@ module Squeel
|
|
15
15
|
queries = queries_for do
|
16
16
|
Person.find_by_id('')
|
17
17
|
end
|
18
|
-
|
19
|
-
|
18
|
+
if activerecord_version_at_least('3.1.0')
|
19
|
+
queries.should have(1).query
|
20
|
+
else
|
21
|
+
puts 'skips count of queries expectation'
|
22
|
+
end
|
23
|
+
|
24
|
+
if activerecord_version_at_least('4.2.0')
|
25
|
+
if PG_ENV
|
26
|
+
queries.last.should match /#{Q}people#{Q}.#{Q}id#{Q} = \$1/
|
27
|
+
else
|
28
|
+
queries.last.should match /#{Q}people#{Q}.#{Q}id#{Q} = ?/
|
29
|
+
end
|
30
|
+
else
|
31
|
+
queries.last.should match /#{Q}people#{Q}.#{Q}id#{Q} = 0/
|
32
|
+
end
|
20
33
|
end
|
21
34
|
|
22
35
|
end
|
@@ -34,8 +47,12 @@ module Squeel
|
|
34
47
|
|
35
48
|
arel = relation.build_arel
|
36
49
|
|
37
|
-
|
38
|
-
|
50
|
+
if activerecord_version_at_least('4.1.0')
|
51
|
+
relation.join_dependency.join_constraints([]).should have(4).items
|
52
|
+
else
|
53
|
+
relation.join_dependency.join_associations.should have(4).items
|
54
|
+
end
|
55
|
+
arel.to_sql.should match /INNER JOIN #{Q}people#{Q} #{Q}parents_people_2#{Q} ON #{Q}parents_people_2#{Q}.#{Q}id#{Q} = #{Q}parents_people#{Q}.#{Q}parent_id#{Q}/
|
39
56
|
end
|
40
57
|
|
41
58
|
it 'joins associations with custom join types' do
|
@@ -49,9 +66,13 @@ module Squeel
|
|
49
66
|
|
50
67
|
arel = relation.build_arel
|
51
68
|
|
52
|
-
|
53
|
-
|
54
|
-
|
69
|
+
if activerecord_version_at_least('4.1.0')
|
70
|
+
relation.join_dependency.join_constraints([]).should have(4).items
|
71
|
+
else
|
72
|
+
relation.join_dependency.join_associations.should have(4).items
|
73
|
+
end
|
74
|
+
arel.to_sql.should match /LEFT OUTER JOIN #{Q}people#{Q} #{Q}children_people#{Q}/
|
75
|
+
arel.to_sql.should match /LEFT OUTER JOIN #{Q}people#{Q} #{Q}parents_people_2#{Q} ON #{Q}parents_people_2#{Q}.#{Q}id#{Q} = #{Q}parents_people#{Q}.#{Q}parent_id#{Q}/
|
55
76
|
end
|
56
77
|
|
57
78
|
it 'only joins an association once, even if two overlapping joins_values hashes are given' do
|
@@ -70,8 +91,12 @@ module Squeel
|
|
70
91
|
})
|
71
92
|
|
72
93
|
arel = relation.build_arel
|
73
|
-
|
74
|
-
|
94
|
+
if activerecord_version_at_least('4.1.0')
|
95
|
+
relation.join_dependency.join_constraints([]).should have(6).items
|
96
|
+
else
|
97
|
+
relation.join_dependency.join_associations.should have(6).items
|
98
|
+
end
|
99
|
+
arel.to_sql.should match /INNER JOIN #{Q}people#{Q} #{Q}parents_people_3#{Q} ON #{Q}parents_people_3#{Q}.#{Q}id#{Q} = #{Q}children_people_3#{Q}.#{Q}parent_id#{Q}/
|
75
100
|
end
|
76
101
|
|
77
102
|
it 'respects :uniq option on associations' do
|
@@ -81,15 +106,15 @@ module Squeel
|
|
81
106
|
it 'visits wheres with a PredicateVisitor, converting them to Arel nodes' do
|
82
107
|
relation = Person.where(:name.matches => '%bob%')
|
83
108
|
arel = relation.build_arel
|
84
|
-
arel.to_sql.should match
|
109
|
+
arel.to_sql.should match /#{Q}people#{Q}.#{Q}name#{Q} [I]*LIKE '%bob%'/
|
85
110
|
end
|
86
111
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
112
|
+
it 'handles multiple wheres using a keypath' do
|
113
|
+
relation = Person.joins{articles}.where{articles.title == 'Hello'}.
|
114
|
+
where{articles.body == 'World'}
|
115
|
+
arel = relation.build_arel
|
116
|
+
arel.to_sql.should match /articles/
|
117
|
+
end
|
93
118
|
|
94
119
|
it 'maps wheres inside a hash to their appropriate association table' do
|
95
120
|
relation = Person.joins({
|
@@ -109,15 +134,14 @@ module Squeel
|
|
109
134
|
})
|
110
135
|
|
111
136
|
arel = relation.build_arel
|
112
|
-
|
113
|
-
arel.to_sql.should match /"parents_people_2"."name" = 'bob'/
|
137
|
+
arel.to_sql.should match /#{Q}parents_people_2#{Q}.#{Q}name#{Q} = 'bob'/
|
114
138
|
end
|
115
139
|
|
116
140
|
it 'combines multiple conditions of the same type against the same column with AND' do
|
117
141
|
relation = Person.where(:name.matches => '%bob%')
|
118
142
|
relation = relation.where(:name.matches => '%joe%')
|
119
143
|
arel = relation.build_arel
|
120
|
-
arel.to_sql.should match
|
144
|
+
arel.to_sql.should match /#{Q}people#{Q}.#{Q}name#{Q} [I]*LIKE '%bob%' AND #{Q}people#{Q}.#{Q}name#{Q} [I]*LIKE '%joe%'/
|
121
145
|
end
|
122
146
|
|
123
147
|
it 'handles ORs between predicates' do
|
@@ -129,7 +153,7 @@ module Squeel
|
|
129
153
|
it 'maintains groupings as given' do
|
130
154
|
relation = Person.where(dsl{(name == 'Ernie') | ((name =~ 'Bob%') & (name =~ '%by'))})
|
131
155
|
arel = relation.build_arel
|
132
|
-
arel.to_sql.should match
|
156
|
+
arel.to_sql.should match /#{Q}people#{Q}.#{Q}name#{Q} = 'Ernie' OR \(#{Q}people#{Q}.#{Q}name#{Q} [I]*LIKE 'Bob%' AND #{Q}people#{Q}.#{Q}name#{Q} [I]*LIKE '%by'\)/
|
133
157
|
end
|
134
158
|
|
135
159
|
it 'maps havings inside a hash to their appropriate association table' do
|
@@ -150,8 +174,7 @@ module Squeel
|
|
150
174
|
})
|
151
175
|
|
152
176
|
arel = relation.build_arel
|
153
|
-
|
154
|
-
arel.to_sql.should match /HAVING "parents_people_2"."name" = 'joe'/
|
177
|
+
arel.to_sql.should match /HAVING #{Q}parents_people_2#{Q}.#{Q}name#{Q} = 'joe'/
|
155
178
|
end
|
156
179
|
|
157
180
|
it 'maps orders inside a hash to their appropriate association table' do
|
@@ -173,8 +196,7 @@ module Squeel
|
|
173
196
|
})
|
174
197
|
|
175
198
|
arel = relation.build_arel
|
176
|
-
|
177
|
-
arel.to_sql.should match /ORDER BY "parents_people_2"."id" ASC/
|
199
|
+
arel.to_sql.should match /ORDER BY #{Q}parents_people_2#{Q}.#{Q}id#{Q} ASC/
|
178
200
|
else
|
179
201
|
pending 'Unsupported in ActiveRecord 4.0.0+'
|
180
202
|
end
|
@@ -191,7 +213,6 @@ module Squeel
|
|
191
213
|
|
192
214
|
it 'reverses order of Arel::Attributes when #last is called' do
|
193
215
|
sorted_people = Person.all.to_a.sort {|a, b| a.name.downcase <=> b.name.downcase}
|
194
|
-
|
195
216
|
Person.order{name}.last.should eq sorted_people.last
|
196
217
|
end
|
197
218
|
|
@@ -245,46 +266,77 @@ module Squeel
|
|
245
266
|
|
246
267
|
it 'eager loads belongs_to associations' do
|
247
268
|
queries = queries_for do
|
248
|
-
|
249
|
-
|
269
|
+
if activerecord_version_at_least('4.1.0')
|
270
|
+
Article.includes(:person).references(:person).
|
271
|
+
where{person.name == 'Ernie'}.to_a
|
272
|
+
else
|
273
|
+
Article.includes(:person).
|
274
|
+
where{person.name == 'Ernie'}.to_a
|
275
|
+
end
|
250
276
|
end
|
251
|
-
|
252
|
-
|
253
|
-
|
277
|
+
|
278
|
+
if activerecord_version_at_least('4.1.0')
|
279
|
+
queries.should have(1).item
|
280
|
+
else
|
281
|
+
puts 'skips count of queries expectation.'
|
282
|
+
end
|
283
|
+
|
284
|
+
queries.last.should match /LEFT OUTER JOIN #{Q}people#{Q}/
|
285
|
+
queries.last.should match /#{Q}people#{Q}.#{Q}name#{Q} = 'Ernie'/
|
254
286
|
end
|
255
287
|
|
256
288
|
it 'eager loads belongs_to associations on models with default_scopes' do
|
257
289
|
queries = queries_for do
|
258
|
-
|
259
|
-
|
290
|
+
if activerecord_version_at_least('4.1.0')
|
291
|
+
PersonNamedBill.includes(:parent).references(:parent).
|
292
|
+
where{parent.name == 'Ernie'}.to_a
|
293
|
+
else
|
294
|
+
PersonNamedBill.includes(:parent).
|
295
|
+
where{parent.name == 'Ernie'}.to_a
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
299
|
+
|
300
|
+
if activerecord_version_at_least('4.1.0')
|
301
|
+
queries.should have(1).item
|
302
|
+
else
|
303
|
+
puts 'skips count of queries expectation.'
|
260
304
|
end
|
261
|
-
|
262
|
-
queries.
|
263
|
-
queries.
|
264
|
-
queries.
|
305
|
+
|
306
|
+
queries.last.should match /LEFT OUTER JOIN #{Q}people#{Q}/
|
307
|
+
queries.last.should match /#{Q}people#{Q}.#{Q}name#{Q} = 'Bill'/
|
308
|
+
queries.last.should match /#{Q}parents_people#{Q}.#{Q}name#{Q} = 'Ernie'/
|
265
309
|
end
|
266
310
|
|
267
311
|
it 'eager loads polymorphic belongs_to associations' do
|
268
312
|
relation = Note.includes{notable(Article)}.where{{notable(Article) => {title => 'hey'}}}
|
269
|
-
relation.debug_sql.should match
|
313
|
+
relation.debug_sql.should match /#{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Article'/
|
270
314
|
end
|
271
315
|
|
272
316
|
it 'eager loads multiple polymorphic belongs_to associations' do
|
273
317
|
relation = Note.includes{[notable(Article), notable(Person)]}.
|
274
318
|
where{{notable(Article) => {title => 'hey'}}}.
|
275
319
|
where{{notable(Person) => {name => 'joe'}}}
|
276
|
-
relation.debug_sql.should match
|
277
|
-
relation.debug_sql.should match
|
320
|
+
relation.debug_sql.should match /#{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Article'/
|
321
|
+
relation.debug_sql.should match /#{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Person'/
|
278
322
|
end
|
279
323
|
|
280
|
-
it
|
324
|
+
it 'only includes once, even if two join types are used' do
|
281
325
|
relation = Person.includes(:articles.inner, :articles.outer).where(:articles => {:title => 'hey'})
|
282
|
-
|
326
|
+
if activerecord_version_at_least('4.1.0')
|
327
|
+
pending ":article != :article.inner != :article.outer, you shouldn't pass two same tables into includes again"
|
328
|
+
else
|
329
|
+
relation.debug_sql.scan("JOIN").size.should eq 1
|
330
|
+
end
|
283
331
|
end
|
284
332
|
|
285
333
|
it 'includes a keypath' do
|
286
|
-
|
287
|
-
|
334
|
+
if activerecord_version_at_least('4.1.0')
|
335
|
+
relation = Note.includes{notable(Article).person.children}.references(:all).where{notable(Article).person.children.name == 'Ernie'}
|
336
|
+
else
|
337
|
+
relation = Note.includes{notable(Article).person.children}.where{notable(Article).person.children.name == 'Ernie'}
|
338
|
+
end
|
339
|
+
relation.debug_sql.should match /SELECT #{Q}notes#{Q}.* FROM #{Q}notes#{Q} LEFT OUTER JOIN #{Q}articles#{Q} ON #{Q}articles#{Q}.#{Q}id#{Q} = #{Q}notes#{Q}.#{Q}notable_id#{Q} AND #{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Article' LEFT OUTER JOIN #{Q}people#{Q} ON #{Q}people#{Q}.#{Q}id#{Q} = #{Q}articles#{Q}.#{Q}person_id#{Q} LEFT OUTER JOIN #{Q}people#{Q} #{Q}children_people#{Q} ON #{Q}children_people#{Q}.#{Q}parent_id#{Q} = #{Q}people#{Q}.#{Q}id#{Q} WHERE #{Q}children_people#{Q}.#{Q}name#{Q} = 'Ernie'/
|
288
340
|
end
|
289
341
|
|
290
342
|
end
|
@@ -311,7 +363,6 @@ module Squeel
|
|
311
363
|
}}
|
312
364
|
|
313
365
|
queries_for {relation.to_a}.should have(4).items
|
314
|
-
|
315
366
|
queries_for {
|
316
367
|
relation.first.articles
|
317
368
|
relation.first.articles.first.comments
|
@@ -327,8 +378,12 @@ module Squeel
|
|
327
378
|
standard = Person.eager_load(:children => :children)
|
328
379
|
block = Person.eager_load{{children => children}}
|
329
380
|
block.debug_sql.should eq standard.debug_sql
|
330
|
-
|
331
|
-
|
381
|
+
if activerecord_version_at_least('3.2.0')
|
382
|
+
queries_for {block.to_a}.should have(1).item
|
383
|
+
queries_for {block.first.children}.should have(0).items
|
384
|
+
else
|
385
|
+
puts 'skips count of queries expectation.'
|
386
|
+
end
|
332
387
|
end
|
333
388
|
|
334
389
|
it 'eager loads multiple top-level associations with a block' do
|
@@ -339,23 +394,27 @@ module Squeel
|
|
339
394
|
|
340
395
|
it 'eager loads polymorphic belongs_to associations' do
|
341
396
|
relation = Note.eager_load{notable(Article)}
|
342
|
-
relation.debug_sql.should match
|
397
|
+
relation.debug_sql.should match /#{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Article'/
|
343
398
|
end
|
344
399
|
|
345
400
|
it 'eager loads multiple polymorphic belongs_to associations' do
|
346
401
|
relation = Note.eager_load{[notable(Article), notable(Person)]}
|
347
|
-
relation.debug_sql.should match
|
348
|
-
relation.debug_sql.should match
|
402
|
+
relation.debug_sql.should match /#{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Article'/
|
403
|
+
relation.debug_sql.should match /#{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Person'/
|
349
404
|
end
|
350
405
|
|
351
406
|
it "only eager_load once, even if two join types are used" do
|
352
407
|
relation = Person.eager_load(:articles.inner, :articles.outer)
|
353
|
-
|
408
|
+
if activerecord_version_at_least('4.1.0')
|
409
|
+
pending ":article != :article.inner != :article.outer, you shouldn't pass two same tables into eager_load again"
|
410
|
+
else
|
411
|
+
relation.debug_sql.scan("JOIN").size.should eq 1
|
412
|
+
end
|
354
413
|
end
|
355
414
|
|
356
415
|
it 'eager_load a keypath' do
|
357
416
|
relation = Note.eager_load{notable(Article).person.children}
|
358
|
-
relation.debug_sql.should match /SELECT
|
417
|
+
relation.debug_sql.should match /SELECT #{Q}notes#{Q}.* FROM #{Q}notes#{Q} LEFT OUTER JOIN #{Q}articles#{Q} ON #{Q}articles#{Q}.#{Q}id#{Q} = #{Q}notes#{Q}.#{Q}notable_id#{Q} AND #{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Article' LEFT OUTER JOIN #{Q}people#{Q} ON #{Q}people#{Q}.#{Q}id#{Q} = #{Q}articles#{Q}.#{Q}person_id#{Q} LEFT OUTER JOIN #{Q}people#{Q} #{Q}children_people#{Q} ON #{Q}children_people#{Q}.#{Q}parent_id#{Q} = #{Q}people#{Q}.#{Q}id#{Q}/
|
359
418
|
end
|
360
419
|
|
361
420
|
end
|
@@ -385,39 +444,52 @@ module Squeel
|
|
385
444
|
|
386
445
|
it 'behaves as normal with standard parameters' do
|
387
446
|
people = Person.select(:id)
|
388
|
-
people.should have(
|
389
|
-
|
447
|
+
people.should have(10).people
|
448
|
+
if ::ActiveRecord::VERSION::MAJOR == 3 && ::ActiveRecord::VERSION::MINOR == 0 && RUBY_VERSION >= '2.0.0'
|
449
|
+
people.first.name.should be_nil
|
450
|
+
else
|
451
|
+
expect { people.first.name }.to raise_error ActiveModel::MissingAttributeError
|
452
|
+
end
|
390
453
|
end
|
391
454
|
|
392
455
|
it 'works with multiple fields in select' do
|
393
|
-
Article.select("title, body").
|
456
|
+
Article.select("title, body").size.should eq 31
|
394
457
|
end
|
395
458
|
|
396
459
|
it 'allows a function in the select values via Symbol#func' do
|
397
|
-
relation = Person.select(:max.func(:id).as('max_id'))
|
398
|
-
relation.first.max_id.should eq
|
460
|
+
relation = Person.select(:max.func(:id).as('max_id')).order('max_id')
|
461
|
+
relation.first.max_id.to_i.should eq 10
|
399
462
|
end
|
400
463
|
|
401
464
|
it 'allows a function in the select values via block' do
|
402
|
-
relation = Person.select{max(id).as(max_id)}
|
403
|
-
relation.first.max_id.should eq
|
465
|
+
relation = Person.select{max(id).as(max_id)}.order('max_id')
|
466
|
+
relation.first.max_id.to_i.should eq 10
|
404
467
|
end
|
405
468
|
|
406
469
|
it 'allows an operation in the select values via block' do
|
407
|
-
relation =
|
408
|
-
|
470
|
+
relation =
|
471
|
+
if SQLITE_ENV
|
472
|
+
Person.select{[id, (id + 1).as('id_plus_one')]}.where('id_plus_one = 2')
|
473
|
+
else
|
474
|
+
Person.select{[id, (id + 1).as('id_plus_one')]}.where{(id + 1) == 2}
|
475
|
+
end
|
476
|
+
relation.first.id.should eq 1
|
409
477
|
end
|
410
478
|
|
411
479
|
it 'allows custom operators in the select values via block' do
|
412
|
-
|
413
|
-
|
480
|
+
if MYSQL_ENV
|
481
|
+
pending "MySQL doesn't support concating string with ||."
|
482
|
+
else
|
483
|
+
relation = Person.select{name.op('||', '-diddly').as(flanderized_name)}
|
484
|
+
relation.first.flanderized_name.should eq Person.first.name + '-diddly'
|
485
|
+
end
|
414
486
|
end
|
415
487
|
|
416
488
|
it 'allows a subquery in the select values' do
|
417
489
|
subquery = Article.where(:person_id => 1).select(:id).order{id.desc}.limit(1)
|
418
490
|
relation = Person.where(:id => 1).select{[id, name, subquery.as('last_article_id')]}
|
419
491
|
aric = relation.first
|
420
|
-
aric.last_article_id.should eq Article.where(:person_id => 1).last.id
|
492
|
+
aric.last_article_id.to_i.should eq Article.where(:person_id => 1).last.id
|
421
493
|
end
|
422
494
|
|
423
495
|
end
|
@@ -425,7 +497,7 @@ module Squeel
|
|
425
497
|
describe '#count' do
|
426
498
|
|
427
499
|
it 'works with non-strings in select' do
|
428
|
-
Article.select{distinct(title)}.count.should eq
|
500
|
+
Article.select{distinct(title)}.count.should eq 31
|
429
501
|
end
|
430
502
|
|
431
503
|
it 'works with non-strings in wheres' do
|
@@ -463,17 +535,17 @@ module Squeel
|
|
463
535
|
|
464
536
|
it 'builds compound conditions with a block' do
|
465
537
|
block = Person.where{(name == 'bob') & (salary == 100000)}
|
466
|
-
block.to_sql.should match
|
538
|
+
block.to_sql.should match /#{Q}people#{Q}.#{Q}name#{Q} = 'bob'/
|
467
539
|
block.to_sql.should match /AND/
|
468
|
-
block.to_sql.should match
|
540
|
+
block.to_sql.should match /#{Q}people#{Q}.#{Q}salary#{Q} = 100000/
|
469
541
|
end
|
470
542
|
|
471
543
|
it 'allows mixing hash and operator syntax inside a block' do
|
472
544
|
block = Person.joins(:comments).
|
473
545
|
where{(name == 'bob') & {comments => (body == 'First post!')}}
|
474
|
-
block.to_sql.should match
|
546
|
+
block.to_sql.should match /#{Q}people#{Q}.#{Q}name#{Q} = 'bob'/
|
475
547
|
block.to_sql.should match /AND/
|
476
|
-
block.to_sql.should match
|
548
|
+
block.to_sql.should match /#{Q}comments#{Q}.#{Q}body#{Q} = 'First post!'/
|
477
549
|
end
|
478
550
|
|
479
551
|
it 'allows a condition on a function via block' do
|
@@ -496,6 +568,12 @@ module Squeel
|
|
496
568
|
people_and_article_notes.should have(40).items
|
497
569
|
end
|
498
570
|
|
571
|
+
it 'maps conditions onto their proper table with a polymorphic belongs_to join followed by a polymorphic has_many join' do
|
572
|
+
relation = Note.joins{notable(Article).notes}.
|
573
|
+
where{notable(Article).notes.note.eq('zomg')}
|
574
|
+
relation.to_sql.should match /#{Q}notes_articles#{Q}\.#{Q}note#{Q} = 'zomg'/
|
575
|
+
end
|
576
|
+
|
499
577
|
it 'allows a subquery on the value side of a predicate' do
|
500
578
|
names = [Person.first.name, Person.last.name]
|
501
579
|
old_and_busted = Person.where(:name => names)
|
@@ -504,6 +582,21 @@ module Squeel
|
|
504
582
|
old_and_busted.to_a.should eq new_hotness.to_a
|
505
583
|
end
|
506
584
|
|
585
|
+
it 'allows a subquery from an association in a hash' do
|
586
|
+
scope = Person.first.articles
|
587
|
+
articles = scope.where(:id => scope)
|
588
|
+
articles.should have(3).articles
|
589
|
+
|
590
|
+
articles = Tag.all.second.articles.where(:id => scope)
|
591
|
+
articles.should have(1).articles
|
592
|
+
end
|
593
|
+
|
594
|
+
it 'allows a subquery from an association in a Squeel node' do
|
595
|
+
scope = Person.first.articles
|
596
|
+
articles = scope.where{id.in scope}
|
597
|
+
articles.should have(3).articles
|
598
|
+
end
|
599
|
+
|
507
600
|
it 'is backwards-compatible with "where.not"' do
|
508
601
|
if activerecord_version_at_least '4.0.0'
|
509
602
|
name = Person.first.name
|
@@ -517,37 +610,61 @@ module Squeel
|
|
517
610
|
it 'allows equality conditions against a belongs_to with an AR::Base value' do
|
518
611
|
first_person = Person.first
|
519
612
|
relation = Article.where { person.eq first_person }
|
520
|
-
relation.to_sql.should match
|
613
|
+
relation.to_sql.should match /#{Q}articles#{Q}.#{Q}person_id#{Q} = #{first_person.id}/
|
521
614
|
end
|
522
615
|
|
523
616
|
it 'allows equality conditions against a polymorphic belongs_to with an AR::Base value' do
|
524
617
|
first_person = Person.first
|
525
618
|
relation = Note.where { notable.eq first_person }
|
526
|
-
relation.to_sql.should match
|
619
|
+
relation.to_sql.should match /#{Q}notes#{Q}.#{Q}notable_id#{Q} = #{first_person.id} AND #{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Person'/
|
527
620
|
end
|
528
621
|
|
529
622
|
it 'allows inequality conditions against a belongs_to with an AR::Base value' do
|
530
623
|
first_person = Person.first
|
531
624
|
relation = Article.where { person.not_eq first_person }
|
532
|
-
relation.to_sql.should match
|
625
|
+
relation.to_sql.should match /#{Q}articles#{Q}.#{Q}person_id#{Q} != #{first_person.id}/
|
533
626
|
end
|
534
627
|
|
535
628
|
it 'allows inequality conditions against a polymorphic belongs_to with an AR::Base value' do
|
536
629
|
first_person = Person.first
|
537
630
|
relation = Note.where { notable.not_eq first_person }
|
538
|
-
relation.to_sql.should match /\(
|
631
|
+
relation.to_sql.should match /\(#{Q}notes#{Q}.#{Q}notable_id#{Q} != #{first_person.id} OR #{Q}notes#{Q}.#{Q}notable_type#{Q} != 'Person'\)/
|
539
632
|
end
|
540
633
|
|
541
634
|
it 'allows hash equality conditions against a belongs_to with an AR::Base value' do
|
542
635
|
first_person = Person.first
|
543
636
|
relation = Article.where(:person => first_person)
|
544
|
-
relation.to_sql.should match
|
637
|
+
relation.to_sql.should match /#{Q}articles#{Q}.#{Q}person_id#{Q} = #{first_person.id}/
|
545
638
|
end
|
546
639
|
|
547
640
|
it 'allows hash equality conditions against a polymorphic belongs_to with an AR::Base value' do
|
548
641
|
first_person = Person.first
|
549
642
|
relation = Note.where(:notable => first_person)
|
550
|
-
relation.to_sql.should match
|
643
|
+
relation.to_sql.should match /#{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Person'/
|
644
|
+
relation.to_sql.should match /#{Q}notes#{Q}.#{Q}notable_id#{Q} = #{first_person.id}/
|
645
|
+
end
|
646
|
+
|
647
|
+
it 'keeps original AR hashes behavior' do
|
648
|
+
relation = Person.joins(:articles).where(articles: { person_id: Person.first })
|
649
|
+
relation.to_sql.should match /SELECT #{Q}people#{Q}.* FROM #{Q}people#{Q} INNER JOIN #{Q}articles#{Q} ON #{Q}articles#{Q}.#{Q}person_id#{Q} = #{Q}people#{Q}.#{Q}id#{Q} WHERE #{Q}articles#{Q}.#{Q}person_id#{Q} = 1/
|
650
|
+
|
651
|
+
relation = Person.joins(:articles).where(articles: { person_id: Person.all.to_a })
|
652
|
+
relation.to_sql.should match /SELECT #{Q}people#{Q}.\* FROM #{Q}people#{Q} INNER JOIN #{Q}articles#{Q} ON #{Q}articles#{Q}.#{Q}person_id#{Q} = #{Q}people#{Q}.#{Q}id#{Q} WHERE #{Q}articles#{Q}.#{Q}person_id#{Q} IN \(1, 2, 3, 4, 5, 6, 7, 8, 9, 10\)/
|
653
|
+
end
|
654
|
+
|
655
|
+
it 'returns ActiveRecord::Relation after complex associations, joins and wheres' do
|
656
|
+
relation = Note.first.notable.articles.joins(:comments).where{comments.article_id != nil}
|
657
|
+
|
658
|
+
relation.should be_kind_of(::ActiveRecord::Relation)
|
659
|
+
relation.first.should be_kind_of(Article)
|
660
|
+
end
|
661
|
+
|
662
|
+
it 'uses Squeel and Arel at the same time' do
|
663
|
+
relation = User.where{id.in([1,2,3]) & User.arel_table[:id].not_eq(nil) }
|
664
|
+
relation.to_sql.should match /SELECT #{Q}users#{Q}.\* FROM #{Q}users#{Q}\s+WHERE \(\(#{Q}users#{Q}.#{Q}id#{Q} IN \(1, 2, 3\) AND #{Q}users#{Q}.#{Q}id#{Q} IS NOT NULL\)\)/
|
665
|
+
relation = User.where{
|
666
|
+
(id.in([1,2,3]) | User.arel_table[:id].eq(1)) & ((id == 1) | User.arel_table[:id].not_eq(nil)) }
|
667
|
+
relation.to_sql.should match /SELECT #{Q}users#{Q}.\* FROM #{Q}users#{Q}\s+WHERE \(\(\(#{Q}users#{Q}.#{Q}id#{Q} IN \(1, 2, 3\) OR #{Q}users#{Q}.#{Q}id#{Q} = 1\) AND \(#{Q}users#{Q}.#{Q}id#{Q} = 1 OR #{Q}users#{Q}.#{Q}id#{Q} IS NOT NULL\)\)\)/
|
551
668
|
end
|
552
669
|
|
553
670
|
end
|
@@ -577,25 +694,66 @@ module Squeel
|
|
577
694
|
|
578
695
|
it 'joins polymorphic belongs_to associations' do
|
579
696
|
relation = Note.joins{notable(Article)}
|
580
|
-
relation.to_sql.should match
|
697
|
+
relation.to_sql.should match /#{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Article'/
|
581
698
|
end
|
582
699
|
|
583
700
|
it 'joins multiple polymorphic belongs_to associations' do
|
584
701
|
relation = Note.joins{[notable(Article), notable(Person)]}
|
585
|
-
relation.to_sql.should match
|
586
|
-
relation.to_sql.should match
|
702
|
+
relation.to_sql.should match /#{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Article'/
|
703
|
+
relation.to_sql.should match /#{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Person'/
|
587
704
|
end
|
588
705
|
|
589
706
|
it "only joins once, even if two join types are used" do
|
590
|
-
|
591
|
-
|
707
|
+
if activerecord_version_at_least('4.1.0')
|
708
|
+
pending "It's unreasonable to join once only, in some cases, we need twice."
|
709
|
+
else
|
710
|
+
relation = Person.joins(:articles.inner, :articles.outer)
|
711
|
+
relation.to_sql.scan("JOIN").size.should eq 1
|
712
|
+
end
|
592
713
|
end
|
593
714
|
|
594
715
|
it 'joins a keypath' do
|
595
716
|
relation = Note.joins{notable(Article).person.children}
|
596
|
-
relation.to_sql.should match /SELECT
|
717
|
+
relation.to_sql.should match /SELECT #{Q}notes#{Q}.* FROM #{Q}notes#{Q} INNER JOIN #{Q}articles#{Q} ON #{Q}articles#{Q}.#{Q}id#{Q} = #{Q}notes#{Q}.#{Q}notable_id#{Q} AND #{Q}notes#{Q}.#{Q}notable_type#{Q} = 'Article' INNER JOIN #{Q}people#{Q} ON #{Q}people#{Q}.#{Q}id#{Q} = #{Q}articles#{Q}.#{Q}person_id#{Q} INNER JOIN #{Q}people#{Q} #{Q}children_people#{Q} ON #{Q}children_people#{Q}.#{Q}parent_id#{Q} = #{Q}people#{Q}.#{Q}id#{Q}/
|
597
718
|
end
|
598
719
|
|
720
|
+
it 'validates polymorphic relationship with source type' do
|
721
|
+
if activerecord_version_at_least '3.2.7'
|
722
|
+
Group.first.users.to_sql.should match /#{Q}memberships#{Q}.#{Q}member_type#{Q} = 'User'/
|
723
|
+
else
|
724
|
+
Group.first.users.size.should eq 1
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
it 'joins an ActiveRecord::Relation subquery' do
|
729
|
+
subquery = OrderItem.
|
730
|
+
group(:orderable_id).
|
731
|
+
select { [orderable_id, sum(quantity * unit_price).as(amount)] }
|
732
|
+
|
733
|
+
relation = Seat.
|
734
|
+
joins { [payment.outer,
|
735
|
+
subquery.as('seat_order_items').on { id == seat_order_items.orderable_id}.outer] }.
|
736
|
+
select { [seat_order_items.amount, "seats.*"] }.
|
737
|
+
where { seat_order_items.amount > 0 }
|
738
|
+
|
739
|
+
relation.debug_sql.should match /SELECT #{Q}seat_order_items#{Q}.#{Q}amount#{Q}, seats.\* FROM #{Q}seats#{Q} LEFT OUTER JOIN #{Q}payments#{Q} ON #{Q}payments#{Q}.#{Q}id#{Q} = #{Q}seats#{Q}.#{Q}payment_id#{Q} LEFT OUTER JOIN \(SELECT #{Q}order_items#{Q}.#{Q}orderable_id#{Q}, sum\(#{Q}order_items#{Q}.#{Q}quantity#{Q} \* #{Q}order_items#{Q}.#{Q}unit_price#{Q}\) AS amount FROM #{Q}order_items#{Q}\s+GROUP BY #{Q}order_items#{Q}.#{Q}orderable_id#{Q}\) seat_order_items ON #{Q}seats#{Q}.#{Q}id#{Q} = #{Q}seat_order_items#{Q}.#{Q}orderable_id#{Q} WHERE #{Q}seat_order_items#{Q}.#{Q}amount#{Q} > 0/
|
740
|
+
relation.to_a.should have(10).seats
|
741
|
+
relation.to_a.second.amount.to_i.should eq(10)
|
742
|
+
end
|
743
|
+
|
744
|
+
it 'joins from an association with default scopes' do
|
745
|
+
if activerecord_version_at_least('3.1.0')
|
746
|
+
if MYSQL_ENV
|
747
|
+
User.first.groups.to_sql.should match /#{Q}memberships#{Q}.#{Q}active#{Q} = 1/
|
748
|
+
else
|
749
|
+
puts User.first.groups.to_sql
|
750
|
+
User.first.groups.to_sql.should match /#{Q}memberships#{Q}.#{Q}active#{Q} = 't'/
|
751
|
+
end
|
752
|
+
|
753
|
+
else
|
754
|
+
pending "Rails 3.0.x doesn't support to_sql in an association."
|
755
|
+
end
|
756
|
+
end
|
599
757
|
end
|
600
758
|
|
601
759
|
describe '#having' do
|
@@ -607,8 +765,12 @@ module Squeel
|
|
607
765
|
end
|
608
766
|
|
609
767
|
it 'allows complex conditions on aggregate columns' do
|
610
|
-
|
611
|
-
|
768
|
+
if SQLITE_ENV
|
769
|
+
relation = Person.group(:parent_id).having{salary == max(salary)}
|
770
|
+
relation.first.name.should eq Person.last.name
|
771
|
+
else
|
772
|
+
pending "MySQL & PG don't support this type of group & having clauses, don't use it."
|
773
|
+
end
|
612
774
|
end
|
613
775
|
|
614
776
|
it 'allows a condition on a function via block' do
|
@@ -627,13 +789,13 @@ module Squeel
|
|
627
789
|
|
628
790
|
it 'builds options with a block' do
|
629
791
|
block = Person.order{name}
|
630
|
-
block.to_sql.should match /ORDER BY
|
792
|
+
block.to_sql.should match /ORDER BY #{Q}people#{Q}.#{Q}name#{Q}/
|
631
793
|
end
|
632
794
|
|
633
795
|
it 'allows AR 4.0-style hash options' do
|
634
796
|
if activerecord_version_at_least '4.0.0'
|
635
797
|
block = Person.order(:name => :desc)
|
636
|
-
block.to_sql.should match /ORDER BY
|
798
|
+
block.to_sql.should match /ORDER BY #{Q}people#{Q}.#{Q}name#{Q} DESC/
|
637
799
|
else
|
638
800
|
pending 'Not required in AR versions < 4.0.0'
|
639
801
|
end
|
@@ -641,7 +803,7 @@ module Squeel
|
|
641
803
|
|
642
804
|
it 'allows ordering by an attributes of a joined table' do
|
643
805
|
relation = Article.joins(:person).order { person.id.asc }
|
644
|
-
relation.to_sql.should match /ORDER BY
|
806
|
+
relation.to_sql.should match /ORDER BY #{Q}people#{Q}.#{Q}id#{Q} ASC/
|
645
807
|
end
|
646
808
|
|
647
809
|
end
|
@@ -654,7 +816,7 @@ module Squeel
|
|
654
816
|
it 'builds options with a block' do
|
655
817
|
block = @standard.reorder{id}
|
656
818
|
block.to_sql.should_not eq @standard.to_sql
|
657
|
-
block.to_sql.should match /ORDER BY
|
819
|
+
block.to_sql.should match /ORDER BY #{Q}people#{Q}.#{Q}id#{Q}/
|
658
820
|
end
|
659
821
|
|
660
822
|
it 'drops order by clause when passed nil' do
|
@@ -673,7 +835,7 @@ module Squeel
|
|
673
835
|
|
674
836
|
describe '#from' do
|
675
837
|
it 'creates froms with a block' do
|
676
|
-
expected = /SELECT
|
838
|
+
expected = /SELECT #{Q}sub#{Q}.#{Q}name#{Q} AS aliased_name FROM \(SELECT #{Q}people#{Q}.#{Q}name#{Q} FROM #{Q}people#{Q}\s*\) sub/
|
677
839
|
block = Person.from{Person.select{name}.as('sub')}.
|
678
840
|
select{sub.name.as('aliased_name')}
|
679
841
|
sql = block.to_sql
|
@@ -681,7 +843,7 @@ module Squeel
|
|
681
843
|
end
|
682
844
|
|
683
845
|
it 'creates froms from literals' do
|
684
|
-
expected = /SELECT
|
846
|
+
expected = /SELECT #{Q}people#{Q}.* FROM sub/
|
685
847
|
relation = Person.from('sub')
|
686
848
|
sql = relation.to_sql
|
687
849
|
sql.should match expected
|
@@ -689,7 +851,7 @@ module Squeel
|
|
689
851
|
|
690
852
|
it 'creates froms from relations' do
|
691
853
|
if activerecord_version_at_least '4.0.0'
|
692
|
-
expected = "SELECT
|
854
|
+
expected = "SELECT #{Q}people#{Q}.* FROM (SELECT #{Q}people#{Q}.* FROM #{Q}people#{Q}) alias"
|
693
855
|
relation = Person.from(Person.all, 'alias')
|
694
856
|
sql = relation.to_sql
|
695
857
|
sql.should == expected
|
@@ -697,6 +859,17 @@ module Squeel
|
|
697
859
|
pending 'Unsupported before ActiveRecord 4.0'
|
698
860
|
end
|
699
861
|
end
|
862
|
+
|
863
|
+
it 'binds params from CollectionProxy subquery' do
|
864
|
+
if activerecord_version_at_least('3.1.0')
|
865
|
+
first_article = Article.first
|
866
|
+
expected_tags = Tag.where(id: [1,2,3]).order{name}.to_a
|
867
|
+
|
868
|
+
expected_tags.should == Tag.from{first_article.tags.as(Tag.table_name)}.order{tags.name}.to_a
|
869
|
+
else
|
870
|
+
pending "ActiveRecord 3.0.x doesn't support CollectionProxy chain."
|
871
|
+
end
|
872
|
+
end
|
700
873
|
end
|
701
874
|
|
702
875
|
describe '#build_where' do
|
@@ -718,7 +891,12 @@ module Squeel
|
|
718
891
|
|
719
892
|
it 'adds hash where values without converting to Arel predicates' do
|
720
893
|
wheres = Person.where({:name => 'bob'}).where_values
|
721
|
-
|
894
|
+
if activerecord_version_at_least('4.0.0')
|
895
|
+
wheres.flatten.should have(1).equality
|
896
|
+
wheres.flatten.last.should be_kind_of(Arel::Nodes::Equality)
|
897
|
+
else
|
898
|
+
wheres.should eq [{:name => 'bob'}]
|
899
|
+
end
|
722
900
|
end
|
723
901
|
|
724
902
|
end
|
@@ -729,8 +907,12 @@ module Squeel
|
|
729
907
|
relation = Person.includes(:comments, :articles).
|
730
908
|
where(:comments => {:body => 'First post!'}).
|
731
909
|
where(:articles => {:title => 'Hello, world!'})
|
732
|
-
|
733
|
-
|
910
|
+
if activerecord_version_at_least('4.1.0')
|
911
|
+
relation.debug_sql.should_not eq relation.arel.to_sql
|
912
|
+
else
|
913
|
+
relation.debug_sql.should_not eq relation.to_sql
|
914
|
+
end
|
915
|
+
relation.debug_sql.should match /SELECT #{Q}people#{Q}.#{Q}id#{Q} AS t0_r0/
|
734
916
|
end
|
735
917
|
|
736
918
|
end
|
@@ -802,22 +984,27 @@ module Squeel
|
|
802
984
|
it 'merges relations with the same base' do
|
803
985
|
relation = Person.where{name == 'bob'}.merge(Person.where{salary == 100000})
|
804
986
|
sql = relation.to_sql
|
805
|
-
sql.should match
|
806
|
-
sql.should match
|
987
|
+
sql.should match /#{Q}people#{Q}.#{Q}name#{Q} = 'bob'/
|
988
|
+
sql.should match /#{Q}people#{Q}.#{Q}salary#{Q} = 100000/
|
807
989
|
end
|
808
990
|
|
809
991
|
it 'merges relations with a different base' do
|
810
992
|
relation = Person.where{name == 'bob'}.joins(:articles).merge(Article.where{title == 'Hello world!'})
|
811
993
|
sql = relation.to_sql
|
812
|
-
sql.should match /INNER JOIN
|
813
|
-
sql.should match
|
814
|
-
sql.should match
|
994
|
+
sql.should match /INNER JOIN #{Q}articles#{Q} ON #{Q}articles#{Q}.#{Q}person_id#{Q} = #{Q}people#{Q}.#{Q}id#{Q}/
|
995
|
+
sql.should match /#{Q}people#{Q}.#{Q}name#{Q} = 'bob'/
|
996
|
+
sql.should match /#{Q}articles#{Q}.#{Q}title#{Q} = 'Hello world!'/
|
815
997
|
end
|
816
998
|
|
817
999
|
it 'does not break hm:t with conditions' do
|
818
1000
|
relation = Person.first.condition_article_comments
|
819
|
-
sql =
|
820
|
-
|
1001
|
+
sql =
|
1002
|
+
if activerecord_version_at_least('4.1.0')
|
1003
|
+
relation.to_sql
|
1004
|
+
else
|
1005
|
+
relation.scoped.to_sql
|
1006
|
+
end
|
1007
|
+
sql.should match /#{Q}articles#{Q}.#{Q}title#{Q} = 'Condition'/
|
821
1008
|
end
|
822
1009
|
|
823
1010
|
it 'uses the last condition in the case of a conflicting where' do
|
@@ -831,7 +1018,13 @@ module Squeel
|
|
831
1018
|
|
832
1019
|
it 'uses the given equality condition in the case of a conflicting where from a default scope' do
|
833
1020
|
if activerecord_version_at_least '3.1'
|
834
|
-
relation =
|
1021
|
+
relation =
|
1022
|
+
if activerecord_version_at_least('4.1.0')
|
1023
|
+
PersonNamedBill.rewhere(name: 'Ernie')
|
1024
|
+
# Or PersonNamedBill.unscope(where: :name).where { name == 'Ernie' }
|
1025
|
+
else
|
1026
|
+
PersonNamedBill.where{name == 'Ernie'}
|
1027
|
+
end
|
835
1028
|
sql = relation.to_sql
|
836
1029
|
sql.should_not match /Bill/
|
837
1030
|
sql.should match /Ernie/
|
@@ -844,53 +1037,82 @@ module Squeel
|
|
844
1037
|
relation = Person.with_article_title('hi').
|
845
1038
|
with_article_condition_title('yo')
|
846
1039
|
sql = relation.to_sql
|
847
|
-
sql.should match
|
848
|
-
sql.should match
|
1040
|
+
sql.should match /#{Q}articles#{Q}.#{Q}title#{Q} = 'hi'/
|
1041
|
+
sql.should match /#{Q}articles_with_conditions_people#{Q}.#{Q}title#{Q} = 'yo'/
|
849
1042
|
end
|
850
1043
|
|
851
1044
|
it "doesn't ruin everything when a scope returns nil" do
|
852
1045
|
relation = Person.nil_scope
|
853
|
-
|
1046
|
+
if activerecord_version_at_least('4.1.0')
|
1047
|
+
relation.should eq Person.all
|
1048
|
+
else
|
1049
|
+
relation.should eq Person.scoped
|
1050
|
+
end
|
854
1051
|
end
|
855
1052
|
|
856
1053
|
it "doesn't ruin everything when a group exists" do
|
857
|
-
relation = Person.scoped.merge(Person.group{name})
|
858
1054
|
count_hash = {}
|
859
|
-
|
860
|
-
|
1055
|
+
if activerecord_version_at_least('4.1.0')
|
1056
|
+
relation = Person.all.merge(Person.group{name})
|
1057
|
+
expect { count_hash = relation.count }.should_not raise_error
|
1058
|
+
count_hash.size.should eq Person.all.size
|
1059
|
+
else
|
1060
|
+
relation = Person.scoped.merge(Person.group{name})
|
1061
|
+
expect { count_hash = relation.count }.should_not raise_error
|
1062
|
+
count_hash.size.should eq Person.scoped.size
|
1063
|
+
end
|
1064
|
+
|
861
1065
|
count_hash.values.all? {|v| v == 1}.should be_true
|
862
1066
|
count_hash.keys.should =~ Person.select{name}.map(&:name)
|
863
1067
|
end
|
864
1068
|
|
865
1069
|
it "doesn't merge the default scope more than once" do
|
866
|
-
relation =
|
1070
|
+
relation =
|
1071
|
+
if activerecord_version_at_least('4.1.0')
|
1072
|
+
PersonNamedBill.all.highly_compensated.ending_with_ill
|
1073
|
+
else
|
1074
|
+
PersonNamedBill.scoped.highly_compensated.ending_with_ill
|
1075
|
+
end
|
867
1076
|
sql = relation.to_sql
|
868
|
-
sql.scan(
|
869
|
-
sql.scan(
|
870
|
-
sql.scan(
|
871
|
-
sql.scan(
|
1077
|
+
sql.scan(/#{Q}people#{Q}.#{Q}name#{Q} = 'Bill'/).should have(1).item
|
1078
|
+
sql.scan(/#{Q}people#{Q}.#{Q}name#{Q} [I]*LIKE '%ill'/).should have(1).item
|
1079
|
+
sql.scan(/#{Q}people#{Q}.#{Q}salary#{Q} > 200000/).should have(1).item
|
1080
|
+
sql.scan(/#{Q}people#{Q}.#{Q}id#{Q}/).should have(1).item
|
872
1081
|
end
|
873
1082
|
|
874
1083
|
it "doesn't hijack the table name when merging a relation with different base and default_scope" do
|
875
|
-
relation =
|
1084
|
+
relation =
|
1085
|
+
if activerecord_version_at_least('4.1.0')
|
1086
|
+
Article.joins(:person).merge(PersonNamedBill.all)
|
1087
|
+
else
|
1088
|
+
Article.joins(:person).merge(PersonNamedBill.scoped)
|
1089
|
+
end
|
876
1090
|
sql = relation.to_sql
|
877
|
-
sql.scan(
|
1091
|
+
sql.scan(/#{Q}people#{Q}.#{Q}name#{Q} = 'Bill'/).should have(1).item
|
878
1092
|
end
|
879
1093
|
|
880
1094
|
it 'merges scopes that contain functions' do
|
881
|
-
relation =
|
1095
|
+
relation =
|
1096
|
+
if activerecord_version_at_least('4.1.0')
|
1097
|
+
PersonNamedBill.all.with_salary_equal_to(100)
|
1098
|
+
else
|
1099
|
+
PersonNamedBill.scoped.with_salary_equal_to(100)
|
1100
|
+
end
|
882
1101
|
sql = relation.to_sql
|
883
|
-
sql.should match /abs\(
|
1102
|
+
sql.should match /abs\(#{Q}people#{Q}.#{Q}salary#{Q}\) = 100/
|
884
1103
|
end
|
885
1104
|
|
886
1105
|
it 'uses last equality when merging two scopes with identical function equalities' do
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
1106
|
+
if activerecord_version_at_least('4.1.0')
|
1107
|
+
pending "Named Functions can't be unscoped"
|
1108
|
+
else
|
1109
|
+
relation = PersonNamedBill.scoped.with_salary_equal_to(100).
|
1110
|
+
with_salary_equal_to(200)
|
1111
|
+
sql = relation.to_sql
|
1112
|
+
sql.should_not match /abs\(#{Q}people#{Q}.#{Q}salary#{Q}\) = 100/
|
1113
|
+
sql.should match /abs\(#{Q}people#{Q}.#{Q}salary#{Q}\) = 200/
|
1114
|
+
end
|
892
1115
|
end
|
893
|
-
|
894
1116
|
end
|
895
1117
|
|
896
1118
|
describe '#to_a' do
|
@@ -908,14 +1130,18 @@ module Squeel
|
|
908
1130
|
relation = UnidentifiedObject.where{person_id < 120}.includes(:person)
|
909
1131
|
queries = queries_for do
|
910
1132
|
vals = relation.to_a
|
911
|
-
vals.should have(
|
1133
|
+
vals.should have(20).items
|
912
1134
|
end
|
913
1135
|
|
914
|
-
|
1136
|
+
if activerecord_version_at_least('3.1.0')
|
1137
|
+
queries.should have(2).queries
|
1138
|
+
else
|
1139
|
+
puts 'skips count of queries expectation'
|
1140
|
+
end
|
915
1141
|
|
916
1142
|
matched_ids = queries.last.match(/IN \(([^)]*)/).captures.first
|
917
1143
|
matched_ids = matched_ids.split(/,\s*/).map(&:to_i)
|
918
|
-
matched_ids.should =~ [1,
|
1144
|
+
matched_ids.should =~ [1, 2, 3, 4, 5 ,6 ,7 ,8 ,9 ,10]
|
919
1145
|
end
|
920
1146
|
|
921
1147
|
end
|