torque-postgresql 1.1.8 → 2.0.0

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql.rb +0 -2
  3. data/lib/torque/postgresql/adapter.rb +0 -1
  4. data/lib/torque/postgresql/adapter/database_statements.rb +4 -15
  5. data/lib/torque/postgresql/adapter/schema_creation.rb +13 -23
  6. data/lib/torque/postgresql/adapter/schema_definitions.rb +7 -21
  7. data/lib/torque/postgresql/adapter/schema_dumper.rb +71 -11
  8. data/lib/torque/postgresql/adapter/schema_statements.rb +2 -12
  9. data/lib/torque/postgresql/associations.rb +0 -3
  10. data/lib/torque/postgresql/associations/association.rb +0 -4
  11. data/lib/torque/postgresql/associations/association_scope.rb +18 -60
  12. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +12 -15
  13. data/lib/torque/postgresql/associations/preloader.rb +0 -32
  14. data/lib/torque/postgresql/associations/preloader/association.rb +13 -10
  15. data/lib/torque/postgresql/autosave_association.rb +4 -4
  16. data/lib/torque/postgresql/auxiliary_statement.rb +1 -13
  17. data/lib/torque/postgresql/coder.rb +1 -2
  18. data/lib/torque/postgresql/config.rb +0 -6
  19. data/lib/torque/postgresql/inheritance.rb +13 -17
  20. data/lib/torque/postgresql/reflection/abstract_reflection.rb +19 -25
  21. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +4 -38
  22. data/lib/torque/postgresql/relation.rb +11 -16
  23. data/lib/torque/postgresql/relation/auxiliary_statement.rb +2 -8
  24. data/lib/torque/postgresql/relation/distinct_on.rb +1 -1
  25. data/lib/torque/postgresql/version.rb +1 -1
  26. data/spec/en.yml +19 -0
  27. data/spec/factories/authors.rb +6 -0
  28. data/spec/factories/comments.rb +13 -0
  29. data/spec/factories/posts.rb +6 -0
  30. data/spec/factories/tags.rb +5 -0
  31. data/spec/factories/texts.rb +5 -0
  32. data/spec/factories/users.rb +6 -0
  33. data/spec/factories/videos.rb +5 -0
  34. data/spec/mocks/cache_query.rb +16 -0
  35. data/spec/mocks/create_table.rb +35 -0
  36. data/spec/models/activity.rb +3 -0
  37. data/spec/models/activity_book.rb +4 -0
  38. data/spec/models/activity_post.rb +7 -0
  39. data/spec/models/activity_post/sample.rb +4 -0
  40. data/spec/models/author.rb +4 -0
  41. data/spec/models/author_journalist.rb +4 -0
  42. data/spec/models/comment.rb +3 -0
  43. data/spec/models/course.rb +2 -0
  44. data/spec/models/geometry.rb +2 -0
  45. data/spec/models/guest_comment.rb +4 -0
  46. data/spec/models/post.rb +6 -0
  47. data/spec/models/tag.rb +2 -0
  48. data/spec/models/text.rb +2 -0
  49. data/spec/models/time_keeper.rb +2 -0
  50. data/spec/models/user.rb +8 -0
  51. data/spec/models/video.rb +2 -0
  52. data/spec/schema.rb +141 -0
  53. data/spec/spec_helper.rb +59 -0
  54. data/spec/tests/arel_spec.rb +72 -0
  55. data/spec/tests/auxiliary_statement_spec.rb +593 -0
  56. data/spec/tests/belongs_to_many_spec.rb +240 -0
  57. data/spec/tests/coder_spec.rb +367 -0
  58. data/spec/tests/collector_spec.rb +59 -0
  59. data/spec/tests/distinct_on_spec.rb +65 -0
  60. data/spec/tests/enum_set_spec.rb +306 -0
  61. data/spec/tests/enum_spec.rb +621 -0
  62. data/spec/tests/geometric_builder_spec.rb +221 -0
  63. data/spec/tests/has_many_spec.rb +390 -0
  64. data/spec/tests/interval_spec.rb +167 -0
  65. data/spec/tests/lazy_spec.rb +24 -0
  66. data/spec/tests/period_spec.rb +954 -0
  67. data/spec/tests/quoting_spec.rb +24 -0
  68. data/spec/tests/range_spec.rb +36 -0
  69. data/spec/tests/relation_spec.rb +57 -0
  70. data/spec/tests/table_inheritance_spec.rb +403 -0
  71. metadata +103 -15
  72. data/lib/torque/postgresql/associations/join_dependency/join_association.rb +0 -15
  73. data/lib/torque/postgresql/schema_dumper.rb +0 -101
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'Quoting', type: :helper do
4
+ let(:connection) { ActiveRecord::Base.connection }
5
+
6
+ context 'on type names' do
7
+ it 'accepts type name only' do
8
+ expect(connection.quote_type_name('sample')).to eql('"public"."sample"')
9
+ end
10
+
11
+ it 'accepts schema and type name' do
12
+ expect(connection.quote_type_name('other.sample')).to eql('"other"."sample"')
13
+ end
14
+
15
+ it 'accepts schema as a parameter' do
16
+ expect(connection.quote_type_name('sample', 'test')).to eql('"test"."sample"')
17
+ end
18
+
19
+ it 'always prefer the schema from parameter' do
20
+ expect(connection.quote_type_name('nothis.sample', 'this')).to eql('"this"."sample"')
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'Range' do
4
+ let(:sample) { (5..15) }
5
+
6
+ it 'has intersection' do
7
+ expect { sample.intersection(10) }.to raise_error(ArgumentError, /Range/)
8
+
9
+ result = sample & (10..15)
10
+ expect(result).to be_a(Range)
11
+ expect(result.min).to be_eql(10)
12
+ expect(result.max).to be_eql(15)
13
+
14
+ result = sample & (15..20)
15
+ expect(result).to be_a(Range)
16
+ expect(result.min).to be_eql(15)
17
+ expect(result.max).to be_eql(15)
18
+
19
+ result = sample & (0..10)
20
+ expect(result).to be_a(Range)
21
+ expect(result.min).to be_eql(5)
22
+ expect(result.max).to be_eql(10)
23
+
24
+ result = sample & (-10..0)
25
+ expect(result).to be_nil
26
+ end
27
+
28
+ it 'has union' do
29
+ expect { sample.union(10) }.to raise_error(ArgumentError, /Range/)
30
+
31
+ result = sample | (0..10)
32
+ expect(result).to be_a(Range)
33
+ expect(result.min).to be_eql(0)
34
+ expect(result.max).to be_eql(15)
35
+ end
36
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec::Matchers.define :be_attributes_as do |list|
4
+ match do |other|
5
+ other.each_with_index.map do |item, idx|
6
+ item.relation.name == list[idx][0] && item.name.to_s == list[idx][1]
7
+ end.all?
8
+ end
9
+ end
10
+
11
+ RSpec.describe 'Relation', type: :helper do
12
+
13
+ context 'on resolving columns' do
14
+ subject { Post.unscoped.method(:resolve_column) }
15
+
16
+ def attribute(relation, name)
17
+ result = Arel::Attributes::Attribute.new
18
+ result.relation = relation
19
+ result.name = name
20
+ result
21
+ end
22
+
23
+ it 'asserts sql literals' do
24
+ check = ['name', 'other.title']
25
+ expect(subject.call(check)).to eql(check)
26
+ end
27
+
28
+ it 'asserts attribute symbols' do
29
+ check = [:title, :content]
30
+ result = [['posts', 'title'], ['posts', 'content']]
31
+ expect(subject.call(check)).to be_attributes_as(result)
32
+ end
33
+
34
+ it 'asserts direct hash relations' do
35
+ check = [:title, author: :name]
36
+ result = [['posts', 'title'], ['authors', 'name']]
37
+ expect(subject.call(check)).to be_attributes_as(result)
38
+ end
39
+
40
+ it 'asserts multiple values on hash definition' do
41
+ check = [author: [:name, :age]]
42
+ result = [['authors', 'name'], ['authors', 'age']]
43
+ expect(subject.call(check)).to be_attributes_as(result)
44
+ end
45
+
46
+ it 'raises on relation not present' do
47
+ check = [tags: :name]
48
+ expect{ subject.call(check) }.to raise_error(ArgumentError, /Relation for/)
49
+ end
50
+
51
+ it 'raises on third level access' do
52
+ check = [author: [comments: :body]]
53
+ expect{ subject.call(check) }.to raise_error(ArgumentError, /on third level/)
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,403 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'TableInheritance' do
4
+ let(:connection) { ActiveRecord::Base.connection }
5
+
6
+ context 'on migration' do
7
+ mock_create_table
8
+
9
+ it 'does not affect some basic forms of table creation' do
10
+ sql = connection.create_table('schema_migrations', id: false) do |t|
11
+ t.string :version, **connection.internal_string_options_for_primary_key
12
+ end
13
+
14
+ result = 'CREATE TABLE "schema_migrations"'
15
+ result << ' \("version" character varying( NOT NULL)? PRIMARY KEY\)'
16
+ expect(sql).to match(/#{result}/)
17
+ end
18
+
19
+ it 'does not affect simple table creation' do
20
+ sql = connection.create_table(:activities) do |t|
21
+ t.string :title
22
+ t.boolean :active
23
+ t.timestamps
24
+ end
25
+
26
+ result = 'CREATE TABLE "activities" ('
27
+ result << '"id" bigserial primary key'
28
+ result << ', "title" character varying'
29
+ result << ', "active" boolean'
30
+ result << ', "created_at" timestamp(6) NOT NULL'
31
+ result << ', "updated_at" timestamp(6) NOT NULL'
32
+ result << ')'
33
+ expect(sql).to eql(result)
34
+ end
35
+
36
+ it 'does not affect temporary table creation based on a query' do
37
+ query = 'SELECT * FROM "authors"'
38
+ sql = connection.create_table(:test, temporary: true, as: query)
39
+
40
+ result = 'CREATE TEMPORARY TABLE "test"'
41
+ result << " AS #{query}"
42
+ expect(sql).to eql(result)
43
+ end
44
+
45
+ it 'adds the inherits statement for a single inheritance' do
46
+ sql = connection.create_table(:activity_videos, inherits: :activities) do |t|
47
+ t.string :url
48
+ end
49
+
50
+ result = 'CREATE TABLE "activity_videos" ('
51
+ result << '"url" character varying'
52
+ result << ') INHERITS ( "activities" )'
53
+ expect(sql).to eql(result)
54
+ end
55
+
56
+ it 'adds the inherits statement for a multiple inheritance' do
57
+ sql = connection.create_table(:activity_tests, inherits: [:activities, :tests]) do |t|
58
+ t.string :grade
59
+ end
60
+
61
+ result = 'CREATE TABLE "activity_tests" ('
62
+ result << '"grade" character varying'
63
+ result << ') INHERITS ( "activities" , "tests" )'
64
+ expect(sql).to eql(result)
65
+ end
66
+
67
+ it 'allows empty-body create table operation' do
68
+ sql = connection.create_table(:activity_posts, inherits: :activities)
69
+ result = 'CREATE TABLE "activity_posts" ()'
70
+ result << ' INHERITS ( "activities" )'
71
+ expect(sql).to eql(result)
72
+ end
73
+ end
74
+
75
+ context 'on schema' do
76
+ it 'dumps single inheritance with body' do
77
+ dump_io = StringIO.new
78
+ ActiveRecord::SchemaDumper.dump(connection, dump_io)
79
+
80
+ parts = '"activity_books"'
81
+ parts << ', id: false'
82
+ parts << ', force: :cascade'
83
+ parts << ', inherits: :activities'
84
+ expect(dump_io.string).to match(/create_table #{parts} do /)
85
+ end
86
+
87
+ it 'dumps single inheritance without body' do
88
+ dump_io = StringIO.new
89
+ ActiveRecord::SchemaDumper.dump(connection, dump_io)
90
+
91
+ parts = '"activity_post_samples"'
92
+ parts << ', id: false'
93
+ parts << ', force: :cascade'
94
+ parts << ', inherits: :activity_posts'
95
+ expect(dump_io.string).to match(/create_table #{parts}(?! do \|t\|)/)
96
+ end
97
+
98
+ it 'dumps multiple inheritance' do
99
+ dump_io = StringIO.new
100
+ ActiveRecord::SchemaDumper.dump(connection, dump_io)
101
+
102
+ parts = '"activity_posts"'
103
+ parts << ', id: false'
104
+ parts << ', force: :cascade'
105
+ parts << ', inherits: (\[:images, :activities\]|\[:activities, :images\])'
106
+ expect(dump_io.string).to match(/create_table #{parts}/)
107
+ end
108
+ end
109
+
110
+ context 'on schema cache' do
111
+ subject { ActiveRecord::Base.connection.schema_cache }
112
+
113
+ it 'correctly defines the associations' do
114
+ scenario = {
115
+ 'M' => %w(N),
116
+ 'N' => %w(C),
117
+ 'C' => %w(B),
118
+ 'B' => %w(A),
119
+ 'D' => %w(A),
120
+ 'F' => %w(E),
121
+ 'G' => %w(E H),
122
+ }
123
+
124
+ subject.instance_variable_set(:@inheritance_loaded, true)
125
+ subject.instance_variable_set(:@inheritance_dependencies, scenario)
126
+ subject.instance_variable_set(:@inheritance_associations, subject.send(:generate_associations))
127
+ expect(subject.instance_variable_get(:@inheritance_associations)).to eql({
128
+ 'A' => %w(B D C N M),
129
+ 'B' => %w(C N M),
130
+ 'C' => %w(N M),
131
+ 'N' => %w(M),
132
+ 'E' => %w(F G),
133
+ 'H' => %w(G),
134
+ })
135
+ end
136
+
137
+ context 'on looking up models' do
138
+ after(:all) do
139
+ schema_cache = ActiveRecord::Base.connection.schema_cache
140
+ schema_cache.instance_variable_set(:@data_sources, {})
141
+ schema_cache.instance_variable_set(:@data_sources_model_names, {})
142
+ end
143
+
144
+ it 'respect irregular names' do
145
+ Torque::PostgreSQL.config.irregular_models = {
146
+ 'posts' => 'ActivityPost',
147
+ }
148
+
149
+ subject.send(:prepare_data_sources)
150
+ list = subject.instance_variable_get(:@data_sources_model_names)
151
+ expect(list).to have_key('posts')
152
+ expect(list['posts']).to eql(ActivityPost)
153
+ end
154
+
155
+ it 'does not load irregular where the data source is not defined' do
156
+ Torque::PostgreSQL.config.irregular_models = {
157
+ 'products' => 'Product',
158
+ }
159
+
160
+ subject.send(:prepare_data_sources)
161
+ list = subject.instance_variable_get(:@data_sources_model_names)
162
+ expect(list).to_not have_key('products')
163
+ end
164
+
165
+ {
166
+ 'activities' => 'Activity',
167
+ 'activity_posts' => 'ActivityPost',
168
+ 'activity_post_samples' => 'ActivityPost::Sample',
169
+ }.each do |table_name, expected_model|
170
+ it "translate the table name #{table_name} to #{expected_model} model" do
171
+ expect(subject.lookup_model(table_name)).to eql(expected_model.constantize)
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ context 'on inheritance' do
178
+ let(:base) { Activity }
179
+ let(:child) { ActivityPost }
180
+ let(:child2) { ActivityBook }
181
+ let(:other) { AuthorJournalist }
182
+
183
+ it 'identifies mergeable attributes' do
184
+ result_base = %w(id author_id title active kind created_at updated_at description url file post_id)
185
+ expect(base.inheritance_mergeable_attributes.sort).to eql(result_base.sort)
186
+ end
187
+
188
+ it 'has a merged version of attributes' do
189
+ result_base = %w(id author_id title active kind created_at updated_at description url activated file post_id)
190
+ result_child = %w(id author_id title active kind created_at updated_at file post_id url activated)
191
+ result_child2 = %w(id author_id title active kind created_at updated_at description url activated)
192
+ result_other = %w(id name type specialty)
193
+
194
+ expect(base.inheritance_merged_attributes).to eql(result_base)
195
+ expect(child.inheritance_merged_attributes).to eql(result_child)
196
+ expect(child2.inheritance_merged_attributes).to eql(result_child2)
197
+ expect(other.inheritance_merged_attributes).to eql(result_other)
198
+ end
199
+
200
+ it 'identifies physical inheritance' do
201
+ expect(base.physically_inherited?).to be_falsey
202
+ expect(child.physically_inherited?).to be_truthy
203
+ expect(child2.physically_inherited?).to be_truthy
204
+ expect(other.physically_inherited?).to be_falsey
205
+ end
206
+
207
+ it 'returns a list of dependent tables' do
208
+ expect(base.inheritance_dependents).to eql(%w(activity_books activity_posts activity_post_samples))
209
+ expect(child.inheritance_dependents).to eql(%w(activity_post_samples))
210
+ expect(child2.inheritance_dependents).to eql(%w())
211
+ expect(other.inheritance_dependents).to eql(%w())
212
+ end
213
+
214
+ it 'can check dependency' do
215
+ expect(base.physically_inheritances?).to be_truthy
216
+ expect(child.physically_inheritances?).to be_truthy
217
+ expect(child2.physically_inheritances?).to be_falsey
218
+ expect(other.physically_inheritances?).to be_falsey
219
+ end
220
+
221
+ it 'returns the list of models that the records can be casted to' do
222
+ expect(base.casted_dependents.values.map(&:name)).to eql(%w(ActivityBook ActivityPost ActivityPost::Sample))
223
+ expect(child.casted_dependents.values.map(&:name)).to eql(%w(ActivityPost::Sample))
224
+ expect(child2.casted_dependents.values.map(&:name)).to eql(%w())
225
+ expect(other.casted_dependents.values.map(&:name)).to eql(%w())
226
+ end
227
+
228
+ it 'correctly generates the tables name' do
229
+ expect(base.table_name).to eql('activities')
230
+ expect(child.table_name).to eql('activity_posts')
231
+ expect(child2.table_name).to eql('activity_books')
232
+ expect(other.table_name).to eql('authors')
233
+ end
234
+ end
235
+
236
+ context 'on relation' do
237
+ let(:base) { Activity }
238
+ let(:child) { ActivityBook }
239
+ let(:other) { AuthorJournalist }
240
+
241
+ it 'has operation methods' do
242
+ expect(base).to respond_to(:itself_only)
243
+ expect(base).to respond_to(:cast_records)
244
+ expect(base.new).to respond_to(:cast_record)
245
+ end
246
+
247
+ context 'itself only' do
248
+ it 'does not mess with original queries' do
249
+ expect(base.all.to_sql).to \
250
+ eql('SELECT "activities".* FROM "activities"')
251
+ end
252
+
253
+ it 'adds the only condition to the query' do
254
+ expect(base.itself_only.to_sql).to \
255
+ eql('SELECT "activities".* FROM ONLY "activities"')
256
+ end
257
+
258
+ it 'returns the right ammount of entries' do
259
+ base.create!(title: 'Activity only')
260
+ child.create!(title: 'Activity book')
261
+
262
+ expect(base.count).to eql(2)
263
+ expect(base.itself_only.count).to eql(1)
264
+ expect(child.count).to eql(1)
265
+ end
266
+ end
267
+
268
+ context 'cast records' do
269
+ before :each do
270
+ base.create(title: 'Activity test')
271
+ child.create(title: 'Activity book', url: 'bookurl1')
272
+ other.create(name: 'An author name')
273
+ end
274
+
275
+ it 'does not mess with single table inheritance' do
276
+ result = 'SELECT "authors".* FROM "authors"'
277
+ result << " WHERE \"authors\".\"type\" = 'AuthorJournalist'"
278
+ expect(other.all.to_sql).to eql(result)
279
+ end
280
+
281
+ it 'adds all statements to load all the necessary records' do
282
+ result = 'WITH "record_class" AS (SELECT "pg_class"."oid", "pg_class"."relname" AS _record_class FROM "pg_class")'
283
+ result << ' SELECT "activities".*, "record_class"."_record_class", "i_0"."description"'
284
+ result << ', COALESCE("i_0"."url", "i_1"."url", "i_2"."url") AS url, "i_0"."activated" AS activity_books__activated'
285
+ result << ', "i_1"."activated" AS activity_posts__activated, "i_2"."activated" AS activity_post_samples__activated'
286
+ result << ', COALESCE("i_1"."file", "i_2"."file") AS file, COALESCE("i_1"."post_id", "i_2"."post_id") AS post_id'
287
+ result << ", \"record_class\".\"_record_class\" IN ('activity_books', 'activity_posts', 'activity_post_samples') AS _auto_cast"
288
+ result << ' FROM "activities"'
289
+ result << ' INNER JOIN "record_class" ON "record_class"."oid" = "activities"."tableoid"'
290
+ result << ' LEFT OUTER JOIN "activity_books" "i_0" ON "activities"."id" = "i_0"."id"'
291
+ result << ' LEFT OUTER JOIN "activity_posts" "i_1" ON "activities"."id" = "i_1"."id"'
292
+ result << ' LEFT OUTER JOIN "activity_post_samples" "i_2" ON "activities"."id" = "i_2"."id"'
293
+ expect(base.cast_records.all.to_sql).to eql(result)
294
+ end
295
+
296
+ it 'can be have simplefied joins' do
297
+ result = 'WITH "record_class" AS (SELECT "pg_class"."oid", "pg_class"."relname" AS _record_class FROM "pg_class")'
298
+ result << ' SELECT "activities".*, "record_class"."_record_class"'
299
+ result << ', "i_0"."description", "i_0"."url", "i_0"."activated"'
300
+ result << ", \"record_class\".\"_record_class\" IN ('activity_books') AS _auto_cast"
301
+ result << ' FROM "activities"'
302
+ result << ' INNER JOIN "record_class" ON "record_class"."oid" = "activities"."tableoid"'
303
+ result << ' LEFT OUTER JOIN "activity_books" "i_0" ON "activities"."id" = "i_0"."id"'
304
+ expect(base.cast_records(child).all.to_sql).to eql(result)
305
+ end
306
+
307
+ it 'can be filtered by record type' do
308
+ result = 'WITH "record_class" AS (SELECT "pg_class"."oid", "pg_class"."relname" AS _record_class FROM "pg_class")'
309
+ result << ' SELECT "activities".*, "record_class"."_record_class"'
310
+ result << ', "i_0"."description", "i_0"."url", "i_0"."activated"'
311
+ result << ", \"record_class\".\"_record_class\" IN ('activity_books') AS _auto_cast"
312
+ result << ' FROM "activities"'
313
+ result << ' INNER JOIN "record_class" ON "record_class"."oid" = "activities"."tableoid"'
314
+ result << ' LEFT OUTER JOIN "activity_books" "i_0" ON "activities"."id" = "i_0"."id"'
315
+ result << " WHERE \"record_class\".\"_record_class\" = 'activity_books'"
316
+ expect(base.cast_records(child, filter: true).all.to_sql).to eql(result)
317
+ end
318
+
319
+ it 'works with count and does not add extra columns' do
320
+ result = 'WITH "record_class" AS (SELECT "pg_class"."oid", "pg_class"."relname" AS _record_class FROM "pg_class")'
321
+ result << ' SELECT COUNT(*)'
322
+ result << ' FROM "activities"'
323
+ result << ' INNER JOIN "record_class" ON "record_class"."oid" = "activities"."tableoid"'
324
+ result << ' LEFT OUTER JOIN "activity_books" "i_0" ON "activities"."id" = "i_0"."id"'
325
+ result << ' LEFT OUTER JOIN "activity_posts" "i_1" ON "activities"."id" = "i_1"."id"'
326
+ result << ' LEFT OUTER JOIN "activity_post_samples" "i_2" ON "activities"."id" = "i_2"."id"'
327
+ query = get_last_executed_query{ base.cast_records.all.count }
328
+ expect(query).to eql(result)
329
+ end
330
+
331
+ it 'works with sum and does not add extra columns' do
332
+ result = 'WITH "record_class" AS (SELECT "pg_class"."oid", "pg_class"."relname" AS _record_class FROM "pg_class")'
333
+ result << ' SELECT SUM("activities"."id")'
334
+ result << ' FROM "activities"'
335
+ result << ' INNER JOIN "record_class" ON "record_class"."oid" = "activities"."tableoid"'
336
+ result << ' LEFT OUTER JOIN "activity_books" "i_0" ON "activities"."id" = "i_0"."id"'
337
+ result << ' LEFT OUTER JOIN "activity_posts" "i_1" ON "activities"."id" = "i_1"."id"'
338
+ result << ' LEFT OUTER JOIN "activity_post_samples" "i_2" ON "activities"."id" = "i_2"."id"'
339
+ query = get_last_executed_query{ base.cast_records.all.sum(:id) }
340
+ expect(query).to eql(result)
341
+ end
342
+
343
+ it 'returns the correct model object' do
344
+ ActivityPost.create(title: 'Activity post')
345
+ ActivityPost::Sample.create(title: 'Activity post')
346
+ records = base.cast_records.order(:id).load.to_a
347
+
348
+ expect(records[0].class).to eql(Activity)
349
+ expect(records[1].class).to eql(ActivityBook)
350
+ expect(records[2].class).to eql(ActivityPost)
351
+ expect(records[3].class).to eql(ActivityPost::Sample)
352
+ end
353
+
354
+ it 'does not cast unnecessary records' do
355
+ ActivityPost.create(title: 'Activity post')
356
+ records = base.cast_records(ActivityBook).order(:id).load.to_a
357
+
358
+ expect(records[0].class).to eql(Activity)
359
+ expect(records[1].class).to eql(ActivityBook)
360
+ expect(records[2].class).to eql(Activity)
361
+ end
362
+
363
+ it 'correctly identify same name attributes' do
364
+ ActivityPost.create(title: 'Activity post', url: 'posturl1')
365
+ records = base.cast_records.order(:id).load.to_a
366
+
367
+ expect(records[1].url).to eql('bookurl1')
368
+ expect(records[2].url).to eql('posturl1')
369
+ end
370
+ end
371
+
372
+ context 'cast record' do
373
+ before :each do
374
+ base.create(title: 'Activity test')
375
+ child.create(title: 'Activity book')
376
+ other.create(name: 'An author name')
377
+ end
378
+
379
+ it 'does not affect normal records' do
380
+ base.instance_variable_set(:@casted_dependents, {})
381
+ expect(base.first.cast_record).to be_a(base)
382
+ expect(child.first.cast_record).to be_a(child)
383
+ expect(other.first.cast_record).to be_a(other)
384
+ end
385
+
386
+ it 'rises an error when the casted model cannot be defined' do
387
+ base.instance_variable_set(:@casted_dependents, {})
388
+ expect{ base.second.cast_record }.to raise_error(ArgumentError, /to type 'activity_books'/)
389
+ end
390
+
391
+ it 'can return the record class even when the auxiliary statement is not mentioned' do
392
+ expect(base.first._record_class).to eql('activities')
393
+ expect(base.second._record_class).to eql('activity_books')
394
+ expect(other.first._record_class).to eql('authors')
395
+ end
396
+
397
+ it 'does trigger record casting when accessed through inheritance' do
398
+ base.instance_variable_set(:@casted_dependents, nil)
399
+ expect(base.second.cast_record).to eql(child.first)
400
+ end
401
+ end
402
+ end
403
+ end