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.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +36 -4
  5. data/CHANGELOG.md +15 -0
  6. data/Gemfile +1 -1
  7. data/README.md +47 -6
  8. data/Rakefile +14 -2
  9. data/lib/squeel.rb +9 -1
  10. data/lib/squeel/adapters/active_record.rb +0 -1
  11. data/lib/squeel/adapters/active_record/3.0/join_dependency_extensions.rb +1 -0
  12. data/lib/squeel/adapters/active_record/3.0/relation_extensions.rb +12 -1
  13. data/lib/squeel/adapters/active_record/3.1/join_dependency_extensions.rb +1 -0
  14. data/lib/squeel/adapters/active_record/3.2/join_dependency_extensions.rb +1 -0
  15. data/lib/squeel/adapters/active_record/4.0/join_dependency_extensions.rb +1 -0
  16. data/lib/squeel/adapters/active_record/4.0/relation_extensions.rb +92 -0
  17. data/lib/squeel/adapters/active_record/4.1/compat.rb +15 -0
  18. data/lib/squeel/adapters/active_record/4.1/context.rb +88 -0
  19. data/lib/squeel/adapters/active_record/4.1/preloader_extensions.rb +31 -0
  20. data/lib/squeel/adapters/active_record/4.1/reflection_extensions.rb +37 -0
  21. data/lib/squeel/adapters/active_record/4.1/relation_extensions.rb +307 -0
  22. data/lib/squeel/adapters/active_record/4.2/compat.rb +1 -0
  23. data/lib/squeel/adapters/active_record/4.2/context.rb +1 -0
  24. data/lib/squeel/adapters/active_record/4.2/preloader_extensions.rb +1 -0
  25. data/lib/squeel/adapters/active_record/4.2/relation_extensions.rb +108 -0
  26. data/lib/squeel/adapters/active_record/context.rb +7 -7
  27. data/lib/squeel/adapters/active_record/join_dependency_extensions.rb +9 -13
  28. data/lib/squeel/adapters/active_record/relation_extensions.rb +38 -8
  29. data/lib/squeel/core_ext/symbol.rb +3 -3
  30. data/lib/squeel/dsl.rb +1 -1
  31. data/lib/squeel/nodes.rb +1 -0
  32. data/lib/squeel/nodes/as.rb +12 -0
  33. data/lib/squeel/nodes/join.rb +8 -4
  34. data/lib/squeel/nodes/key_path.rb +10 -1
  35. data/lib/squeel/nodes/node.rb +21 -0
  36. data/lib/squeel/nodes/stub.rb +8 -4
  37. data/lib/squeel/nodes/subquery_join.rb +44 -0
  38. data/lib/squeel/version.rb +1 -1
  39. data/lib/squeel/visitors.rb +2 -0
  40. data/lib/squeel/visitors/enumeration_visitor.rb +101 -0
  41. data/lib/squeel/visitors/order_visitor.rb +9 -2
  42. data/lib/squeel/visitors/predicate_visitor.rb +11 -0
  43. data/lib/squeel/visitors/preload_visitor.rb +12 -0
  44. data/lib/squeel/visitors/visitor.rb +89 -13
  45. data/spec/config.travis.yml +13 -0
  46. data/spec/config.yml +12 -0
  47. data/spec/console.rb +3 -12
  48. data/spec/core_ext/symbol_spec.rb +3 -3
  49. data/spec/helpers/squeel_helper.rb +8 -5
  50. data/spec/spec_helper.rb +4 -16
  51. data/spec/squeel/adapters/active_record/context_spec.rb +8 -4
  52. data/spec/squeel/adapters/active_record/join_dependency_extensions_spec.rb +123 -38
  53. data/spec/squeel/adapters/active_record/relation_extensions_spec.rb +350 -124
  54. data/spec/squeel/core_ext/symbol_spec.rb +3 -3
  55. data/spec/squeel/nodes/join_spec.rb +4 -4
  56. data/spec/squeel/nodes/stub_spec.rb +3 -3
  57. data/spec/squeel/nodes/subquery_join_spec.rb +46 -0
  58. data/spec/squeel/visitors/order_visitor_spec.rb +3 -3
  59. data/spec/squeel/visitors/predicate_visitor_spec.rb +69 -36
  60. data/spec/squeel/visitors/select_visitor_spec.rb +1 -1
  61. data/spec/squeel/visitors/visitor_spec.rb +7 -6
  62. data/spec/support/models.rb +99 -15
  63. data/spec/support/schema.rb +109 -4
  64. data/squeel.gemspec +8 -6
  65. metadata +89 -107
  66. data/.ruby-gemset +0 -1
  67. data/.ruby-version +0 -1
  68. data/spec/blueprints/articles.rb +0 -5
  69. data/spec/blueprints/comments.rb +0 -5
  70. data/spec/blueprints/notes.rb +0 -3
  71. data/spec/blueprints/people.rb +0 -4
  72. 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.send(:build, :articles => :comments)
13
- @jd.join_associations.should have(2).associations
14
- @jd.join_associations.each do |association|
15
- association.join_type.should eq Arel::InnerJoin
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.send(:build, :authored_article_comments)
21
- @jd.join_associations.should have(1).association
22
- @jd.join_associations.first.table_name.should eq 'comments'
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.send(:build, Nodes::Stub.new(:articles) => Nodes::Stub.new(:comments))
27
- @jd.join_associations.should have(2).associations
28
- @jd.join_associations.each do |association|
29
- association.join_type.should eq Arel::InnerJoin
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.send(:build, dsl{children.children.parent})
37
- @jd.join_associations.should have(3).associations
38
- @jd.join_associations.each do |association|
39
- association.join_type.should eq Arel::InnerJoin
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.send(:build, dsl{{children.parent => parent}})
48
- @jd.join_associations.should have(3).associations
49
- @jd.join_associations.each do |association|
50
- association.join_type.should eq Arel::InnerJoin
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.send(:build, :articles.outer => :comments.outer)
59
- @jd.join_associations.should have(2).associations
60
- @jd.join_associations.each do |association|
61
- association.join_type.should eq Arel::OuterJoin
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
- queries.should have(1).query
19
- queries.first.should match /"people"."id" = 0/
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
- relation.join_dependency.join_associations.should have(4).items
38
- arel.to_sql.should match /INNER JOIN "people" "parents_people_2" ON "parents_people_2"."id" = "parents_people"."parent_id"/
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
- relation.join_dependency.join_associations.should have(4).items
53
- arel.to_sql.should match /LEFT OUTER JOIN "people" "children_people"/
54
- arel.to_sql.should match /LEFT OUTER JOIN "people" "parents_people_2" ON "parents_people_2"."id" = "parents_people"."parent_id"/
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
- relation.join_dependency.join_associations.should have(6).items
74
- arel.to_sql.should match /INNER JOIN "people" "parents_people_3" ON "parents_people_3"."id" = "children_people_3"."parent_id"/
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 /"people"."name" LIKE '%bob%'/
109
+ arel.to_sql.should match /#{Q}people#{Q}.#{Q}name#{Q} [I]*LIKE '%bob%'/
85
110
  end
86
111
 
87
- it 'handles multiple wheres using a keypath' do
88
- relation = Person.joins{articles}.where{articles.title == 'Hello'}.
89
- where{articles.body == 'World'}
90
- arel = relation.build_arel
91
- arel.to_sql.should match /articles/
92
- end
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 /"people"."name" LIKE '%bob%' AND "people"."name" LIKE '%joe%'/
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 /"people"."name" = 'Ernie' OR \("people"."name" LIKE 'Bob%' AND "people"."name" LIKE '%by'\)/
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
- Article.includes(:person).
249
- where{person.name == 'Ernie'}.to_a
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
- queries.should have(1).item
252
- queries.first.should match /LEFT OUTER JOIN "people"/
253
- queries.first.should match /"people"."name" = 'Ernie'/
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
- PersonNamedBill.includes(:parent).
259
- where{parent.name == 'Ernie'}.to_a
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
- queries.should have(1).item
262
- queries.first.should match /LEFT OUTER JOIN "people"/
263
- queries.first.should match /"people"."name" = 'Bill'/
264
- queries.first.should match /"parents_people"."name" = 'Ernie'/
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 /"notes"."notable_type" = 'Article'/
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 /"notes"."notable_type" = 'Article'/
277
- relation.debug_sql.should match /"notes"."notable_type" = 'Person'/
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 "only includes once, even if two join types are used" do
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
- relation.debug_sql.scan("JOIN").size.should eq 1
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
- relation = Note.includes{notable(Article).person.children}.where{notable(Article).person.children.name == 'Ernie'}
287
- 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"/
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
- queries_for {block.to_a}.should have(1).item
331
- queries_for {block.first.children}.should have(0).items
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 /"notes"."notable_type" = 'Article'/
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 /"notes"."notable_type" = 'Article'/
348
- relation.debug_sql.should match /"notes"."notable_type" = 'Person'/
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
- relation.debug_sql.scan("JOIN").size.should eq 1
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 "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"/
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(332).people
389
- expect { people.first.name }.to raise_error ActiveModel::MissingAttributeError
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").count.should eq 51
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 332
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 332
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 = Person.select{[id, (id + 1).as('id_plus_one')]}.where('id_plus_one = 2')
408
- relation.first.id.should eq 1
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
- relation = Person.select{name.op('||', '-diddly').as(flanderized_name)}
413
- relation.first.flanderized_name.should eq Person.first.name + '-diddly'
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 51
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 /"people"."name" = 'bob'/
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 /"people"."salary" = 100000/
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 /"people"."name" = 'bob'/
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 /"comments"."body" = 'First post!'/
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 /"articles"."person_id" = #{first_person.id}/
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 /"notes"."notable_id" = #{first_person.id} AND "notes"."notable_type" = 'Person'/
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 /"articles"."person_id" != #{first_person.id}/
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 /\("notes"."notable_id" != #{first_person.id} OR "notes"."notable_type" != 'Person'\)/
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 /"articles"."person_id" = #{first_person.id}/
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 /"notes"."notable_id" = #{first_person.id} AND "notes"."notable_type" = 'Person'/
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 /"notes"."notable_type" = 'Article'/
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 /"notes"."notable_type" = 'Article'/
586
- relation.to_sql.should match /"notes"."notable_type" = 'Person'/
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
- relation = Person.joins(:articles.inner, :articles.outer)
591
- relation.to_sql.scan("JOIN").size.should eq 1
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 "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"/
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
- relation = Person.group(:parent_id).having{salary == max(salary)}
611
- relation.first.name.should eq Person.last.name
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 "people"\."name"/
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 "people"\."name" DESC/
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 "people"\."id" ASC/
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 "people"."id"/
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 "sub"."name" AS aliased_name FROM \(SELECT "people"."name" FROM "people"\s*\) sub/
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 "people".* FROM sub/
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 \"people\".* FROM (SELECT \"people\".* FROM \"people\") alias"
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
- wheres.should eq [{:name => 'bob'}]
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
- relation.debug_sql.should_not eq relation.to_sql
733
- relation.debug_sql.should match /SELECT "people"."id" AS t0_r0/
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 /"people"."name" = 'bob'/
806
- sql.should match /"people"."salary" = 100000/
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 "articles" ON "articles"."person_id" = "people"."id"/
813
- sql.should match /"people"."name" = 'bob'/
814
- sql.should match /"articles"."title" = 'Hello world!'/
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 = relation.scoped.to_sql
820
- sql.should match /"articles"."title" = 'Condition'/
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 = PersonNamedBill.where{name == 'Ernie'}
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 /"articles"."title" = 'hi'/
848
- sql.should match /"articles_with_conditions_people"."title" = 'yo'/
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
- relation.should eq Person.scoped
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
- expect { count_hash = relation.count }.should_not raise_error
860
- count_hash.size.should eq Person.scoped.size
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 = PersonNamedBill.scoped.highly_compensated.ending_with_ill
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(/"people"."name" = 'Bill'/).should have(1).item
869
- sql.scan(/"people"."name" LIKE '%ill'/).should have(1).item
870
- sql.scan(/"people"."salary" > 200000/).should have(1).item
871
- sql.scan(/"people"."id"/).should have(1).item
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 = Article.joins(:person).merge(PersonNamedBill.scoped)
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(/"people"."name" = 'Bill'/).should have(1).item
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 = PersonNamedBill.scoped.with_salary_equal_to(100)
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\("people"."salary"\) = 100/
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
- relation = PersonNamedBill.scoped.with_salary_equal_to(100).
888
- with_salary_equal_to(200)
889
- sql = relation.to_sql
890
- sql.should_not match /abs\("people"."salary"\) = 100/
891
- sql.should match /abs\("people"."salary"\) = 200/
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(8).items
1133
+ vals.should have(20).items
912
1134
  end
913
1135
 
914
- queries.should have(2).queries
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, 34, 67, 100]
1144
+ matched_ids.should =~ [1, 2, 3, 4, 5 ,6 ,7 ,8 ,9 ,10]
919
1145
  end
920
1146
 
921
1147
  end