torque-postgresql 1.1.7 → 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql.rb +0 -2
  3. data/lib/torque/postgresql/adapter.rb +7 -1
  4. data/lib/torque/postgresql/adapter/database_statements.rb +6 -15
  5. data/lib/torque/postgresql/adapter/oid.rb +3 -1
  6. data/lib/torque/postgresql/adapter/oid/box.rb +2 -0
  7. data/lib/torque/postgresql/adapter/oid/circle.rb +2 -0
  8. data/lib/torque/postgresql/adapter/oid/enum.rb +2 -0
  9. data/lib/torque/postgresql/adapter/oid/enum_set.rb +2 -0
  10. data/lib/torque/postgresql/adapter/oid/interval.rb +2 -0
  11. data/lib/torque/postgresql/adapter/oid/line.rb +2 -0
  12. data/lib/torque/postgresql/adapter/oid/range.rb +2 -0
  13. data/lib/torque/postgresql/adapter/oid/segment.rb +2 -0
  14. data/lib/torque/postgresql/adapter/quoting.rb +2 -0
  15. data/lib/torque/postgresql/adapter/schema_creation.rb +20 -23
  16. data/lib/torque/postgresql/adapter/schema_definitions.rb +9 -21
  17. data/lib/torque/postgresql/adapter/schema_dumper.rb +76 -11
  18. data/lib/torque/postgresql/adapter/schema_statements.rb +4 -12
  19. data/lib/torque/postgresql/arel/infix_operation.rb +5 -1
  20. data/lib/torque/postgresql/arel/join_source.rb +2 -0
  21. data/lib/torque/postgresql/arel/nodes.rb +2 -0
  22. data/lib/torque/postgresql/arel/operations.rb +2 -0
  23. data/lib/torque/postgresql/arel/select_manager.rb +2 -0
  24. data/lib/torque/postgresql/arel/visitors.rb +6 -3
  25. data/lib/torque/postgresql/associations.rb +0 -3
  26. data/lib/torque/postgresql/associations/association.rb +5 -1
  27. data/lib/torque/postgresql/associations/association_scope.rb +20 -60
  28. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +5 -1
  29. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +2 -0
  30. data/lib/torque/postgresql/associations/builder/has_many.rb +2 -0
  31. data/lib/torque/postgresql/associations/preloader.rb +0 -32
  32. data/lib/torque/postgresql/associations/preloader/association.rb +42 -10
  33. data/lib/torque/postgresql/attributes/builder.rb +2 -0
  34. data/lib/torque/postgresql/attributes/builder/enum.rb +5 -3
  35. data/lib/torque/postgresql/attributes/builder/period.rb +6 -4
  36. data/lib/torque/postgresql/attributes/enum.rb +5 -10
  37. data/lib/torque/postgresql/attributes/enum_set.rb +2 -0
  38. data/lib/torque/postgresql/attributes/lazy.rb +3 -1
  39. data/lib/torque/postgresql/attributes/period.rb +2 -0
  40. data/lib/torque/postgresql/autosave_association.rb +9 -3
  41. data/lib/torque/postgresql/auxiliary_statement.rb +3 -13
  42. data/lib/torque/postgresql/auxiliary_statement/settings.rb +2 -0
  43. data/lib/torque/postgresql/base.rb +2 -0
  44. data/lib/torque/postgresql/coder.rb +6 -5
  45. data/lib/torque/postgresql/collector.rb +2 -0
  46. data/lib/torque/postgresql/config.rb +3 -4
  47. data/lib/torque/postgresql/geometry_builder.rb +2 -0
  48. data/lib/torque/postgresql/i18n.rb +2 -0
  49. data/lib/torque/postgresql/inheritance.rb +15 -17
  50. data/lib/torque/postgresql/migration/command_recorder.rb +2 -0
  51. data/lib/torque/postgresql/railtie.rb +2 -0
  52. data/lib/torque/postgresql/reflection.rb +2 -0
  53. data/lib/torque/postgresql/reflection/abstract_reflection.rb +28 -26
  54. data/lib/torque/postgresql/reflection/association_reflection.rb +2 -0
  55. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +6 -26
  56. data/lib/torque/postgresql/reflection/has_many_reflection.rb +2 -0
  57. data/lib/torque/postgresql/reflection/runtime_reflection.rb +2 -0
  58. data/lib/torque/postgresql/reflection/through_reflection.rb +2 -0
  59. data/lib/torque/postgresql/relation.rb +10 -11
  60. data/lib/torque/postgresql/relation/auxiliary_statement.rb +7 -8
  61. data/lib/torque/postgresql/relation/distinct_on.rb +3 -1
  62. data/lib/torque/postgresql/relation/inheritance.rb +2 -0
  63. data/lib/torque/postgresql/relation/merger.rb +2 -0
  64. data/lib/torque/postgresql/schema_cache.rb +2 -0
  65. data/lib/torque/postgresql/version.rb +3 -1
  66. data/spec/en.yml +19 -0
  67. data/spec/factories/authors.rb +6 -0
  68. data/spec/factories/comments.rb +13 -0
  69. data/spec/factories/posts.rb +6 -0
  70. data/spec/factories/tags.rb +5 -0
  71. data/spec/factories/texts.rb +5 -0
  72. data/spec/factories/users.rb +6 -0
  73. data/spec/factories/videos.rb +5 -0
  74. data/spec/mocks/cache_query.rb +16 -0
  75. data/spec/mocks/create_table.rb +35 -0
  76. data/spec/models/activity.rb +3 -0
  77. data/spec/models/activity_book.rb +4 -0
  78. data/spec/models/activity_post.rb +7 -0
  79. data/spec/models/activity_post/sample.rb +4 -0
  80. data/spec/models/author.rb +4 -0
  81. data/spec/models/author_journalist.rb +4 -0
  82. data/spec/models/comment.rb +3 -0
  83. data/spec/models/course.rb +2 -0
  84. data/spec/models/geometry.rb +2 -0
  85. data/spec/models/guest_comment.rb +4 -0
  86. data/spec/models/post.rb +6 -0
  87. data/spec/models/tag.rb +2 -0
  88. data/spec/models/text.rb +2 -0
  89. data/spec/models/time_keeper.rb +2 -0
  90. data/spec/models/user.rb +8 -0
  91. data/spec/models/video.rb +2 -0
  92. data/spec/schema.rb +141 -0
  93. data/spec/spec_helper.rb +59 -0
  94. data/spec/tests/arel_spec.rb +74 -0
  95. data/spec/tests/auxiliary_statement_spec.rb +593 -0
  96. data/spec/tests/belongs_to_many_spec.rb +246 -0
  97. data/spec/tests/coder_spec.rb +367 -0
  98. data/spec/tests/collector_spec.rb +59 -0
  99. data/spec/tests/distinct_on_spec.rb +65 -0
  100. data/spec/tests/enum_set_spec.rb +306 -0
  101. data/spec/tests/enum_spec.rb +628 -0
  102. data/spec/tests/geometric_builder_spec.rb +221 -0
  103. data/spec/tests/has_many_spec.rb +400 -0
  104. data/spec/tests/interval_spec.rb +167 -0
  105. data/spec/tests/lazy_spec.rb +24 -0
  106. data/spec/tests/period_spec.rb +954 -0
  107. data/spec/tests/quoting_spec.rb +24 -0
  108. data/spec/tests/range_spec.rb +36 -0
  109. data/spec/tests/relation_spec.rb +57 -0
  110. data/spec/tests/table_inheritance_spec.rb +416 -0
  111. metadata +102 -14
  112. data/lib/torque/postgresql/associations/join_dependency/join_association.rb +0 -15
  113. data/lib/torque/postgresql/schema_dumper.rb +0 -91
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'Arel' do
4
+ context 'on inflix operation' do
5
+ let(:list) { Torque::PostgreSQL::Arel::INFLIX_OPERATION }
6
+ let(:collector) { ::Arel::Collectors::SQLString }
7
+ let(:attribute) { ::Arel::Table.new('a')['sample'] }
8
+ let(:conn) { ActiveRecord::Base.connection }
9
+ let(:visitor) { ::Arel::Visitors::PostgreSQL.new(conn) }
10
+
11
+ [
12
+ [:overlaps, [1, 2], "ARRAY[1, 2]"],
13
+ [:contains, [3, 4], "ARRAY[3, 4]"],
14
+ [:contained_by, [5, 6], "ARRAY[5, 6]"],
15
+ [:has_key, ::Arel.sql("'a'"), "'a'"],
16
+ [:has_all_keys, ['b', 'c'], "ARRAY['b', 'c']"],
17
+ [:has_any_keys, ['d', 'e'], "ARRAY['d', 'e']"],
18
+
19
+ [:strictly_left, ::Arel.sql('numrange(1, 2)'), 'numrange(1, 2)'],
20
+ [:strictly_right, ::Arel.sql('numrange(3, 4)'), 'numrange(3, 4)'],
21
+ [:doesnt_right_extend, ::Arel.sql('numrange(5, 6)'), 'numrange(5, 6)'],
22
+ [:doesnt_left_extend, ::Arel.sql('numrange(7, 8)'), 'numrange(7, 8)'],
23
+ [:adjacent_to, ::Arel.sql('numrange(9, 0)'), 'numrange(9, 0)'],
24
+ ].each do |(operator, value, quoted_value)|
25
+ klass_name = operator.to_s.camelize
26
+
27
+ context "##{operator}" do
28
+ let(:instance) do
29
+ attribute.public_send(operator, value.is_a?(Array) ? ::Arel.array(value) : value)
30
+ end
31
+
32
+ context 'for attribute' do
33
+ let(:klass) { ::Arel::Nodes.const_get(klass_name) }
34
+
35
+ it "returns a new #{klass_name}" do
36
+ expect(instance).to be_a(klass)
37
+ end
38
+ end
39
+
40
+ context 'for visitor' do
41
+ let(:result) { visitor.accept(instance, collector.new).value }
42
+
43
+ it 'returns a formatted operation' do
44
+ expect(result).to be_eql("\"a\".\"sample\" #{list[klass_name]} #{quoted_value}")
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'on cast' do
52
+ it 'provides an array method' do
53
+ sample1 = ::Arel.array(1, 2, 3, 4)
54
+ sample2 = ::Arel.array([1, 2, 3, 4])
55
+ sample3 = ::Arel.array(1, 2, 3, 4, cast: 'bigint')
56
+ sample4 = ::Arel.array([1, 2, 3, 4], [5, 6, 7, 8], cast: 'integer')
57
+
58
+ expect(sample1.to_sql).to be_eql('ARRAY[1, 2, 3, 4]')
59
+ expect(sample2.to_sql).to be_eql('ARRAY[1, 2, 3, 4]')
60
+ expect(sample3.to_sql).to be_eql('ARRAY[1, 2, 3, 4]::bigint[]')
61
+ expect(sample4.to_sql).to be_eql('ARRAY[ARRAY[1, 2, 3, 4], ARRAY[5, 6, 7, 8]]::integer[]')
62
+ end
63
+
64
+ it 'provides a cast method' do
65
+ attribute = ::Arel::Table.new('a')['sample']
66
+ quoted = ::Arel::Nodes::build_quoted([1])
67
+ casted = ::Arel::Nodes::build_quoted(1, attribute)
68
+
69
+ expect(attribute.cast('text').to_sql).to be_eql('"a"."sample"::text')
70
+ expect(quoted.cast('bigint', true).to_sql).to be_eql('ARRAY[1]::bigint[]')
71
+ expect(casted.cast('string').to_sql).to be_eql("1::string")
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,593 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'AuxiliaryStatement' do
4
+ before :each do
5
+ User.auxiliary_statements_list = {}
6
+ end
7
+
8
+ context 'on relation' do
9
+ let(:klass) { User }
10
+ let(:true_value) { 'TRUE' }
11
+ subject { klass.unscoped }
12
+
13
+ it 'has its method' do
14
+ expect(subject).to respond_to(:with)
15
+ end
16
+
17
+ it 'can perform simple queries' do
18
+ klass.send(:auxiliary_statement, :comments) do |cte|
19
+ cte.query Comment.all
20
+ cte.attributes content: :comment_content
21
+ end
22
+
23
+ result = 'WITH "comments" AS'
24
+ result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content FROM "comments")'
25
+ result << ' SELECT "users".*, "comments"."comment_content" FROM "users"'
26
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
27
+ expect(subject.with(:comments).arel.to_sql).to eql(result)
28
+ end
29
+
30
+ it 'can perform more complex queries' do
31
+ klass.send(:auxiliary_statement, :comments) do |cte|
32
+ cte.query Comment.distinct_on(:user_id).order(:user_id, id: :desc)
33
+ cte.attributes content: :last_comment
34
+ end
35
+
36
+ result = 'WITH "comments" AS (SELECT DISTINCT ON ( "comments"."user_id" )'
37
+ result << ' "comments"."user_id", "comments"."content" AS last_comment'
38
+ result << ' FROM "comments" ORDER BY "comments"."user_id" ASC,'
39
+ result << ' "comments"."id" DESC) SELECT "users".*,'
40
+ result << ' "comments"."last_comment" FROM "users" INNER JOIN "comments"'
41
+ result << ' ON "comments"."user_id" = "users"."id"'
42
+ expect(subject.with(:comments).arel.to_sql).to eql(result)
43
+ end
44
+
45
+ it 'accepts extra select columns' do
46
+ klass.send(:auxiliary_statement, :comments) do |cte|
47
+ cte.query Comment.all
48
+ cte.attributes content: :comment_content
49
+ end
50
+
51
+ result = 'WITH "comments" AS'
52
+ result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content, "comments"."slug" AS comment_slug FROM "comments")'
53
+ result << ' SELECT "users".*, "comments"."comment_content", "comments"."comment_slug" FROM "users"'
54
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
55
+ expect(subject.with(:comments, select: {slug: :comment_slug}).arel.to_sql).to eql(result)
56
+ end
57
+
58
+ it 'accepts extra join columns' do
59
+ klass.send(:auxiliary_statement, :comments) do |cte|
60
+ cte.query Comment.all
61
+ cte.attributes content: :comment_content
62
+ end
63
+
64
+ result = 'WITH "comments" AS'
65
+ result << ' (SELECT "comments"."user_id", "comments"."active", "comments"."content" AS comment_content FROM "comments")'
66
+ result << ' SELECT "users".*, "comments"."comment_content" FROM "users"'
67
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id" AND "comments"."active" = "users"."active"'
68
+ expect(subject.with(:comments, join: {active: :active}).arel.to_sql).to eql(result)
69
+ end
70
+
71
+ it 'accepts extra conditions' do
72
+ klass.send(:auxiliary_statement, :comments) do |cte|
73
+ cte.query Comment.all
74
+ cte.attributes content: :comment_content
75
+ end
76
+
77
+ result = 'WITH "comments" AS'
78
+ result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content'
79
+ result << ' FROM "comments" WHERE "comments"."active" = $1)'
80
+ result << ' SELECT "users".*, "comments"."comment_content" FROM "users"'
81
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
82
+ expect(subject.with(:comments, where: {active: true}).arel.to_sql).to eql(result)
83
+ end
84
+
85
+ it 'accepts scopes from both sides' do
86
+ klass.send(:auxiliary_statement, :comments) do |cte|
87
+ cte.query Comment.where(id: 1).all
88
+ cte.attributes content: :comment_content
89
+ end
90
+
91
+ query = subject.where(id: 2).with(:comments)
92
+
93
+ result = 'WITH "comments" AS'
94
+ result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content FROM "comments"'
95
+ result << ' WHERE "comments"."id" = $1)'
96
+ result << ' SELECT "users".*, "comments"."comment_content" FROM "users"'
97
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
98
+ result << ' WHERE "users"."id" = $2'
99
+
100
+ expect(query.arel.to_sql).to eql(result)
101
+ expect(query.send(:bound_attributes).map(&:value_before_type_cast)).to eql([1, 2])
102
+ end
103
+
104
+ it 'accepts string as attributes' do
105
+ klass.send(:auxiliary_statement, :comments) do |cte|
106
+ cte.query Comment.all
107
+ cte.attributes sql('MAX(id)') => :comment_id
108
+ end
109
+
110
+ result = 'WITH "comments" AS'
111
+ result << ' (SELECT "comments"."user_id", MAX(id) AS comment_id FROM "comments")'
112
+ result << ' SELECT "users".*, "comments"."comment_id" FROM "users"'
113
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
114
+ expect(subject.with(:comments).arel.to_sql).to eql(result)
115
+ end
116
+
117
+ it 'accepts complex string as attributes' do
118
+ klass.send(:auxiliary_statement, :comments) do |cte|
119
+ cte.query Comment.all
120
+ cte.attributes sql('ROW_NUMBER() OVER (PARTITION BY ORDER BY "comments"."id")') => :comment_id
121
+ end
122
+
123
+ result = 'WITH "comments" AS'
124
+ result << ' (SELECT "comments"."user_id", ROW_NUMBER() OVER (PARTITION BY ORDER BY "comments"."id") AS comment_id FROM "comments")'
125
+ result << ' SELECT "users".*, "comments"."comment_id" FROM "users"'
126
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
127
+ expect(subject.with(:comments).arel.to_sql).to eql(result)
128
+ end
129
+
130
+ it 'accepts arel attribute as attributes' do
131
+ klass.send(:auxiliary_statement, :comments) do |cte|
132
+ cte.query Comment.all
133
+ cte.attributes col(:id).minimum => :comment_id
134
+ end
135
+
136
+ result = 'WITH "comments" AS'
137
+ result << ' (SELECT "comments"."user_id", MIN("comments"."id") AS comment_id FROM "comments")'
138
+ result << ' SELECT "users".*, "comments"."comment_id" FROM "users"'
139
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
140
+ expect(subject.with(:comments).arel.to_sql).to eql(result)
141
+ end
142
+
143
+ it 'accepts custom join properties' do
144
+ klass.send(:auxiliary_statement, :comments) do |cte|
145
+ cte.query Comment.all
146
+ cte.attributes content: :comment_content
147
+ cte.join name: :id, 'a.col' => :col
148
+ end
149
+
150
+ result = 'WITH "comments" AS (SELECT "comments"."id", "comments"."col",'
151
+ result << ' "comments"."content" AS comment_content FROM "comments") SELECT "users".*,'
152
+ result << ' "comments"."comment_content" FROM "users" INNER JOIN "comments"'
153
+ result << ' ON "comments"."id" = "users"."name" AND "comments"."col" = "a"."col"'
154
+ expect(subject.with(:comments).arel.to_sql).to eql(result)
155
+ end
156
+
157
+ it 'can perform other types of joins' do
158
+ klass.send(:auxiliary_statement, :comments) do |cte|
159
+ cte.query Comment.all
160
+ cte.attributes content: :comment_content
161
+ cte.join_type :left
162
+ end
163
+
164
+ result = 'WITH "comments" AS (SELECT "comments"."user_id",'
165
+ result << ' "comments"."content" AS comment_content FROM "comments") SELECT "users".*,'
166
+ result << ' "comments"."comment_content" FROM "users" LEFT OUTER JOIN "comments"'
167
+ result << ' ON "comments"."user_id" = "users"."id"'
168
+ expect(subject.with(:comments).arel.to_sql).to eql(result)
169
+ end
170
+
171
+ it 'can manually define the association' do
172
+ klass.has_many :sample_comment, class_name: 'Comment', foreign_key: :a_user_id
173
+ klass.send(:auxiliary_statement, :comments) do |cte|
174
+ cte.query Comment.all
175
+ cte.through :sample_comment
176
+ cte.attributes content: :sample_content
177
+ end
178
+
179
+ result = 'WITH "comments" AS'
180
+ result << ' (SELECT "comments"."a_user_id", "comments"."content" AS sample_content FROM "comments")'
181
+ result << ' SELECT "users".*, "comments"."sample_content" FROM "users"'
182
+ result << ' INNER JOIN "comments" ON "comments"."a_user_id" = "users"."id"'
183
+ expect(subject.with(:comments).arel.to_sql).to eql(result)
184
+ end
185
+
186
+ it 'accepts complex scopes from dependencies' do
187
+ klass.send(:auxiliary_statement, :comments1) do |cte|
188
+ cte.query Comment.where(id: 1).all
189
+ cte.attributes content: :comment_content1
190
+ end
191
+
192
+ klass.send(:auxiliary_statement, :comments2) do |cte|
193
+ cte.requires :comments1
194
+ cte.query Comment.where(id: 2).all
195
+ cte.attributes content: :comment_content2
196
+ end
197
+
198
+ query = subject.where(id: 3).with(:comments2)
199
+
200
+ result = 'WITH '
201
+ result << '"comments1" AS (SELECT "comments"."user_id", "comments"."content" AS comment_content1 FROM "comments" WHERE "comments"."id" = $1), '
202
+ result << '"comments2" AS (SELECT "comments"."user_id", "comments"."content" AS comment_content2 FROM "comments" WHERE "comments"."id" = $2)'
203
+ result << ' SELECT "users".*, "comments1"."comment_content1", "comments2"."comment_content2" FROM "users"'
204
+ result << ' INNER JOIN "comments1" ON "comments1"."user_id" = "users"."id"'
205
+ result << ' INNER JOIN "comments2" ON "comments2"."user_id" = "users"."id"'
206
+ result << ' WHERE "users"."id" = $3'
207
+
208
+ expect(query.arel.to_sql).to eql(result)
209
+ expect(query.send(:bound_attributes).map(&:value_before_type_cast)).to eql([1, 2, 3])
210
+ end
211
+
212
+ context 'with dependency' do
213
+ before :each do
214
+ klass.send(:auxiliary_statement, :comments1) do |cte|
215
+ cte.query Comment.all
216
+ cte.attributes content: :comment_content1
217
+ end
218
+
219
+ klass.send(:auxiliary_statement, :comments2) do |cte|
220
+ cte.requires :comments1
221
+ cte.query Comment.all
222
+ cte.attributes content: :comment_content2
223
+ end
224
+ end
225
+
226
+ it 'can requires another statement as dependency' do
227
+ result = 'WITH '
228
+ result << '"comments1" AS (SELECT "comments"."user_id", "comments"."content" AS comment_content1 FROM "comments"), '
229
+ result << '"comments2" AS (SELECT "comments"."user_id", "comments"."content" AS comment_content2 FROM "comments")'
230
+ result << ' SELECT "users".*, "comments1"."comment_content1", "comments2"."comment_content2" FROM "users"'
231
+ result << ' INNER JOIN "comments1" ON "comments1"."user_id" = "users"."id"'
232
+ result << ' INNER JOIN "comments2" ON "comments2"."user_id" = "users"."id"'
233
+ expect(subject.with(:comments2).arel.to_sql).to eql(result)
234
+ end
235
+
236
+ it 'can uses already already set dependent' do
237
+ result = 'WITH '
238
+ result << '"comments1" AS (SELECT "comments"."user_id", "comments"."content" AS comment_content1 FROM "comments"), '
239
+ result << '"comments2" AS (SELECT "comments"."user_id", "comments"."content" AS comment_content2 FROM "comments")'
240
+ result << ' SELECT "users".*, "comments1"."comment_content1", "comments2"."comment_content2" FROM "users"'
241
+ result << ' INNER JOIN "comments1" ON "comments1"."user_id" = "users"."id"'
242
+ result << ' INNER JOIN "comments2" ON "comments2"."user_id" = "users"."id"'
243
+ expect(subject.with(:comments1, :comments2).arel.to_sql).to eql(result)
244
+ end
245
+
246
+ it 'raises an error if the dependent does not exist' do
247
+ klass.send(:auxiliary_statement, :comments2) do |cte|
248
+ cte.requires :comments3
249
+ cte.query Comment.all
250
+ cte.attributes content: :comment_content2
251
+ end
252
+ expect{ subject.with(:comments2).arel.to_sql }.to raise_error(ArgumentError)
253
+ end
254
+ end
255
+
256
+ context 'query as string' do
257
+ it 'performs correctly' do
258
+ klass.send(:auxiliary_statement, :comments) do |cte|
259
+ cte.query :comments, 'SELECT * FROM comments'
260
+ cte.attributes content: :comment
261
+ cte.join id: :user_id
262
+ end
263
+
264
+ result = 'WITH "comments" AS (SELECT * FROM comments)'
265
+ result << ' SELECT "users".*, "comments"."comment" FROM "users"'
266
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
267
+ expect(subject.with(:comments).arel.to_sql).to eql(result)
268
+ end
269
+
270
+ it 'accepts arguments to format the query' do
271
+ klass.send(:auxiliary_statement, :comments) do |cte|
272
+ cte.query :comments, 'SELECT * FROM comments WHERE active = %{active}'
273
+ cte.attributes content: :comment
274
+ cte.join id: :user_id
275
+ end
276
+
277
+ result = "WITH \"comments\" AS (SELECT * FROM comments WHERE active = #{true_value})"
278
+ result << ' SELECT "users".*, "comments"."comment" FROM "users"'
279
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
280
+ expect(subject.with(:comments, args: {active: true}).arel.to_sql).to eql(result)
281
+ end
282
+
283
+ it 'raises an error when join columns are not given' do
284
+ klass.send(:auxiliary_statement, :comments) do |cte|
285
+ cte.query :comments, 'SELECT * FROM comments'
286
+ cte.attributes content: :comment
287
+ end
288
+
289
+ expect{ subject.with(:comments).arel.to_sql }.to raise_error(ArgumentError, /join columns/)
290
+ end
291
+
292
+ it 'raises an error when not given the table name as first argument' do
293
+ klass.send(:auxiliary_statement, :comments) do |cte|
294
+ cte.query 'SELECT * FROM comments'
295
+ cte.attributes content: :comment
296
+ cte.join id: :user_id
297
+ end
298
+
299
+ expect{ subject.with(:comments).arel.to_sql }.to raise_error(ArgumentError, /table name/)
300
+ end
301
+ end
302
+
303
+ context 'query as proc' do
304
+ it 'performs correctly for result as relation' do
305
+ klass.send(:auxiliary_statement, :comments) do |cte|
306
+ cte.query :comments, -> { Comment.all }
307
+ cte.attributes content: :comment
308
+ cte.join id: :user_id
309
+ end
310
+
311
+ result = 'WITH "comments" AS'
312
+ result << ' (SELECT "comments"."user_id", "comments"."content" AS comment FROM "comments")'
313
+ result << ' SELECT "users".*, "comments"."comment" FROM "users"'
314
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
315
+ expect(subject.with(:comments).arel.to_sql).to eql(result)
316
+ end
317
+
318
+ it 'performs correctly for anything that has a call method' do
319
+ obj = Struct.new(:call, :arity).new('SELECT * FROM comments', 0)
320
+ klass.send(:auxiliary_statement, :comments) do |cte|
321
+ cte.query :comments, obj
322
+ cte.attributes content: :comment
323
+ cte.join id: :user_id
324
+ end
325
+
326
+ result = 'WITH "comments" AS (SELECT * FROM comments)'
327
+ result << ' SELECT "users".*, "comments"."comment" FROM "users"'
328
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
329
+ expect(subject.with(:comments).arel.to_sql).to eql(result)
330
+ end
331
+
332
+ it 'performs correctly for result as string' do
333
+ klass.send(:auxiliary_statement, :comments) do |cte|
334
+ cte.query :comments, -> { 'SELECT * FROM comments' }
335
+ cte.attributes content: :comment
336
+ cte.join id: :user_id
337
+ end
338
+
339
+ result = 'WITH "comments" AS (SELECT * FROM comments)'
340
+ result << ' SELECT "users".*, "comments"."comment" FROM "users"'
341
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
342
+ expect(subject.with(:comments).arel.to_sql).to eql(result)
343
+ end
344
+
345
+ it 'performs correctly when the proc requires arguments' do
346
+ klass.send(:auxiliary_statement, :comments) do |cte|
347
+ cte.query :comments, -> (args) { Comment.where(id: args.id) }
348
+ cte.attributes content: :comment
349
+ cte.join id: :user_id
350
+ end
351
+
352
+ query = subject.with(:comments, args: {id: 1})
353
+
354
+ result = 'WITH "comments" AS'
355
+ result << ' (SELECT "comments"."user_id", "comments"."content" AS comment'
356
+ result << ' FROM "comments" WHERE "comments"."id" = $1)'
357
+ result << ' SELECT "users".*, "comments"."comment" FROM "users"'
358
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
359
+
360
+ expect(query.arel.to_sql).to eql(result)
361
+ expect(query.send(:bound_attributes).map(&:value_before_type_cast)).to eql([1])
362
+ end
363
+
364
+ it 'raises an error when join columns are not given' do
365
+ klass.send(:auxiliary_statement, :comments) do |cte|
366
+ cte.query :comments, -> { Author.all }
367
+ cte.attributes content: :comment
368
+ end
369
+
370
+ expect{ subject.with(:comments).arel.to_sql }.to raise_error(ArgumentError, /join columns/)
371
+ end
372
+
373
+ it 'raises an error when not given the table name as first argument' do
374
+ klass.send(:auxiliary_statement, :comments) do |cte|
375
+ cte.query -> { Comment.all }
376
+ cte.attributes content: :comment
377
+ cte.join id: :user_id
378
+ end
379
+
380
+ expect{ subject.with(:comments).arel.to_sql }.to raise_error(ArgumentError, /table name/)
381
+ end
382
+
383
+ it 'raises an error when the result of the proc is an invalid type' do
384
+ klass.send(:auxiliary_statement, :comments) do |cte|
385
+ cte.query :comments, -> { false }
386
+ cte.attributes content: :comment
387
+ cte.join id: :user_id
388
+ end
389
+
390
+ expect{ subject.with(:comments).arel.to_sql }.to raise_error(ArgumentError, /query objects/)
391
+ end
392
+ end
393
+
394
+ context 'with inheritance' do
395
+ let(:base) { Activity }
396
+ let(:klass) { ActivityBook }
397
+
398
+ it 'accepts ancestors auxiliary statements' do
399
+ base.send(:auxiliary_statement, :authors) do |cte|
400
+ cte.query Author.all
401
+ cte.attributes name: :author_name
402
+ cte.join author_id: :id
403
+ end
404
+
405
+ result = 'WITH "authors" AS'
406
+ result << ' (SELECT "authors"."id", "authors"."name" AS author_name FROM "authors")'
407
+ result << ' SELECT "activity_books".*, "authors"."author_name" FROM "activity_books"'
408
+ result << ' INNER JOIN "authors" ON "authors"."id" = "activity_books"."author_id"'
409
+ expect(subject.with(:authors).arel.to_sql).to eql(result)
410
+ end
411
+
412
+ it 'can replace ancestors auxiliary statements' do
413
+ base.send(:auxiliary_statement, :authors) do |cte|
414
+ cte.query Author.all
415
+ cte.attributes name: :author_name
416
+ cte.join author_id: :id
417
+ end
418
+
419
+ klass.send(:auxiliary_statement, :authors) do |cte|
420
+ cte.query Author.all
421
+ cte.attributes type: :author_type
422
+ cte.join author_id: :id
423
+ end
424
+
425
+ result = 'WITH "authors" AS'
426
+ result << ' (SELECT "authors"."id", "authors"."type" AS author_type FROM "authors")'
427
+ result << ' SELECT "activity_books".*, "authors"."author_type" FROM "activity_books"'
428
+ result << ' INNER JOIN "authors" ON "authors"."id" = "activity_books"."author_id"'
429
+ expect(subject.with(:authors).arel.to_sql).to eql(result)
430
+ end
431
+
432
+ it 'raises an error when no class has the auxiliary statement' do
433
+ expect{ subject.with(:comments).arel.to_sql }.to raise_error(ArgumentError)
434
+ end
435
+ end
436
+
437
+ it 'works with count and does not add extra columns' do
438
+ klass.send(:auxiliary_statement, :comments) do |cte|
439
+ cte.query Comment.all
440
+ cte.attributes content: :comment_content
441
+ end
442
+
443
+ result = 'WITH "comments" AS'
444
+ result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content FROM "comments")'
445
+ result << ' SELECT COUNT(*) FROM "users"'
446
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
447
+
448
+ query = get_last_executed_query{ subject.with(:comments).count }
449
+ expect(query).to eql(result)
450
+ end
451
+
452
+ it 'works with sum and does not add extra columns' do
453
+ klass.send(:auxiliary_statement, :comments) do |cte|
454
+ cte.query Comment.all
455
+ cte.attributes id: :value
456
+ end
457
+
458
+ result = 'WITH "comments" AS'
459
+ result << ' (SELECT "comments"."user_id", "comments"."id" AS value FROM "comments")'
460
+ result << ' SELECT SUM("comments"."value") FROM "users"'
461
+ result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
462
+
463
+ query = get_last_executed_query{ subject.with(:comments).sum(comments: :value) }
464
+ expect(query).to eql(result)
465
+ end
466
+
467
+ it 'raises an error when using an invalid type of object as query' do
468
+ klass.send(:auxiliary_statement, :comments) do |cte|
469
+ cte.query :string, String
470
+ end
471
+
472
+ expect{ subject.with(:comments).arel.to_sql }.to raise_error(ArgumentError, /object types/)
473
+ end
474
+
475
+ it 'raises an error when traying to use a statement that is not defined' do
476
+ expect{ subject.with(:does_not_exist).arel.to_sql }.to raise_error(ArgumentError)
477
+ end
478
+
479
+ it 'raises an error when using an invalid type of join' do
480
+ klass.send(:auxiliary_statement, :comments) do |cte|
481
+ cte.query Comment.all
482
+ cte.attributes content: :comment_content
483
+ cte.join_type :invalid
484
+ end
485
+
486
+ expect{ subject.with(:comments).arel.to_sql }.to raise_error(ArgumentError)
487
+ end
488
+ end
489
+
490
+ context 'on model' do
491
+ subject { User }
492
+
493
+ it 'has its configurator' do
494
+ expect(subject.protected_methods).to include(:cte)
495
+ expect(subject.protected_methods).to include(:auxiliary_statement)
496
+ end
497
+
498
+ it 'allows configurate new auxiliary statements' do
499
+ subject.send(:auxiliary_statement, :cte1)
500
+ expect(subject.auxiliary_statements_list).to include(:cte1)
501
+ expect(subject.const_defined?('Cte1_AuxiliaryStatement')).to be_truthy
502
+ end
503
+
504
+ it 'has its query method' do
505
+ expect(subject).to respond_to(:with)
506
+ end
507
+
508
+ it 'returns a relation when using the method' do
509
+ subject.send(:auxiliary_statement, :comments) do |cte|
510
+ cte.query Comment.all
511
+ cte.attributes content: :comment_content
512
+ end
513
+ expect(subject.with(:comments)).to be_a(ActiveRecord::Relation)
514
+ end
515
+ end
516
+
517
+ context 'on external' do
518
+ let(:klass) { Torque::PostgreSQL::AuxiliaryStatement }
519
+ subject { User }
520
+
521
+ it 'has the external method available' do
522
+ expect(klass).to respond_to(:create)
523
+ end
524
+
525
+ it 'accepts simple auxiliary statement definition' do
526
+ sample = klass.create(Comment.all)
527
+ query = subject.with(sample, select: {content: :comment_content}).arel.to_sql
528
+
529
+ result = 'WITH "comment" AS'
530
+ result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content FROM "comments")'
531
+ result << ' SELECT "users".*, "comment"."comment_content" FROM "users"'
532
+ result << ' INNER JOIN "comment" ON "comment"."user_id" = "users"."id"'
533
+ expect(query).to eql(result)
534
+ end
535
+
536
+ it 'accepts a hash auxiliary statement definition' do
537
+ sample = klass.create(query: Comment.all, select: {content: :comment_content})
538
+ query = subject.with(sample).arel.to_sql
539
+
540
+ result = 'WITH "comment" AS'
541
+ result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content FROM "comments")'
542
+ result << ' SELECT "users".*, "comment"."comment_content" FROM "users"'
543
+ result << ' INNER JOIN "comment" ON "comment"."user_id" = "users"."id"'
544
+ expect(query).to eql(result)
545
+ end
546
+
547
+ it 'accepts a block when creating the auxiliary statement' do
548
+ sample = klass.create(:all_comments) do |cte|
549
+ cte.query Comment.all
550
+ cte.select content: :comment_content
551
+ end
552
+
553
+ result = 'WITH "all_comments" AS'
554
+ result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content FROM "comments")'
555
+ result << ' SELECT "users".*, "all_comments"."comment_content" FROM "users"'
556
+ result << ' INNER JOIN "all_comments" ON "all_comments"."user_id" = "users"."id"'
557
+
558
+ query = subject.with(sample).arel.to_sql
559
+ expect(query).to eql(result)
560
+ end
561
+ end
562
+
563
+ context 'on settings' do
564
+ let(:base) { User }
565
+ let(:statement_klass) do
566
+ base.send(:auxiliary_statement, :statement)
567
+ base::Statement_AuxiliaryStatement
568
+ end
569
+
570
+ subject do
571
+ Torque::PostgreSQL::AuxiliaryStatement::Settings.new(base, statement_klass)
572
+ end
573
+
574
+ it 'has access to base' do
575
+ expect(subject.base).to eql(User)
576
+ expect(subject.base_table).to be_a(Arel::Table)
577
+ end
578
+
579
+ it 'has access to statement table' do
580
+ expect(subject.table_name).to eql('statement')
581
+ expect(subject.table).to be_a(Arel::Table)
582
+ end
583
+
584
+ it 'has access to the query arel table' do
585
+ subject.query Comment.all
586
+ expect(subject.query_table).to be_a(Arel::Table)
587
+ end
588
+
589
+ it 'raises an error when trying to access query table before defining the query' do
590
+ expect{ subject.with(:comments).arel.to_sql }.to raise_error(StandardError)
591
+ end
592
+ end
593
+ end