torque-postgresql 1.1.5 → 2.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- 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 +74 -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 -60
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +16 -11
- data/lib/torque/postgresql/associations/preloader.rb +0 -24
- data/lib/torque/postgresql/associations/preloader/association.rb +13 -9
- data/lib/torque/postgresql/autosave_association.rb +4 -4
- data/lib/torque/postgresql/auxiliary_statement.rb +1 -13
- 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/reflection/belongs_to_many_reflection.rb +16 -4
- data/lib/torque/postgresql/relation.rb +11 -16
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +2 -8
- data/lib/torque/postgresql/relation/distinct_on.rb +1 -1
- 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 +246 -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 +400 -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 +102 -14
- 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
|