updateable_views_inheritance 1.4.2 → 1.4.4
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/.deepsource.toml +8 -0
- data/.github/workflows/build.yml +66 -0
- data/.gitignore +4 -1
- data/.vscode/settings.json +6 -0
- data/CHANGELOG.md +24 -8
- data/README.md +166 -0
- data/Rakefile +3 -3
- data/lib/updateable_views_inheritance/active_record.rb +3 -1
- data/lib/updateable_views_inheritance/postgresql_adapter.rb +205 -191
- data/lib/updateable_views_inheritance/version.rb +1 -1
- data/test/content_test.rb +2 -1
- data/test/dummy/app/assets/config/manifest.js +0 -0
- data/test/dummy/config/database.yml +1 -0
- data/test/install_generator_test.rb +2 -2
- data/test/instantiation_test.rb +38 -0
- data/test/migration_test.rb +3 -3
- data/test/schema_test.rb +32 -2
- data/test/single_table_inheritance.rb +1 -1
- data/test/test_helper.rb +21 -0
- data/updateable_views_inheritance.gemspec +8 -4
- metadata +81 -19
- data/README.rdoc +0 -121
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'active_record/connection_adapters/postgresql/utils'
|
2
2
|
|
3
|
-
|
4
3
|
module ActiveRecord #:nodoc:
|
5
4
|
module ConnectionAdapters #:nodoc:
|
6
5
|
module PostgreSQL
|
@@ -9,30 +8,38 @@ module ActiveRecord #:nodoc:
|
|
9
8
|
# Options:
|
10
9
|
# [:parent]
|
11
10
|
# parent relation
|
12
|
-
# [:
|
11
|
+
# [:table]
|
13
12
|
# default is <tt>"#{child_view}_data"</tt>
|
13
|
+
# [:skip_creating_child_table]
|
14
|
+
# use together with :table option
|
14
15
|
def create_child(child_view, options)
|
15
16
|
raise 'Please call me with a parent, for example: create_child(:steam_locomotives, :parent => :locomotives)' unless options[:parent]
|
16
17
|
|
17
|
-
unqualified_child_view_name = Utils.extract_schema_qualified_name(child_view).identifier
|
18
|
-
|
19
18
|
parent_relation = options[:parent].to_s
|
20
|
-
if is_view?(parent_relation) # interpreted as inheritance chain deeper than two levels
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
parent_table = if is_view?(parent_relation) # interpreted as inheritance chain deeper than two levels
|
20
|
+
query(<<~SQL)[0][0]
|
21
|
+
SELECT child_relation
|
22
|
+
FROM updateable_views_inheritance
|
23
|
+
WHERE child_aggregate_view = #{quote(parent_relation)}
|
24
|
+
SQL
|
25
|
+
else
|
26
|
+
parent_relation
|
27
|
+
end
|
25
28
|
|
26
29
|
child_table = options[:table] || quote_table_name("#{child_view}_data")
|
27
|
-
child_table_pk = "#{unqualified_child_view_name.singularize}_id"
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
31
|
+
unless options.key?(:skip_creating_child_table)
|
32
|
+
unqualified_child_view_name = Utils.extract_schema_qualified_name(child_view).identifier
|
33
|
+
child_table_pk = "#{unqualified_child_view_name.singularize}_id"
|
34
|
+
|
35
|
+
create_table(child_table, :id => false) do |t|
|
36
|
+
t.integer child_table_pk, :null => false
|
37
|
+
yield t
|
38
|
+
end
|
39
|
+
execute "ALTER TABLE #{child_table} ADD PRIMARY KEY (#{child_table_pk})"
|
40
|
+
execute "ALTER TABLE #{child_table} ADD FOREIGN KEY (#{child_table_pk})
|
41
|
+
REFERENCES #{parent_table} ON DELETE CASCADE ON UPDATE CASCADE"
|
32
42
|
end
|
33
|
-
execute "ALTER TABLE #{child_table} ADD PRIMARY KEY (#{child_table_pk})"
|
34
|
-
execute "ALTER TABLE #{child_table} ADD FOREIGN KEY (#{child_table_pk})
|
35
|
-
REFERENCES #{parent_table} ON DELETE CASCADE ON UPDATE CASCADE"
|
36
43
|
|
37
44
|
create_child_view(parent_relation, child_view, child_table)
|
38
45
|
end
|
@@ -84,9 +91,9 @@ module ActiveRecord #:nodoc:
|
|
84
91
|
end
|
85
92
|
if pk
|
86
93
|
if sequence
|
87
|
-
select_value <<-
|
94
|
+
select_value <<-SQL, 'Reset sequence'
|
88
95
|
SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
|
89
|
-
|
96
|
+
SQL
|
90
97
|
else
|
91
98
|
@logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
|
92
99
|
end
|
@@ -102,7 +109,7 @@ module ActiveRecord #:nodoc:
|
|
102
109
|
# Returns a relation's primary key and belonging sequence. If +relation+ is a table the result is its PK and sequence.
|
103
110
|
# When it is a view, PK and sequence of the table at the root of the inheritance chain are returned.
|
104
111
|
def pk_and_sequence_for(relation)
|
105
|
-
result = query(<<-
|
112
|
+
result = query(<<-SQL, 'PK')[0]
|
106
113
|
SELECT attr.attname
|
107
114
|
FROM pg_attribute attr,
|
108
115
|
pg_constraint cons
|
@@ -110,7 +117,7 @@ module ActiveRecord #:nodoc:
|
|
110
117
|
AND cons.conrelid = '#{relation}'::regclass
|
111
118
|
AND cons.contype = 'p'
|
112
119
|
AND attr.attnum = ANY(cons.conkey)
|
113
|
-
|
120
|
+
SQL
|
114
121
|
if result.nil? or result.empty?
|
115
122
|
parent = parent_table(relation)
|
116
123
|
pk_and_sequence_for(parent) if parent
|
@@ -130,7 +137,7 @@ module ActiveRecord #:nodoc:
|
|
130
137
|
# Return the list of all views in the schema search path.
|
131
138
|
def views(name=nil)
|
132
139
|
schemas = schema_search_path.split(/,\s*/).map { |p| quote(p) }.join(',')
|
133
|
-
query(
|
140
|
+
query(<<~SQL, name).map { |row| row[0] }
|
134
141
|
SELECT viewname
|
135
142
|
FROM pg_views
|
136
143
|
WHERE schemaname IN (#{schemas})
|
@@ -139,7 +146,7 @@ module ActiveRecord #:nodoc:
|
|
139
146
|
|
140
147
|
# Checks whether relation +name+ is a view.
|
141
148
|
def is_view?(name)
|
142
|
-
result = query(
|
149
|
+
result = query(<<~SQL, name).map { |row| row[0] }
|
143
150
|
SELECT viewname
|
144
151
|
FROM pg_views
|
145
152
|
WHERE viewname = '#{name}'
|
@@ -149,11 +156,11 @@ module ActiveRecord #:nodoc:
|
|
149
156
|
|
150
157
|
# Recursively delete +parent_relation+ (if it is a view) and the children views the depend on it.
|
151
158
|
def remove_parent_and_children_views(parent_relation)
|
152
|
-
children_views = query(<<-
|
159
|
+
children_views = query(<<-SQL).map{|row| row[0]}
|
153
160
|
SELECT child_aggregate_view
|
154
161
|
FROM updateable_views_inheritance
|
155
162
|
WHERE parent_relation = '#{parent_relation}'
|
156
|
-
|
163
|
+
SQL
|
157
164
|
children_views.each do |cv|
|
158
165
|
remove_parent_and_children_views(cv)
|
159
166
|
# drop the view only if it wasn't dropped beforehand in recursive call from other method.
|
@@ -172,18 +179,18 @@ module ActiveRecord #:nodoc:
|
|
172
179
|
def rebuild_parent_and_children_views(parent_relation)
|
173
180
|
# Current implementation is not very efficient - it can drop and recreate one and the same view in the bottom of the hierarchy many times.
|
174
181
|
remove_parent_and_children_views(parent_relation)
|
175
|
-
children = query(<<-
|
182
|
+
children = query(<<-SQL)
|
176
183
|
SELECT parent_relation, child_aggregate_view, child_relation
|
177
184
|
FROM updateable_views_inheritance
|
178
185
|
WHERE parent_relation = '#{parent_relation}'
|
179
|
-
|
186
|
+
SQL
|
180
187
|
|
181
188
|
#if the parent is in the middle of the inheritance chain, it's a view that should be rebuilt as well
|
182
|
-
parent = query(<<-
|
189
|
+
parent = query(<<-SQL)[0]
|
183
190
|
SELECT parent_relation, child_aggregate_view, child_relation
|
184
191
|
FROM updateable_views_inheritance
|
185
192
|
WHERE child_aggregate_view = '#{parent_relation}'
|
186
|
-
|
193
|
+
SQL
|
187
194
|
create_child_view(parent[0], parent[1], parent[2]) if (parent && !parent.empty?)
|
188
195
|
|
189
196
|
children.each do |child|
|
@@ -220,11 +227,11 @@ module ActiveRecord #:nodoc:
|
|
220
227
|
quoted_inheritance_column = quote_column_name(parent_klass_name.inheritance_column)
|
221
228
|
queries = relations.map{|rel| generate_single_table_inheritanche_union_clause(rel, sorted_column_names, conflict_column_names, columns_hash, quoted_inheritance_column)}
|
222
229
|
unioin_clauses = queries.join("\n UNION ")
|
223
|
-
execute <<-
|
230
|
+
execute <<-SQL
|
224
231
|
CREATE VIEW #{sti_aggregate_view} AS (
|
225
232
|
#{unioin_clauses}
|
226
233
|
)
|
227
|
-
|
234
|
+
SQL
|
228
235
|
end
|
229
236
|
|
230
237
|
# Recreates the Single_Table_Inheritanche-like aggregate view +sti_aggregate_view+
|
@@ -234,7 +241,7 @@ module ActiveRecord #:nodoc:
|
|
234
241
|
create_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view)
|
235
242
|
end
|
236
243
|
|
237
|
-
# Overriden - it must return false, otherwise deleting fixtures won't work
|
244
|
+
# Overriden - it solargraph-must return false, otherwise deleting fixtures won't work
|
238
245
|
def supports_disable_referential_integrity?
|
239
246
|
false
|
240
247
|
end
|
@@ -247,194 +254,201 @@ module ActiveRecord #:nodoc:
|
|
247
254
|
module Tutuf #:nodoc:
|
248
255
|
class ClassTableReflection
|
249
256
|
class << self
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
@@klasses.uniq
|
257
|
+
# Returns all models' class objects that are ActiveRecord::Base descendants
|
258
|
+
def all_db_klasses
|
259
|
+
return @@klasses if defined?(@@klasses)
|
260
|
+
|
261
|
+
@@klasses = []
|
262
|
+
# load model classes so that inheritance_column is set correctly where defined
|
263
|
+
model_filenames.collect{|m| load "#{Rails.root}/app/models/#{m}";m.match(%r{([^/]+?)\.rb$})[1].camelize.constantize }.each do |klass|
|
264
|
+
@@klasses << klass if klass < ActiveRecord::Base
|
259
265
|
end
|
266
|
+
@@klasses.uniq
|
267
|
+
end
|
260
268
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
269
|
+
# Returns the class object for +table_name+
|
270
|
+
def get_klass_for_table(table_name)
|
271
|
+
klass_for_tables()[table_name.to_s]
|
272
|
+
end
|
265
273
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
@@tables_klasses = {}
|
271
|
-
all_db_klasses.each do |klass|
|
272
|
-
@@tables_klasses[klass.table_name] = klass if klass.respond_to?(:table_name)
|
273
|
-
end
|
274
|
-
@@tables_klasses
|
275
|
-
end
|
274
|
+
# Returns hash with tables and thier corresponding class.
|
275
|
+
# {table_name1 => ClassName1, ...}
|
276
|
+
def klass_for_tables
|
277
|
+
return @@tables_klasses if defined?(@@tables_klasses)
|
276
278
|
|
277
|
-
|
278
|
-
|
279
|
-
|
279
|
+
@@tables_klasses = {}
|
280
|
+
all_db_klasses.each do |klass|
|
281
|
+
@@tables_klasses[klass.table_name] = klass if klass.respond_to?(:table_name)
|
280
282
|
end
|
283
|
+
@@tables_klasses
|
284
|
+
end
|
285
|
+
|
286
|
+
# Returns filenames for models in the current Rails application
|
287
|
+
def model_filenames
|
288
|
+
Dir.chdir("#{Rails.root}/app/models"){ Dir["**/*.rb"] }
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Set default values from the table columns for a view
|
295
|
+
def set_defaults(view_name, table_name)
|
296
|
+
column_definitions(table_name).each do |column_name, type, default, notnull|
|
297
|
+
if !default.nil?
|
298
|
+
execute("ALTER TABLE #{quote_table_name(view_name)} ALTER #{quote_column_name(column_name)} SET DEFAULT #{default}")
|
281
299
|
end
|
282
300
|
end
|
283
301
|
end
|
284
302
|
|
285
303
|
private
|
286
304
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
305
|
+
def do_create_child_view(parent_table, parent_columns, parent_pk, child_view, child_columns, child_pk, child_table)
|
306
|
+
view_columns = parent_columns + child_columns
|
307
|
+
execute(<<~SQL)
|
308
|
+
CREATE OR REPLACE VIEW #{quote_column_name(child_view)} AS (
|
309
|
+
SELECT parent.#{parent_pk},
|
310
|
+
#{ view_columns.map { |col| quote_column_name(col) }.join(",") }
|
311
|
+
FROM #{parent_table} parent
|
312
|
+
INNER JOIN #{child_table} child
|
313
|
+
ON ( parent.#{parent_pk}=child.#{child_pk} )
|
314
|
+
)
|
315
|
+
SQL
|
316
|
+
end
|
317
|
+
|
318
|
+
# Creates rules for +INSERT+, +UPDATE+ and +DELETE+ on the view
|
319
|
+
def make_child_view_updateable(parent_table, parent_columns, parent_pk, parent_pk_seq, child_view, child_columns, child_pk, child_table)
|
320
|
+
# insert
|
321
|
+
# NEW.#{parent_pk} can be explicitly specified and when it is null every call to it increments the sequence.
|
322
|
+
# Setting the sequence to its value (explicitly supplied or the default) covers both cases.
|
323
|
+
execute(<<~SQL)
|
324
|
+
CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_insert")} AS
|
325
|
+
ON INSERT TO #{quote_column_name(child_view)} DO INSTEAD (
|
326
|
+
INSERT INTO #{parent_table}
|
327
|
+
( #{ [parent_pk, parent_columns].flatten.map { |col| quote_column_name(col) }.join(", ") } )
|
328
|
+
VALUES( DEFAULT #{ parent_columns.empty? ? '' : ' ,' + parent_columns.collect{ |col| "NEW.#{quote_column_name(col)}" }.join(", ") } ) ;
|
329
|
+
INSERT INTO #{child_table}
|
330
|
+
( #{ [child_pk, child_columns].flatten.map { |col| quote_column_name(col) }.join(",")} )
|
331
|
+
VALUES( currval('#{parent_pk_seq}') #{ child_columns.empty? ? '' : ' ,' + child_columns.collect{ |col| "NEW.#{quote_column_name(col)}" }.join(", ") } )
|
332
|
+
#{insert_returning_clause(parent_pk, child_pk, child_view)}
|
333
|
+
)
|
334
|
+
SQL
|
335
|
+
|
336
|
+
# delete
|
337
|
+
execute(<<~SQL)
|
338
|
+
CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_delete")} AS
|
339
|
+
ON DELETE TO #{quote_column_name(child_view)} DO INSTEAD
|
340
|
+
DELETE FROM #{parent_table} WHERE #{parent_pk} = OLD.#{parent_pk}
|
341
|
+
SQL
|
299
342
|
|
300
|
-
#
|
301
|
-
|
302
|
-
#
|
303
|
-
#
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
ON INSERT TO #{child_view} DO INSTEAD (
|
308
|
-
INSERT INTO #{parent_table}
|
309
|
-
( #{ [parent_pk, parent_columns].flatten.join(", ") } )
|
310
|
-
VALUES( DEFAULT #{ parent_columns.empty? ? '' : ' ,' + parent_columns.collect{ |col| "NEW." + col}.join(", ") } ) ;
|
311
|
-
INSERT INTO #{child_table}
|
312
|
-
( #{ [child_pk, child_columns].flatten.join(",")} )
|
313
|
-
VALUES( currval('#{parent_pk_seq}') #{ child_columns.empty? ? '' : ' ,' + child_columns.collect{ |col| "NEW." + col}.join(", ") } )
|
314
|
-
#{insert_returning_clause(parent_pk, child_pk, child_view)}
|
315
|
-
)
|
316
|
-
end_sql
|
317
|
-
|
318
|
-
# delete
|
319
|
-
execute <<-end_sql
|
320
|
-
CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_delete")} AS
|
321
|
-
ON DELETE TO #{child_view} DO INSTEAD
|
322
|
-
DELETE FROM #{parent_table} WHERE #{parent_pk} = OLD.#{parent_pk}
|
323
|
-
end_sql
|
324
|
-
|
325
|
-
# update
|
326
|
-
execute <<-end_sql
|
327
|
-
CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_update")} AS
|
328
|
-
ON UPDATE TO #{child_view} DO INSTEAD (
|
329
|
-
#{ parent_columns.empty? ? '':
|
343
|
+
# update
|
344
|
+
execute(<<~SQL)
|
345
|
+
CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_update")} AS
|
346
|
+
ON UPDATE TO #{quote_column_name(child_view)} DO INSTEAD (
|
347
|
+
#{ if parent_columns.empty?
|
348
|
+
''
|
349
|
+
else
|
330
350
|
"UPDATE #{parent_table}
|
331
|
-
SET #{ parent_columns.
|
332
|
-
WHERE #{parent_pk} = OLD.#{parent_pk};"
|
333
|
-
|
351
|
+
SET #{ parent_columns.map { |col| "#{quote_column_name(col)} = NEW.#{quote_column_name(col)}" }.join(', ')}
|
352
|
+
WHERE #{parent_pk} = OLD.#{parent_pk};"
|
353
|
+
end }
|
354
|
+
#{ if child_columns.empty?
|
355
|
+
''
|
356
|
+
else
|
334
357
|
"UPDATE #{child_table}
|
335
|
-
SET #{ child_columns.
|
358
|
+
SET #{ child_columns.map { |col| "#{quote_column_name(col)} = NEW.#{quote_column_name(col)}" }.join(', ')}
|
336
359
|
WHERE #{child_pk} = OLD.#{parent_pk}"
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
360
|
+
end }
|
361
|
+
)
|
362
|
+
SQL
|
363
|
+
end
|
341
364
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
365
|
+
def insert_returning_clause(parent_pk, child_pk, child_view)
|
366
|
+
columns_cast_to_null = columns(child_view)
|
367
|
+
.reject { |c| c.name == parent_pk}
|
368
|
+
.map { |c| "CAST (NULL AS #{c.sql_type})" }
|
369
|
+
.join(", ")
|
370
|
+
"RETURNING #{child_pk}, #{columns_cast_to_null}"
|
371
|
+
end
|
349
372
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
373
|
+
def create_system_table_records(parent_relation, child_aggregate_view, child_relation)
|
374
|
+
parent_relation, child_aggregate_view, child_relation = [parent_relation, child_aggregate_view, child_relation].collect{|rel| quote(rel.to_s)}
|
375
|
+
exists = query <<~SQL
|
376
|
+
SELECT parent_relation, child_aggregate_view, child_relation
|
377
|
+
FROM updateable_views_inheritance
|
378
|
+
WHERE parent_relation = #{parent_relation}
|
379
|
+
AND child_aggregate_view = #{child_aggregate_view}
|
380
|
+
AND child_relation = #{child_relation}
|
381
|
+
SQL
|
382
|
+
# log "res: #{exists}"
|
383
|
+
if exists.nil? or exists.empty?
|
384
|
+
execute "INSERT INTO updateable_views_inheritance (parent_relation, child_aggregate_view, child_relation)" +
|
385
|
+
"VALUES( #{parent_relation}, #{child_aggregate_view}, #{child_relation} )"
|
357
386
|
end
|
387
|
+
end
|
358
388
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
SELECT parent_relation
|
389
|
+
def parent_table(relation)
|
390
|
+
if table_exists?('updateable_views_inheritance')
|
391
|
+
res = query(<<-SQL, 'Parent relation')[0]
|
392
|
+
SELECT parent_relation
|
363
393
|
FROM updateable_views_inheritance
|
364
|
-
WHERE
|
365
|
-
|
366
|
-
|
367
|
-
end_sql
|
368
|
-
# log "res: #{exists}"
|
369
|
-
if exists.nil? or exists.empty?
|
370
|
-
execute "INSERT INTO updateable_views_inheritance (parent_relation, child_aggregate_view, child_relation)" +
|
371
|
-
"VALUES( #{parent_relation}, #{child_aggregate_view}, #{child_relation} )"
|
372
|
-
end
|
373
|
-
end
|
374
|
-
|
375
|
-
def parent_table(relation)
|
376
|
-
if table_exists?('updateable_views_inheritance')
|
377
|
-
res = query(<<-end_sql, 'Parent relation')[0]
|
378
|
-
SELECT parent_relation
|
379
|
-
FROM updateable_views_inheritance
|
380
|
-
WHERE child_aggregate_view = '#{relation}'
|
381
|
-
end_sql
|
382
|
-
res[0] if res
|
383
|
-
end
|
394
|
+
WHERE child_aggregate_view = '#{relation}'
|
395
|
+
SQL
|
396
|
+
res[0] if res
|
384
397
|
end
|
398
|
+
end
|
385
399
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
end
|
405
|
-
hierarchy
|
400
|
+
# Single Table Inheritance Aggregate View
|
401
|
+
|
402
|
+
# Nested list for the +parent_relation+ inheritance hierarchy
|
403
|
+
# Every descendant relation is presented as an array with relation's name as first element
|
404
|
+
# and the other elements are the relation's children presented in the same way as lists.
|
405
|
+
# For example:
|
406
|
+
# [[child_view1, [grandchild11,[...]], [grandchild12]],
|
407
|
+
# [child_view2, [...]
|
408
|
+
# ]
|
409
|
+
def get_view_hierarchy_for(parent_relation)
|
410
|
+
hierarchy = []
|
411
|
+
children = query(<<-SQL)
|
412
|
+
SELECT parent_relation, child_aggregate_view, child_relation
|
413
|
+
FROM updateable_views_inheritance
|
414
|
+
WHERE parent_relation = '#{parent_relation}'
|
415
|
+
SQL
|
416
|
+
children.each do |child|
|
417
|
+
hierarchy << [child[1], *get_view_hierarchy_for(child[1])]
|
406
418
|
end
|
419
|
+
hierarchy
|
420
|
+
end
|
407
421
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
end
|
422
|
+
def get_leaves_relations(hierarchy)
|
423
|
+
return [] if hierarchy.nil? || hierarchy.empty?
|
424
|
+
head, hierarchy = hierarchy.first, hierarchy[1..(hierarchy.size)]
|
425
|
+
if(head.is_a? Array)
|
426
|
+
return (get_leaves_relations(head) + get_leaves_relations(hierarchy)).compact
|
427
|
+
elsif(hierarchy.nil? || hierarchy.empty?)
|
428
|
+
return [head]
|
429
|
+
else
|
430
|
+
return get_leaves_relations(hierarchy).compact
|
418
431
|
end
|
432
|
+
end
|
419
433
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
end
|
429
|
-
statement = " AS #{col_name}"
|
430
|
-
statement = "#{value} #{statement}"
|
431
|
-
arr << " #{statement}"
|
434
|
+
def generate_single_table_inheritanche_union_clause(rel, column_names, conflict_column_names, columns_hash, quoted_inheritance_column)
|
435
|
+
relation_columns = columns(rel).collect{|c| c.name}
|
436
|
+
columns_select = column_names.inject([]) do |arr, col_name|
|
437
|
+
sql_type = conflict_column_names.include?(col_name) ? 'text' : columns_hash[col_name].sql_type
|
438
|
+
value = "NULL::#{sql_type}"
|
439
|
+
if(relation_columns.include?(col_name))
|
440
|
+
value = col_name
|
441
|
+
value = "#{value}::text" if conflict_column_names.include?(col_name)
|
432
442
|
end
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
["SELECT", columns_select, "FROM #{rel} #{where_clause}"].join(" ")
|
443
|
+
statement = " AS #{col_name}"
|
444
|
+
statement = "#{value} #{statement}"
|
445
|
+
arr << " #{statement}"
|
437
446
|
end
|
447
|
+
columns_select = columns_select.join(", ")
|
448
|
+
rel_klass_name = Tutuf::ClassTableReflection.get_klass_for_table(rel)
|
449
|
+
where_clause = " WHERE #{quoted_inheritance_column} = '#{rel_klass_name}'"
|
450
|
+
["SELECT", columns_select, "FROM #{rel} #{where_clause}"].join(" ")
|
451
|
+
end
|
438
452
|
end
|
439
453
|
end
|
440
454
|
end
|
data/test/content_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative 'test_helper'
|
2
2
|
|
3
3
|
class UpdateableViewsInheritanceContentTest < ActiveSupport::TestCase
|
4
4
|
def setup
|
@@ -6,6 +6,7 @@ class UpdateableViewsInheritanceContentTest < ActiveSupport::TestCase
|
|
6
6
|
ActiveRecord::FixtureSet.reset_cache
|
7
7
|
end
|
8
8
|
|
9
|
+
|
9
10
|
def test_find
|
10
11
|
ActiveRecord::FixtureSet.create_fixtures(File.dirname(__FILE__) + '/fixtures/', :steam_locomotives)
|
11
12
|
locomotive = Locomotive.find(1)
|
File without changes
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative 'test_helper'
|
2
2
|
require 'generators/updateable_views_inheritance/install_generator'
|
3
3
|
|
4
4
|
class InstallGeneratorTest < Rails::Generators::TestCase
|
@@ -10,4 +10,4 @@ class InstallGeneratorTest < Rails::Generators::TestCase
|
|
10
10
|
run_generator
|
11
11
|
assert_migration 'db/migrate/create_updateable_views_inheritance.rb'
|
12
12
|
end
|
13
|
-
end
|
13
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class InstantiationTest < ActiveSupport::TestCase
|
4
|
+
def setup
|
5
|
+
ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 7)
|
6
|
+
# order of fixtures is important for the test - last loaded should not be with max(id)
|
7
|
+
%w[steam_locomotives electric_locomotives maglev_locomotives bicycles].each do |f|
|
8
|
+
ActiveRecord::FixtureSet.create_fixtures(File.dirname(__FILE__) + '/fixtures/', f)
|
9
|
+
end
|
10
|
+
@connection = ActiveRecord::Base.connection
|
11
|
+
|
12
|
+
Locomotive.disable_inheritance_instantiation = true
|
13
|
+
ElectricLocomotive.disable_inheritance_instantiation = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
Locomotive.disable_inheritance_instantiation = false
|
18
|
+
ActiveRecord::FixtureSet.reset_cache
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_setting_disable_inheritance_instantiation_does_not_load_child_columns
|
22
|
+
unless Locomotive.first
|
23
|
+
puts "num locomotives:", @connection.select_one("SELECT count(id) FROM locomotives")
|
24
|
+
end
|
25
|
+
assert_equal %w[id max_speed name type],
|
26
|
+
Locomotive.first.attributes.keys.sort
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_switching_off_disable_inheritance_instantiation_loads_child_columns
|
30
|
+
assert_equal %w[electricity_consumption id magnetic_field max_speed name type],
|
31
|
+
MaglevLocomotive.first.attributes.keys.sort
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_disable_inheritance_instantiatioon_not_set_loads_child_attributes
|
35
|
+
assert_equal %w[id name number_of_gears number_of_wheels vehicle_type],
|
36
|
+
Bicycle.first.attributes.keys.sort
|
37
|
+
end
|
38
|
+
end
|
data/test/migration_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative 'test_helper'
|
2
2
|
|
3
3
|
class UpdateableViewsInheritanceMigrationTest < ActiveSupport::TestCase
|
4
4
|
# We use transactional fixtures - migration from the setup is rolled back by Rails on teardown
|
@@ -17,7 +17,7 @@ class UpdateableViewsInheritanceMigrationTest < ActiveSupport::TestCase
|
|
17
17
|
assert_equal %w(electricity_consumption id max_speed name type),
|
18
18
|
@connection.columns(:electric_locomotives).map{ |c| c.name }.sort
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def test_drop_child
|
22
22
|
ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 3)
|
23
23
|
ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 2)
|
@@ -27,4 +27,4 @@ class UpdateableViewsInheritanceMigrationTest < ActiveSupport::TestCase
|
|
27
27
|
steam_locomotives_data
|
28
28
|
updateable_views_inheritance), @connection.tables.sort
|
29
29
|
end
|
30
|
-
end
|
30
|
+
end
|