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.
- checksums.yaml +5 -5
- data/Rakefile +5 -2
- data/lib/torque/postgresql.rb +0 -2
- data/lib/torque/postgresql/adapter.rb +0 -1
- data/lib/torque/postgresql/adapter/database_statements.rb +4 -15
- data/lib/torque/postgresql/adapter/schema_creation.rb +13 -23
- data/lib/torque/postgresql/adapter/schema_definitions.rb +7 -21
- data/lib/torque/postgresql/adapter/schema_dumper.rb +71 -11
- data/lib/torque/postgresql/adapter/schema_statements.rb +2 -12
- data/lib/torque/postgresql/associations.rb +0 -3
- data/lib/torque/postgresql/associations/association_scope.rb +18 -61
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +2 -1
- data/lib/torque/postgresql/associations/preloader.rb +0 -24
- data/lib/torque/postgresql/associations/preloader/association.rb +13 -9
- data/lib/torque/postgresql/auxiliary_statement.rb +12 -17
- data/lib/torque/postgresql/coder.rb +1 -2
- data/lib/torque/postgresql/config.rb +0 -4
- data/lib/torque/postgresql/inheritance.rb +13 -17
- data/lib/torque/postgresql/reflection/abstract_reflection.rb +19 -25
- data/lib/torque/postgresql/relation.rb +11 -16
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +9 -15
- data/lib/torque/postgresql/relation/distinct_on.rb +1 -1
- data/lib/torque/postgresql/schema_cache.rb +19 -11
- data/lib/torque/postgresql/version.rb +1 -1
- data/spec/en.yml +19 -0
- data/spec/factories/authors.rb +6 -0
- data/spec/factories/comments.rb +13 -0
- data/spec/factories/posts.rb +6 -0
- data/spec/factories/tags.rb +5 -0
- data/spec/factories/texts.rb +5 -0
- data/spec/factories/users.rb +6 -0
- data/spec/factories/videos.rb +5 -0
- data/spec/mocks/cache_query.rb +16 -0
- data/spec/mocks/create_table.rb +35 -0
- data/spec/models/activity.rb +3 -0
- data/spec/models/activity_book.rb +4 -0
- data/spec/models/activity_post.rb +7 -0
- data/spec/models/activity_post/sample.rb +4 -0
- data/spec/models/author.rb +4 -0
- data/spec/models/author_journalist.rb +4 -0
- data/spec/models/comment.rb +3 -0
- data/spec/models/course.rb +2 -0
- data/spec/models/geometry.rb +2 -0
- data/spec/models/guest_comment.rb +4 -0
- data/spec/models/post.rb +6 -0
- data/spec/models/tag.rb +2 -0
- data/spec/models/text.rb +2 -0
- data/spec/models/time_keeper.rb +2 -0
- data/spec/models/user.rb +8 -0
- data/spec/models/video.rb +2 -0
- data/spec/schema.rb +141 -0
- data/spec/spec_helper.rb +59 -0
- data/spec/tests/arel_spec.rb +72 -0
- data/spec/tests/auxiliary_statement_spec.rb +593 -0
- data/spec/tests/belongs_to_many_spec.rb +240 -0
- data/spec/tests/coder_spec.rb +367 -0
- data/spec/tests/collector_spec.rb +59 -0
- data/spec/tests/distinct_on_spec.rb +65 -0
- data/spec/tests/enum_set_spec.rb +306 -0
- data/spec/tests/enum_spec.rb +628 -0
- data/spec/tests/geometric_builder_spec.rb +221 -0
- data/spec/tests/has_many_spec.rb +390 -0
- data/spec/tests/interval_spec.rb +167 -0
- data/spec/tests/lazy_spec.rb +24 -0
- data/spec/tests/period_spec.rb +954 -0
- data/spec/tests/quoting_spec.rb +24 -0
- data/spec/tests/range_spec.rb +36 -0
- data/spec/tests/relation_spec.rb +57 -0
- data/spec/tests/table_inheritance_spec.rb +416 -0
- metadata +103 -16
- data/lib/torque/postgresql/associations/join_dependency/join_association.rb +0 -15
- 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
|