torque-postgresql 1.1.8 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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