torque-postgresql 1.1.1 → 2.0.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 +5 -5
  2. data/Rakefile +5 -2
  3. data/lib/torque/postgresql.rb +0 -2
  4. data/lib/torque/postgresql/adapter.rb +0 -1
  5. data/lib/torque/postgresql/adapter/database_statements.rb +4 -15
  6. data/lib/torque/postgresql/adapter/schema_creation.rb +13 -23
  7. data/lib/torque/postgresql/adapter/schema_definitions.rb +7 -21
  8. data/lib/torque/postgresql/adapter/schema_dumper.rb +71 -11
  9. data/lib/torque/postgresql/adapter/schema_statements.rb +2 -12
  10. data/lib/torque/postgresql/associations.rb +0 -3
  11. data/lib/torque/postgresql/associations/association_scope.rb +18 -61
  12. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +2 -1
  13. data/lib/torque/postgresql/associations/preloader.rb +0 -24
  14. data/lib/torque/postgresql/associations/preloader/association.rb +13 -9
  15. data/lib/torque/postgresql/auxiliary_statement.rb +12 -17
  16. data/lib/torque/postgresql/coder.rb +1 -2
  17. data/lib/torque/postgresql/config.rb +0 -4
  18. data/lib/torque/postgresql/inheritance.rb +13 -17
  19. data/lib/torque/postgresql/reflection/abstract_reflection.rb +19 -25
  20. data/lib/torque/postgresql/relation.rb +11 -16
  21. data/lib/torque/postgresql/relation/auxiliary_statement.rb +9 -15
  22. data/lib/torque/postgresql/relation/distinct_on.rb +1 -1
  23. data/lib/torque/postgresql/schema_cache.rb +19 -11
  24. data/lib/torque/postgresql/version.rb +1 -1
  25. data/spec/en.yml +19 -0
  26. data/spec/factories/authors.rb +6 -0
  27. data/spec/factories/comments.rb +13 -0
  28. data/spec/factories/posts.rb +6 -0
  29. data/spec/factories/tags.rb +5 -0
  30. data/spec/factories/texts.rb +5 -0
  31. data/spec/factories/users.rb +6 -0
  32. data/spec/factories/videos.rb +5 -0
  33. data/spec/mocks/cache_query.rb +16 -0
  34. data/spec/mocks/create_table.rb +35 -0
  35. data/spec/models/activity.rb +3 -0
  36. data/spec/models/activity_book.rb +4 -0
  37. data/spec/models/activity_post.rb +7 -0
  38. data/spec/models/activity_post/sample.rb +4 -0
  39. data/spec/models/author.rb +4 -0
  40. data/spec/models/author_journalist.rb +4 -0
  41. data/spec/models/comment.rb +3 -0
  42. data/spec/models/course.rb +2 -0
  43. data/spec/models/geometry.rb +2 -0
  44. data/spec/models/guest_comment.rb +4 -0
  45. data/spec/models/post.rb +6 -0
  46. data/spec/models/tag.rb +2 -0
  47. data/spec/models/text.rb +2 -0
  48. data/spec/models/time_keeper.rb +2 -0
  49. data/spec/models/user.rb +8 -0
  50. data/spec/models/video.rb +2 -0
  51. data/spec/schema.rb +141 -0
  52. data/spec/spec_helper.rb +59 -0
  53. data/spec/tests/arel_spec.rb +72 -0
  54. data/spec/tests/auxiliary_statement_spec.rb +593 -0
  55. data/spec/tests/belongs_to_many_spec.rb +240 -0
  56. data/spec/tests/coder_spec.rb +367 -0
  57. data/spec/tests/collector_spec.rb +59 -0
  58. data/spec/tests/distinct_on_spec.rb +65 -0
  59. data/spec/tests/enum_set_spec.rb +306 -0
  60. data/spec/tests/enum_spec.rb +628 -0
  61. data/spec/tests/geometric_builder_spec.rb +221 -0
  62. data/spec/tests/has_many_spec.rb +390 -0
  63. data/spec/tests/interval_spec.rb +167 -0
  64. data/spec/tests/lazy_spec.rb +24 -0
  65. data/spec/tests/period_spec.rb +954 -0
  66. data/spec/tests/quoting_spec.rb +24 -0
  67. data/spec/tests/range_spec.rb +36 -0
  68. data/spec/tests/relation_spec.rb +57 -0
  69. data/spec/tests/table_inheritance_spec.rb +416 -0
  70. metadata +103 -16
  71. data/lib/torque/postgresql/associations/join_dependency/join_association.rb +0 -15
  72. data/lib/torque/postgresql/schema_dumper.rb +0 -88
@@ -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,416 @@
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
+
235
+ it 'respects the table name prefix and sufix defined on parent module' do
236
+ mod = Object.const_set('Private', Module.new)
237
+ mod.define_singleton_method(:table_name_prefix) { 'private.' }
238
+ mod.define_singleton_method(:table_name_suffix) { '_bundle' }
239
+ result = 'private.activity_post_others_bundle'
240
+
241
+ klass = mod.const_set('Other', Class.new(ActivityPost))
242
+ allow(klass).to receive(:module_parent).and_return(child)
243
+ allow(klass).to receive(:module_parents).and_return([mod])
244
+ allow(klass).to receive(:physically_inherited?).and_return(true)
245
+ expect(klass.send(:compute_table_name)).to be_eql(result)
246
+ end
247
+ end
248
+
249
+ context 'on relation' do
250
+ let(:base) { Activity }
251
+ let(:child) { ActivityBook }
252
+ let(:other) { AuthorJournalist }
253
+
254
+ it 'has operation methods' do
255
+ expect(base).to respond_to(:itself_only)
256
+ expect(base).to respond_to(:cast_records)
257
+ expect(base.new).to respond_to(:cast_record)
258
+ end
259
+
260
+ context 'itself only' do
261
+ it 'does not mess with original queries' do
262
+ expect(base.all.to_sql).to \
263
+ eql('SELECT "activities".* FROM "activities"')
264
+ end
265
+
266
+ it 'adds the only condition to the query' do
267
+ expect(base.itself_only.to_sql).to \
268
+ eql('SELECT "activities".* FROM ONLY "activities"')
269
+ end
270
+
271
+ it 'returns the right ammount of entries' do
272
+ base.create!(title: 'Activity only')
273
+ child.create!(title: 'Activity book')
274
+
275
+ expect(base.count).to eql(2)
276
+ expect(base.itself_only.count).to eql(1)
277
+ expect(child.count).to eql(1)
278
+ end
279
+ end
280
+
281
+ context 'cast records' do
282
+ before :each do
283
+ base.create(title: 'Activity test')
284
+ child.create(title: 'Activity book', url: 'bookurl1')
285
+ other.create(name: 'An author name')
286
+ end
287
+
288
+ it 'does not mess with single table inheritance' do
289
+ result = 'SELECT "authors".* FROM "authors"'
290
+ result << " WHERE \"authors\".\"type\" = 'AuthorJournalist'"
291
+ expect(other.all.to_sql).to eql(result)
292
+ end
293
+
294
+ it 'adds all statements to load all the necessary records' do
295
+ result = 'WITH "record_class" AS (SELECT "pg_class"."oid", "pg_class"."relname" AS _record_class FROM "pg_class")'
296
+ result << ' SELECT "activities".*, "record_class"."_record_class", "i_0"."description"'
297
+ result << ', COALESCE("i_0"."url", "i_1"."url", "i_2"."url") AS url, "i_0"."activated" AS activity_books__activated'
298
+ result << ', "i_1"."activated" AS activity_posts__activated, "i_2"."activated" AS activity_post_samples__activated'
299
+ result << ', COALESCE("i_1"."file", "i_2"."file") AS file, COALESCE("i_1"."post_id", "i_2"."post_id") AS post_id'
300
+ result << ", \"record_class\".\"_record_class\" IN ('activity_books', 'activity_posts', 'activity_post_samples') 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
+ result << ' LEFT OUTER JOIN "activity_posts" "i_1" ON "activities"."id" = "i_1"."id"'
305
+ result << ' LEFT OUTER JOIN "activity_post_samples" "i_2" ON "activities"."id" = "i_2"."id"'
306
+ expect(base.cast_records.all.to_sql).to eql(result)
307
+ end
308
+
309
+ it 'can be have simplefied joins' do
310
+ result = 'WITH "record_class" AS (SELECT "pg_class"."oid", "pg_class"."relname" AS _record_class FROM "pg_class")'
311
+ result << ' SELECT "activities".*, "record_class"."_record_class"'
312
+ result << ', "i_0"."description", "i_0"."url", "i_0"."activated"'
313
+ result << ", \"record_class\".\"_record_class\" IN ('activity_books') AS _auto_cast"
314
+ result << ' FROM "activities"'
315
+ result << ' INNER JOIN "record_class" ON "record_class"."oid" = "activities"."tableoid"'
316
+ result << ' LEFT OUTER JOIN "activity_books" "i_0" ON "activities"."id" = "i_0"."id"'
317
+ expect(base.cast_records(child).all.to_sql).to eql(result)
318
+ end
319
+
320
+ it 'can be filtered by record type' do
321
+ result = 'WITH "record_class" AS (SELECT "pg_class"."oid", "pg_class"."relname" AS _record_class FROM "pg_class")'
322
+ result << ' SELECT "activities".*, "record_class"."_record_class"'
323
+ result << ', "i_0"."description", "i_0"."url", "i_0"."activated"'
324
+ result << ", \"record_class\".\"_record_class\" IN ('activity_books') AS _auto_cast"
325
+ result << ' FROM "activities"'
326
+ result << ' INNER JOIN "record_class" ON "record_class"."oid" = "activities"."tableoid"'
327
+ result << ' LEFT OUTER JOIN "activity_books" "i_0" ON "activities"."id" = "i_0"."id"'
328
+ result << " WHERE \"record_class\".\"_record_class\" = 'activity_books'"
329
+ expect(base.cast_records(child, filter: true).all.to_sql).to eql(result)
330
+ end
331
+
332
+ it 'works with count and does not add extra columns' do
333
+ result = 'WITH "record_class" AS (SELECT "pg_class"."oid", "pg_class"."relname" AS _record_class FROM "pg_class")'
334
+ result << ' SELECT COUNT(*)'
335
+ result << ' FROM "activities"'
336
+ result << ' INNER JOIN "record_class" ON "record_class"."oid" = "activities"."tableoid"'
337
+ result << ' LEFT OUTER JOIN "activity_books" "i_0" ON "activities"."id" = "i_0"."id"'
338
+ result << ' LEFT OUTER JOIN "activity_posts" "i_1" ON "activities"."id" = "i_1"."id"'
339
+ result << ' LEFT OUTER JOIN "activity_post_samples" "i_2" ON "activities"."id" = "i_2"."id"'
340
+ query = get_last_executed_query{ base.cast_records.all.count }
341
+ expect(query).to eql(result)
342
+ end
343
+
344
+ it 'works with sum and does not add extra columns' do
345
+ result = 'WITH "record_class" AS (SELECT "pg_class"."oid", "pg_class"."relname" AS _record_class FROM "pg_class")'
346
+ result << ' SELECT SUM("activities"."id")'
347
+ result << ' FROM "activities"'
348
+ result << ' INNER JOIN "record_class" ON "record_class"."oid" = "activities"."tableoid"'
349
+ result << ' LEFT OUTER JOIN "activity_books" "i_0" ON "activities"."id" = "i_0"."id"'
350
+ result << ' LEFT OUTER JOIN "activity_posts" "i_1" ON "activities"."id" = "i_1"."id"'
351
+ result << ' LEFT OUTER JOIN "activity_post_samples" "i_2" ON "activities"."id" = "i_2"."id"'
352
+ query = get_last_executed_query{ base.cast_records.all.sum(:id) }
353
+ expect(query).to eql(result)
354
+ end
355
+
356
+ it 'returns the correct model object' do
357
+ ActivityPost.create(title: 'Activity post')
358
+ ActivityPost::Sample.create(title: 'Activity post')
359
+ records = base.cast_records.order(:id).load.to_a
360
+
361
+ expect(records[0].class).to eql(Activity)
362
+ expect(records[1].class).to eql(ActivityBook)
363
+ expect(records[2].class).to eql(ActivityPost)
364
+ expect(records[3].class).to eql(ActivityPost::Sample)
365
+ end
366
+
367
+ it 'does not cast unnecessary records' do
368
+ ActivityPost.create(title: 'Activity post')
369
+ records = base.cast_records(ActivityBook).order(:id).load.to_a
370
+
371
+ expect(records[0].class).to eql(Activity)
372
+ expect(records[1].class).to eql(ActivityBook)
373
+ expect(records[2].class).to eql(Activity)
374
+ end
375
+
376
+ it 'correctly identify same name attributes' do
377
+ ActivityPost.create(title: 'Activity post', url: 'posturl1')
378
+ records = base.cast_records.order(:id).load.to_a
379
+
380
+ expect(records[1].url).to eql('bookurl1')
381
+ expect(records[2].url).to eql('posturl1')
382
+ end
383
+ end
384
+
385
+ context 'cast record' do
386
+ before :each do
387
+ base.create(title: 'Activity test')
388
+ child.create(title: 'Activity book')
389
+ other.create(name: 'An author name')
390
+ end
391
+
392
+ it 'does not affect normal records' do
393
+ base.instance_variable_set(:@casted_dependents, {})
394
+ expect(base.first.cast_record).to be_a(base)
395
+ expect(child.first.cast_record).to be_a(child)
396
+ expect(other.first.cast_record).to be_a(other)
397
+ end
398
+
399
+ it 'rises an error when the casted model cannot be defined' do
400
+ base.instance_variable_set(:@casted_dependents, {})
401
+ expect{ base.second.cast_record }.to raise_error(ArgumentError, /to type 'activity_books'/)
402
+ end
403
+
404
+ it 'can return the record class even when the auxiliary statement is not mentioned' do
405
+ expect(base.first._record_class).to eql('activities')
406
+ expect(base.second._record_class).to eql('activity_books')
407
+ expect(other.first._record_class).to eql('authors')
408
+ end
409
+
410
+ it 'does trigger record casting when accessed through inheritance' do
411
+ base.instance_variable_set(:@casted_dependents, nil)
412
+ expect(base.second.cast_record).to eql(child.first)
413
+ end
414
+ end
415
+ end
416
+ end