updateable_views_inheritance 1.4.1 → 1.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9fbbb9446777110e2bf865f105afbcb4e745df87
4
- data.tar.gz: c7b1e21e88f857d6faca65194048044683ffb170
3
+ metadata.gz: 7a73bd114b93585afae503c742efea0bbb5fa03a
4
+ data.tar.gz: 50871f961687a777113315c999b6f578add39566
5
5
  SHA512:
6
- metadata.gz: b8326aa19c08ce403a26c7598c56bddf80a888c89bbd5707125d0429dbfe07e90c48674571531310ce16b458f44f42dbb64a9f5102a39c6767b0eada3d739dd0
7
- data.tar.gz: e03ca1453d61313e673f5dcf543b39571fa3813a6e34a23f0d925e3238b9194f482840febb0a2812ef90ddc4b5180a723901c020c676e5745ae055d2294c60f0
6
+ metadata.gz: 710e9a867878a14618a1ec28881a9dc48ca530a82df33690d55a69e996b3720fb7c0fe1a14b03e76b67528a73fac8d23d76c35d13578dd47648cf9ba6694ccc7
7
+ data.tar.gz: 0d7b56539848266e86167887dc686995f5f2691ded47e29b4ebf2ed66fa236b2c7e2c8ecd3843ad9c8f6f5d0eafdbf0463c67331866e56ce14aa5fce11ca9f97
data/CHANGELOG.md CHANGED
@@ -1,4 +1,9 @@
1
- ## 1.4.0 (2 0 March 2017)
1
+ ## 1.4.2 (28 March 2017)
2
+
3
+ Upgrade to Rails 4.2
4
+
5
+
6
+ ## 1.4.1 (20 March 2017)
2
7
 
3
8
  Upgrade to Rails 4.1
4
9
 
@@ -1,436 +1,441 @@
1
+ require 'active_record/connection_adapters/postgresql/utils'
2
+
3
+
1
4
  module ActiveRecord #:nodoc:
2
5
  module ConnectionAdapters #:nodoc:
3
- class PostgreSQLAdapter
4
- # Use this in migration to create child table and view.
5
- # Options:
6
- # [:parent]
7
- # parent relation
8
- # [:child_table_name]
9
- # default is <tt>"#{child_view}_data"</tt>
10
- def create_child(child_view, options)
11
- raise 'Please call me with a parent, for example: create_child(:steam_locomotives, :parent => :locomotives)' unless options[:parent]
12
-
13
- schema, unqualified_child_view_name = Utils.extract_schema_and_table(child_view)
14
-
15
- parent_relation = options[:parent].to_s
16
- if is_view?(parent_relation) # interpreted as inheritance chain deeper than two levels
17
- parent_table = query("SELECT child_relation FROM updateable_views_inheritance WHERE child_aggregate_view = #{quote(parent_relation)}")[0][0]
18
- else
19
- parent_table = parent_relation
20
- end
6
+ module PostgreSQL
7
+ module SchemaStatements
8
+ # Use this in migration to create child table and view.
9
+ # Options:
10
+ # [:parent]
11
+ # parent relation
12
+ # [:child_table_name]
13
+ # default is <tt>"#{child_view}_data"</tt>
14
+ def create_child(child_view, options)
15
+ raise 'Please call me with a parent, for example: create_child(:steam_locomotives, :parent => :locomotives)' unless options[:parent]
16
+
17
+ unqualified_child_view_name = Utils.extract_schema_qualified_name(child_view).identifier
18
+
19
+ parent_relation = options[:parent].to_s
20
+ if is_view?(parent_relation) # interpreted as inheritance chain deeper than two levels
21
+ parent_table = query("SELECT child_relation FROM updateable_views_inheritance WHERE child_aggregate_view = #{quote(parent_relation)}")[0][0]
22
+ else
23
+ parent_table = parent_relation
24
+ end
21
25
 
22
- child_table = options[:table] || quote_table_name("#{child_view}_data")
23
- child_table_pk = "#{unqualified_child_view_name.singularize}_id"
26
+ child_table = options[:table] || quote_table_name("#{child_view}_data")
27
+ child_table_pk = "#{unqualified_child_view_name.singularize}_id"
24
28
 
25
- create_table(child_table, :id => false) do |t|
26
- t.integer child_table_pk, :null => false
27
- yield t
28
- end
29
- execute "ALTER TABLE #{child_table} ADD PRIMARY KEY (#{child_table_pk})"
30
- execute "ALTER TABLE #{child_table} ADD FOREIGN KEY (#{child_table_pk})
31
- REFERENCES #{parent_table} ON DELETE CASCADE ON UPDATE CASCADE"
29
+ create_table(child_table, :id => false) do |t|
30
+ t.integer child_table_pk, :null => false
31
+ yield t
32
+ 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"
32
36
 
33
- create_child_view(parent_relation, child_view, child_table)
34
- end
37
+ create_child_view(parent_relation, child_view, child_table)
38
+ end
35
39
 
36
- # Drop child view and table
37
- def drop_child(child_view)
38
- drop_view(child_view)
39
- child_table = query("SELECT child_relation FROM updateable_views_inheritance WHERE child_aggregate_view = #{quote(child_view)}")[0][0]
40
- drop_table(child_table)
41
- execute "DELETE FROM updateable_views_inheritance WHERE child_aggregate_view = #{quote(child_view)}"
42
- end
40
+ # Drop child view and table
41
+ def drop_child(child_view)
42
+ drop_view(child_view)
43
+ child_table = query("SELECT child_relation FROM updateable_views_inheritance WHERE child_aggregate_view = #{quote(child_view)}")[0][0]
44
+ drop_table(child_table)
45
+ execute "DELETE FROM updateable_views_inheritance WHERE child_aggregate_view = #{quote(child_view)}"
46
+ end
43
47
 
44
- # Creates aggregate updateable view of parent and child relations. The convention for naming child tables is
45
- # <tt>"#{child_view}_data"</tt>. If you don't follow it, supply +child_table_name+ as third argument.
46
- def create_child_view(parent_table, child_view, child_table=nil)
47
- child_table ||= child_view.to_s + "_data"
48
+ # Creates aggregate updateable view of parent and child relations. The convention for naming child tables is
49
+ # <tt>"#{child_view}_data"</tt>. If you don't follow it, supply +child_table_name+ as third argument.
50
+ def create_child_view(parent_table, child_view, child_table=nil)
51
+ child_table ||= child_view.to_s + "_data"
48
52
 
49
- parent_columns = columns(parent_table)
50
- child_columns = columns(child_table)
53
+ parent_columns = columns(parent_table)
54
+ child_columns = columns(child_table)
51
55
 
52
- child_column_names = child_columns.collect{|c| c.name}
53
- parent_column_names = parent_columns.collect{|c| c.name}
56
+ child_column_names = child_columns.collect{|c| c.name}
57
+ parent_column_names = parent_columns.collect{|c| c.name}
54
58
 
55
- child_pk = pk_and_sequence_for(child_table)[0]
56
- child_column_names.delete(child_pk)
59
+ child_pk = pk_and_sequence_for(child_table)[0]
60
+ child_column_names.delete(child_pk)
57
61
 
58
- parent_pk, parent_pk_seq = pk_and_sequence_for(parent_table)
59
- parent_column_names.delete(parent_pk)
62
+ parent_pk, parent_pk_seq = pk_and_sequence_for(parent_table)
63
+ parent_column_names.delete(parent_pk)
60
64
 
61
- do_create_child_view(parent_table, parent_column_names, parent_pk, child_view, child_column_names, child_pk, child_table)
62
- make_child_view_updateable(parent_table, parent_column_names, parent_pk, parent_pk_seq, child_view, child_column_names, child_pk, child_table)
65
+ do_create_child_view(parent_table, parent_column_names, parent_pk, child_view, child_column_names, child_pk, child_table)
66
+ make_child_view_updateable(parent_table, parent_column_names, parent_pk, parent_pk_seq, child_view, child_column_names, child_pk, child_table)
63
67
 
64
- # assign default values for table columns on the view - it is not automatic in Postgresql 8.1
65
- set_defaults(child_view, parent_table)
66
- set_defaults(child_view, child_table)
67
- create_system_table_records(parent_table, child_view, child_table)
68
- end
68
+ # assign default values for table columns on the view - it is not automatic in Postgresql 8.1
69
+ set_defaults(child_view, parent_table)
70
+ set_defaults(child_view, child_table)
71
+ create_system_table_records(parent_table, child_view, child_table)
72
+ end
69
73
 
70
- # Resets sequence to the max value of the table's pk if present respecting inheritance (i.e. one sequence can be shared by many tables).
71
- def reset_pk_sequence!(table, pk = nil, sequence = nil)
72
- parent = parent_table(table)
73
- if parent
74
- reset_pk_sequence!(parent, pk, sequence)
75
- else
76
- unless pk and sequence
77
- default_pk, default_sequence = pk_and_sequence_for(table)
78
- pk ||= default_pk
79
- sequence ||= default_sequence
80
- end
81
- if pk
82
- if sequence
83
- select_value <<-end_sql, 'Reset sequence'
84
- SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
85
- end_sql
86
- else
87
- @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
74
+ # Resets sequence to the max value of the table's pk if present respecting inheritance (i.e. one sequence can be shared by many tables).
75
+ def reset_pk_sequence!(table, pk = nil, sequence = nil)
76
+ parent = parent_table(table)
77
+ if parent
78
+ reset_pk_sequence!(parent, pk, sequence)
79
+ else
80
+ unless pk and sequence
81
+ default_pk, default_sequence = pk_and_sequence_for(table)
82
+ pk ||= default_pk
83
+ sequence ||= default_sequence
84
+ end
85
+ if pk
86
+ if sequence
87
+ select_value <<-end_sql, 'Reset sequence'
88
+ SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
89
+ end_sql
90
+ else
91
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
92
+ end
88
93
  end
89
94
  end
90
95
  end
91
- end
92
96
 
93
- def primary_key(relation)
94
- res = pk_and_sequence_for(relation)
95
- res && res.first
96
- end
97
-
98
- # Returns a relation's primary key and belonging sequence. If +relation+ is a table the result is its PK and sequence.
99
- # When it is a view, PK and sequence of the table at the root of the inheritance chain are returned.
100
- def pk_and_sequence_for(relation)
101
- result = query(<<-end_sql, 'PK')[0]
102
- SELECT attr.attname
103
- FROM pg_attribute attr,
104
- pg_constraint cons
105
- WHERE cons.conrelid = attr.attrelid
106
- AND cons.conrelid = '#{relation}'::regclass
107
- AND cons.contype = 'p'
108
- AND attr.attnum = ANY(cons.conkey)
109
- end_sql
110
- if result.nil? or result.empty?
111
- parent = parent_table(relation)
112
- pk_and_sequence_for(parent) if parent
113
- else
114
- # log(result[0], "PK for #{relation}") {}
115
- [result[0], query("SELECT pg_get_serial_sequence('#{relation}', '#{result[0]}') ")[0][0]]
97
+ def primary_key(relation)
98
+ res = pk_and_sequence_for(relation)
99
+ res && res.first
116
100
  end
117
- rescue
118
- nil
119
- end
120
101
 
121
- # Drops a view from the database.
122
- def drop_view(name)
123
- execute "DROP VIEW #{name}"
124
- end
125
-
126
- # Return the list of all views in the schema search path.
127
- def views(name=nil)
128
- schemas = schema_search_path.split(/,\s*/).map { |p| quote(p) }.join(',')
129
- query(<<-SQL, name).map { |row| row[0] }
130
- SELECT viewname
131
- FROM pg_views
132
- WHERE schemaname IN (#{schemas})
133
- SQL
134
- end
135
-
136
- # Checks whether relation +name+ is a view.
137
- def is_view?(name)
138
- result = query(<<-SQL, name).map { |row| row[0] }
139
- SELECT viewname
140
- FROM pg_views
141
- WHERE viewname = '#{name}'
142
- SQL
143
- !result.empty?
144
- end
102
+ # Returns a relation's primary key and belonging sequence. If +relation+ is a table the result is its PK and sequence.
103
+ # When it is a view, PK and sequence of the table at the root of the inheritance chain are returned.
104
+ def pk_and_sequence_for(relation)
105
+ result = query(<<-end_sql, 'PK')[0]
106
+ SELECT attr.attname
107
+ FROM pg_attribute attr,
108
+ pg_constraint cons
109
+ WHERE cons.conrelid = attr.attrelid
110
+ AND cons.conrelid = '#{relation}'::regclass
111
+ AND cons.contype = 'p'
112
+ AND attr.attnum = ANY(cons.conkey)
113
+ end_sql
114
+ if result.nil? or result.empty?
115
+ parent = parent_table(relation)
116
+ pk_and_sequence_for(parent) if parent
117
+ else
118
+ # log(result[0], "PK for #{relation}") {}
119
+ [result[0], query("SELECT pg_get_serial_sequence('#{relation}', '#{result[0]}') ")[0][0]]
120
+ end
121
+ rescue
122
+ nil
123
+ end
145
124
 
146
- # Recursively delete +parent_relation+ (if it is a view) and the children views the depend on it.
147
- def remove_parent_and_children_views(parent_relation)
148
- children_views = query(<<-end_sql).map{|row| row[0]}
149
- SELECT child_aggregate_view
150
- FROM updateable_views_inheritance
151
- WHERE parent_relation = '#{parent_relation}'
152
- end_sql
153
- children_views.each do |cv|
154
- remove_parent_and_children_views(cv)
155
- # drop the view only if it wasn't dropped beforehand in recursive call from other method.
156
- drop_view(cv) if is_view?(cv)
125
+ # Drops a view from the database.
126
+ def drop_view(name)
127
+ execute "DROP VIEW #{name}"
157
128
  end
158
- drop_view(parent_relation) if is_view?(parent_relation)
159
- end
160
129
 
161
- # Recreates all views in all hierarchy chains
162
- def rebuild_all_parent_and_children_views
163
- parent_relations = select_values('SELECT DISTINCT parent_relation FROM updateable_views_inheritance')
164
- parent_relations.each { |parent_relation| rebuild_parent_and_children_views(parent_relation) }
165
- end
130
+ # Return the list of all views in the schema search path.
131
+ def views(name=nil)
132
+ schemas = schema_search_path.split(/,\s*/).map { |p| quote(p) }.join(',')
133
+ query(<<-SQL, name).map { |row| row[0] }
134
+ SELECT viewname
135
+ FROM pg_views
136
+ WHERE schemaname IN (#{schemas})
137
+ SQL
138
+ end
166
139
 
167
- # Recreates views in the part of the hierarchy chain starting from the +parent_relation+.
168
- def rebuild_parent_and_children_views(parent_relation)
169
- # Current implementation is not very efficient - it can drop and recreate one and the same view in the bottom of the hierarchy many times.
170
- remove_parent_and_children_views(parent_relation)
171
- children = query(<<-end_sql)
172
- SELECT parent_relation, child_aggregate_view, child_relation
173
- FROM updateable_views_inheritance
174
- WHERE parent_relation = '#{parent_relation}'
175
- end_sql
176
-
177
- #if the parent is in the middle of the inheritance chain, it's a view that should be rebuilt as well
178
- parent = query(<<-end_sql)[0]
179
- SELECT parent_relation, child_aggregate_view, child_relation
180
- FROM updateable_views_inheritance
181
- WHERE child_aggregate_view = '#{parent_relation}'
182
- end_sql
183
- create_child_view(parent[0], parent[1], parent[2]) if (parent && !parent.empty?)
184
-
185
- children.each do |child|
186
- create_child_view(child[0], child[1], child[2])
187
- rebuild_parent_and_children_views(child[1])
140
+ # Checks whether relation +name+ is a view.
141
+ def is_view?(name)
142
+ result = query(<<-SQL, name).map { |row| row[0] }
143
+ SELECT viewname
144
+ FROM pg_views
145
+ WHERE viewname = '#{name}'
146
+ SQL
147
+ !result.empty?
188
148
  end
189
- end
190
149
 
191
- # Creates Single Table Inheritanche-like aggregate view called +sti_aggregate_view+
192
- # for +parent_relation+ and all its descendants. <i>The view isn't updateable.</i>
193
- # The order of all or just the first few columns in the aggregate view can be explicitly set
194
- # by passing array of column names as third argument.
195
- # If there are columns with the same name but different types in two or more relations
196
- # they will appear as a single column of type +text+ in the view.
197
- def create_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view = nil)
198
- columns_for_view ||= []
199
- relations_heirarchy = get_view_hierarchy_for(parent_relation)
200
- relations = relations_heirarchy.flatten
201
- leaves_relations = get_leaves_relations(relations_heirarchy)
202
- all_columns = leaves_relations.map{|rel| columns(rel)}.flatten
203
- columns_hash = {}
204
- conflict_column_names = []
205
- all_columns.each do |col|
206
- c = columns_hash[col.name]
207
- if(c && col.sql_type != c.sql_type)
208
- conflict_column_names << col.name
209
- else
210
- columns_hash[col.name] = col
150
+ # Recursively delete +parent_relation+ (if it is a view) and the children views the depend on it.
151
+ def remove_parent_and_children_views(parent_relation)
152
+ children_views = query(<<-end_sql).map{|row| row[0]}
153
+ SELECT child_aggregate_view
154
+ FROM updateable_views_inheritance
155
+ WHERE parent_relation = '#{parent_relation}'
156
+ end_sql
157
+ children_views.each do |cv|
158
+ remove_parent_and_children_views(cv)
159
+ # drop the view only if it wasn't dropped beforehand in recursive call from other method.
160
+ drop_view(cv) if is_view?(cv)
211
161
  end
162
+ drop_view(parent_relation) if is_view?(parent_relation)
212
163
  end
213
- conflict_column_names = conflict_column_names.uniq.sort if !conflict_column_names.empty?
214
- sorted_column_names = (columns_for_view + columns_hash.keys.sort).uniq
215
- parent_klass_name = Tutuf::ClassTableReflection.get_klass_for_table(parent_relation)
216
- quoted_inheritance_column = quote_column_name(parent_klass_name.inheritance_column)
217
- queries = relations.map{|rel| generate_single_table_inheritanche_union_clause(rel, sorted_column_names, conflict_column_names, columns_hash, quoted_inheritance_column)}
218
- unioin_clauses = queries.join("\n UNION ")
219
- execute <<-end_sql
220
- CREATE VIEW #{sti_aggregate_view} AS (
221
- #{unioin_clauses}
222
- )
223
- end_sql
224
- end
225
-
226
- # Recreates the Single_Table_Inheritanche-like aggregate view +sti_aggregate_view+
227
- # for +parent_relation+ and all its descendants.
228
- def rebuild_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view = nil)
229
- drop_view(sti_aggregate_view)
230
- create_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view)
231
- end
232
164
 
233
- # Overriden - it must return false, otherwise deleting fixtures won't work
234
- def supports_disable_referential_integrity?
235
- false
236
- end
237
-
238
- def table_exists_with_updateable_views_inheritance_support?(name)
239
- is_view?(name) ? true : table_exists_without_updateable_views_inheritance_support?(name)
240
- end
241
- alias_method_chain :table_exists?, :updateable_views_inheritance_support
242
-
243
- module Tutuf #:nodoc:
244
- class ClassTableReflection
245
- class << self
246
- # Returns all models' class objects that are ActiveRecord::Base descendants
247
- def all_db_klasses
248
- return @@klasses if defined?(@@klasses)
249
- @@klasses = []
250
- # load model classes so that inheritance_column is set correctly where defined
251
- model_filenames.collect{|m| load "#{Rails.root}/app/models/#{m}";m.match(%r{([^/]+?)\.rb$})[1].camelize.constantize }.each do |klass|
252
- @@klasses << klass if klass < ActiveRecord::Base
253
- end
254
- @@klasses.uniq
255
- end
165
+ # Recreates all views in all hierarchy chains
166
+ def rebuild_all_parent_and_children_views
167
+ parent_relations = select_values('SELECT DISTINCT parent_relation FROM updateable_views_inheritance')
168
+ parent_relations.each { |parent_relation| rebuild_parent_and_children_views(parent_relation) }
169
+ end
256
170
 
257
- # Returns the class object for +table_name+
258
- def get_klass_for_table(table_name)
259
- klass_for_tables()[table_name.to_s]
260
- end
171
+ # Recreates views in the part of the hierarchy chain starting from the +parent_relation+.
172
+ def rebuild_parent_and_children_views(parent_relation)
173
+ # 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
+ remove_parent_and_children_views(parent_relation)
175
+ children = query(<<-end_sql)
176
+ SELECT parent_relation, child_aggregate_view, child_relation
177
+ FROM updateable_views_inheritance
178
+ WHERE parent_relation = '#{parent_relation}'
179
+ end_sql
261
180
 
262
- # Returns hash with tables and thier corresponding class.
263
- # {table_name1 => ClassName1, ...}
264
- def klass_for_tables
265
- return @@tables_klasses if defined?(@@tables_klasses)
266
- @@tables_klasses = {}
267
- all_db_klasses.each do |klass|
268
- @@tables_klasses[klass.table_name] = klass if klass.respond_to?(:table_name)
269
- end
270
- @@tables_klasses
271
- end
181
+ #if the parent is in the middle of the inheritance chain, it's a view that should be rebuilt as well
182
+ parent = query(<<-end_sql)[0]
183
+ SELECT parent_relation, child_aggregate_view, child_relation
184
+ FROM updateable_views_inheritance
185
+ WHERE child_aggregate_view = '#{parent_relation}'
186
+ end_sql
187
+ create_child_view(parent[0], parent[1], parent[2]) if (parent && !parent.empty?)
272
188
 
273
- # Returns filenames for models in the current Rails application
274
- def model_filenames
275
- Dir.chdir("#{Rails.root}/app/models"){ Dir["**/*.rb"] }
276
- end
189
+ children.each do |child|
190
+ create_child_view(child[0], child[1], child[2])
191
+ rebuild_parent_and_children_views(child[1])
277
192
  end
278
193
  end
279
- end
280
194
 
281
- private
282
-
283
- def do_create_child_view(parent_table, parent_columns, parent_pk, child_view, child_columns, child_pk, child_table)
284
- view_columns = parent_columns + child_columns
195
+ # Creates Single Table Inheritanche-like aggregate view called +sti_aggregate_view+
196
+ # for +parent_relation+ and all its descendants. <i>The view isn't updateable.</i>
197
+ # The order of all or just the first few columns in the aggregate view can be explicitly set
198
+ # by passing array of column names as third argument.
199
+ # If there are columns with the same name but different types in two or more relations
200
+ # they will appear as a single column of type +text+ in the view.
201
+ def create_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view = nil)
202
+ columns_for_view ||= []
203
+ relations_heirarchy = get_view_hierarchy_for(parent_relation)
204
+ relations = relations_heirarchy.flatten
205
+ leaves_relations = get_leaves_relations(relations_heirarchy)
206
+ all_columns = leaves_relations.map{|rel| columns(rel)}.flatten
207
+ columns_hash = {}
208
+ conflict_column_names = []
209
+ all_columns.each do |col|
210
+ c = columns_hash[col.name]
211
+ if(c && col.sql_type != c.sql_type)
212
+ conflict_column_names << col.name
213
+ else
214
+ columns_hash[col.name] = col
215
+ end
216
+ end
217
+ conflict_column_names = conflict_column_names.uniq.sort if !conflict_column_names.empty?
218
+ sorted_column_names = (columns_for_view + columns_hash.keys.sort).uniq
219
+ parent_klass_name = Tutuf::ClassTableReflection.get_klass_for_table(parent_relation)
220
+ quoted_inheritance_column = quote_column_name(parent_klass_name.inheritance_column)
221
+ queries = relations.map{|rel| generate_single_table_inheritanche_union_clause(rel, sorted_column_names, conflict_column_names, columns_hash, quoted_inheritance_column)}
222
+ unioin_clauses = queries.join("\n UNION ")
285
223
  execute <<-end_sql
286
- CREATE OR REPLACE VIEW #{child_view} AS (
287
- SELECT parent.#{parent_pk},
288
- #{ view_columns.join(",") }
289
- FROM #{parent_table} parent
290
- INNER JOIN #{child_table} child
291
- ON ( parent.#{parent_pk}=child.#{child_pk} )
224
+ CREATE VIEW #{sti_aggregate_view} AS (
225
+ #{unioin_clauses}
292
226
  )
293
227
  end_sql
294
228
  end
295
229
 
296
- # Creates rules for +INSERT+, +UPDATE+ and +DELETE+ on the view
297
- def make_child_view_updateable(parent_table, parent_columns, parent_pk, parent_pk_seq, child_view, child_columns, child_pk, child_table)
298
- # insert
299
- # NEW.#{parent_pk} can be explicitly specified and when it is null every call to it increments the sequence.
300
- # Setting the sequence to its value (explicitly supplied or the default) covers both cases.
301
- execute <<-end_sql
302
- CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_insert")} AS
303
- ON INSERT TO #{child_view} DO INSTEAD (
304
- INSERT INTO #{parent_table}
305
- ( #{ [parent_pk, parent_columns].flatten.join(", ") } )
306
- VALUES( DEFAULT #{ parent_columns.empty? ? '' : ' ,' + parent_columns.collect{ |col| "NEW." + col}.join(", ") } ) ;
307
- INSERT INTO #{child_table}
308
- ( #{ [child_pk, child_columns].flatten.join(",")} )
309
- VALUES( currval('#{parent_pk_seq}') #{ child_columns.empty? ? '' : ' ,' + child_columns.collect{ |col| "NEW." + col}.join(", ") } )
310
- #{insert_returning_clause(parent_pk, child_pk, child_view) if supports_insert_with_returning?}
311
- )
312
- end_sql
313
-
314
- # delete
315
- execute <<-end_sql
316
- CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_delete")} AS
317
- ON DELETE TO #{child_view} DO INSTEAD
318
- DELETE FROM #{parent_table} WHERE #{parent_pk} = OLD.#{parent_pk}
319
- end_sql
230
+ # Recreates the Single_Table_Inheritanche-like aggregate view +sti_aggregate_view+
231
+ # for +parent_relation+ and all its descendants.
232
+ def rebuild_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view = nil)
233
+ drop_view(sti_aggregate_view)
234
+ create_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view)
235
+ end
320
236
 
321
- # update
322
- execute <<-end_sql
323
- CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_update")} AS
324
- ON UPDATE TO #{child_view} DO INSTEAD (
325
- #{ parent_columns.empty? ? '':
326
- "UPDATE #{parent_table}
327
- SET #{ parent_columns.collect{ |col| col + "= NEW." +col }.join(", ") }
328
- WHERE #{parent_pk} = OLD.#{parent_pk};"}
329
- #{ child_columns.empty? ? '':
330
- "UPDATE #{child_table}
331
- SET #{ child_columns.collect{ |col| col + " = NEW." +col }.join(", ") }
332
- WHERE #{child_pk} = OLD.#{parent_pk}"
333
- }
334
- )
335
- end_sql
237
+ # Overriden - it must return false, otherwise deleting fixtures won't work
238
+ def supports_disable_referential_integrity?
239
+ false
336
240
  end
337
241
 
338
- def insert_returning_clause(parent_pk, child_pk, child_view)
339
- columns_cast_to_null = columns(child_view)
340
- .reject { |c| c.name == parent_pk}
341
- .map { |c| "CAST (NULL AS #{c.sql_type})" }
342
- .join(", ")
343
- "RETURNING #{child_pk}, #{columns_cast_to_null}"
242
+ def table_exists_with_updateable_views_inheritance_support?(name)
243
+ is_view?(name) ? true : table_exists_without_updateable_views_inheritance_support?(name)
344
244
  end
245
+ alias_method_chain :table_exists?, :updateable_views_inheritance_support
246
+
247
+ module Tutuf #:nodoc:
248
+ class ClassTableReflection
249
+ class << self
250
+ # Returns all models' class objects that are ActiveRecord::Base descendants
251
+ def all_db_klasses
252
+ return @@klasses if defined?(@@klasses)
253
+ @@klasses = []
254
+ # load model classes so that inheritance_column is set correctly where defined
255
+ model_filenames.collect{|m| load "#{Rails.root}/app/models/#{m}";m.match(%r{([^/]+?)\.rb$})[1].camelize.constantize }.each do |klass|
256
+ @@klasses << klass if klass < ActiveRecord::Base
257
+ end
258
+ @@klasses.uniq
259
+ end
345
260
 
346
- # Set default values from the table columns for a view
347
- def set_defaults(view_name, table_name)
348
- column_definitions(table_name).each do |column_name, type, default, notnull|
349
- if !default.nil?
350
- execute("ALTER TABLE #{quote_table_name(view_name)} ALTER #{quote_column_name(column_name)} SET DEFAULT #{default}")
261
+ # Returns the class object for +table_name+
262
+ def get_klass_for_table(table_name)
263
+ klass_for_tables()[table_name.to_s]
264
+ end
265
+
266
+ # Returns hash with tables and thier corresponding class.
267
+ # {table_name1 => ClassName1, ...}
268
+ def klass_for_tables
269
+ return @@tables_klasses if defined?(@@tables_klasses)
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
276
+
277
+ # Returns filenames for models in the current Rails application
278
+ def model_filenames
279
+ Dir.chdir("#{Rails.root}/app/models"){ Dir["**/*.rb"] }
280
+ end
351
281
  end
352
282
  end
353
283
  end
354
284
 
355
- def create_system_table_records(parent_relation, child_aggregate_view, child_relation)
356
- parent_relation, child_aggregate_view, child_relation = [parent_relation, child_aggregate_view, child_relation].collect{|rel| quote(rel.to_s)}
357
- exists = query <<-end_sql
358
- SELECT parent_relation, child_aggregate_view, child_relation
359
- FROM updateable_views_inheritance
360
- WHERE parent_relation = #{parent_relation}
361
- AND child_aggregate_view = #{child_aggregate_view}
362
- AND child_relation = #{child_relation}
363
- end_sql
364
- # log "res: #{exists}"
365
- if exists.nil? or exists.empty?
366
- execute "INSERT INTO updateable_views_inheritance (parent_relation, child_aggregate_view, child_relation)" +
367
- "VALUES( #{parent_relation}, #{child_aggregate_view}, #{child_relation} )"
285
+ private
286
+
287
+ def do_create_child_view(parent_table, parent_columns, parent_pk, child_view, child_columns, child_pk, child_table)
288
+ view_columns = parent_columns + child_columns
289
+ execute <<-end_sql
290
+ CREATE OR REPLACE VIEW #{child_view} AS (
291
+ SELECT parent.#{parent_pk},
292
+ #{ view_columns.join(",") }
293
+ FROM #{parent_table} parent
294
+ INNER JOIN #{child_table} child
295
+ ON ( parent.#{parent_pk}=child.#{child_pk} )
296
+ )
297
+ end_sql
298
+ end
299
+
300
+ # Creates rules for +INSERT+, +UPDATE+ and +DELETE+ on the view
301
+ def make_child_view_updateable(parent_table, parent_columns, parent_pk, parent_pk_seq, child_view, child_columns, child_pk, child_table)
302
+ # insert
303
+ # NEW.#{parent_pk} can be explicitly specified and when it is null every call to it increments the sequence.
304
+ # Setting the sequence to its value (explicitly supplied or the default) covers both cases.
305
+ execute <<-end_sql
306
+ CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_insert")} AS
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? ? '':
330
+ "UPDATE #{parent_table}
331
+ SET #{ parent_columns.collect{ |col| col + "= NEW." + col }.join(", ") }
332
+ WHERE #{parent_pk} = OLD.#{parent_pk};"}
333
+ #{ child_columns.empty? ? '':
334
+ "UPDATE #{child_table}
335
+ SET #{ child_columns.collect{ |col| col + " = NEW." + col }.join(", ") }
336
+ WHERE #{child_pk} = OLD.#{parent_pk}"
337
+ }
338
+ )
339
+ end_sql
340
+ end
341
+
342
+ def insert_returning_clause(parent_pk, child_pk, child_view)
343
+ columns_cast_to_null = columns(child_view)
344
+ .reject { |c| c.name == parent_pk}
345
+ .map { |c| "CAST (NULL AS #{c.sql_type})" }
346
+ .join(", ")
347
+ "RETURNING #{child_pk}, #{columns_cast_to_null}"
348
+ end
349
+
350
+ # Set default values from the table columns for a view
351
+ def set_defaults(view_name, table_name)
352
+ column_definitions(table_name).each do |column_name, type, default, notnull|
353
+ if !default.nil?
354
+ execute("ALTER TABLE #{quote_table_name(view_name)} ALTER #{quote_column_name(column_name)} SET DEFAULT #{default}")
355
+ end
356
+ end
368
357
  end
369
- end
370
358
 
371
- def parent_table(relation)
372
- if table_exists?('updateable_views_inheritance')
373
- res = query(<<-end_sql, 'Parent relation')[0]
374
- SELECT parent_relation
359
+ def create_system_table_records(parent_relation, child_aggregate_view, child_relation)
360
+ parent_relation, child_aggregate_view, child_relation = [parent_relation, child_aggregate_view, child_relation].collect{|rel| quote(rel.to_s)}
361
+ exists = query <<-end_sql
362
+ SELECT parent_relation, child_aggregate_view, child_relation
375
363
  FROM updateable_views_inheritance
376
- WHERE child_aggregate_view = '#{relation}'
364
+ WHERE parent_relation = #{parent_relation}
365
+ AND child_aggregate_view = #{child_aggregate_view}
366
+ AND child_relation = #{child_relation}
377
367
  end_sql
378
- res[0] if res
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
379
373
  end
380
- end
381
374
 
382
- # Single Table Inheritance Aggregate View
383
-
384
- # Nested list for the +parent_relation+ inheritance hierarchy
385
- # Every descendant relation is presented as an array with relation's name as first element
386
- # and the other elements are the relation's children presented in the same way as lists.
387
- # For example:
388
- # [[child_view1, [grandchild11,[...]], [grandchild12]],
389
- # [child_view2, [...]
390
- # ]
391
- def get_view_hierarchy_for(parent_relation)
392
- hierarchy = []
393
- children = query(<<-end_sql)
394
- SELECT parent_relation, child_aggregate_view, child_relation
395
- FROM updateable_views_inheritance
396
- WHERE parent_relation = '#{parent_relation}'
397
- end_sql
398
- children.each do |child|
399
- hierarchy << [child[1], *get_view_hierarchy_for(child[1])]
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
400
384
  end
401
- hierarchy
402
- end
403
385
 
404
- def get_leaves_relations(hierarchy)
405
- return [] if hierarchy.nil? || hierarchy.empty?
406
- head, hierarchy = hierarchy.first, hierarchy[1..(hierarchy.size)]
407
- if(head.is_a? Array)
408
- return (get_leaves_relations(head) + get_leaves_relations(hierarchy)).compact
409
- elsif(hierarchy.nil? || hierarchy.empty?)
410
- return [head]
411
- else
412
- return get_leaves_relations(hierarchy).compact
386
+ # Single Table Inheritance Aggregate View
387
+
388
+ # Nested list for the +parent_relation+ inheritance hierarchy
389
+ # Every descendant relation is presented as an array with relation's name as first element
390
+ # and the other elements are the relation's children presented in the same way as lists.
391
+ # For example:
392
+ # [[child_view1, [grandchild11,[...]], [grandchild12]],
393
+ # [child_view2, [...]
394
+ # ]
395
+ def get_view_hierarchy_for(parent_relation)
396
+ hierarchy = []
397
+ children = query(<<-end_sql)
398
+ SELECT parent_relation, child_aggregate_view, child_relation
399
+ FROM updateable_views_inheritance
400
+ WHERE parent_relation = '#{parent_relation}'
401
+ end_sql
402
+ children.each do |child|
403
+ hierarchy << [child[1], *get_view_hierarchy_for(child[1])]
404
+ end
405
+ hierarchy
413
406
  end
414
- end
415
407
 
416
- def generate_single_table_inheritanche_union_clause(rel, column_names, conflict_column_names, columns_hash, quoted_inheritance_column)
417
- relation_columns = columns(rel).collect{|c| c.name}
418
- columns_select = column_names.inject([]) do |arr, col_name|
419
- sql_type = conflict_column_names.include?(col_name) ? 'text' : columns_hash[col_name].sql_type
420
- value = "NULL::#{sql_type}"
421
- if(relation_columns.include?(col_name))
422
- value = col_name
423
- value = "#{value}::text" if conflict_column_names.include?(col_name)
408
+ def get_leaves_relations(hierarchy)
409
+ return [] if hierarchy.nil? || hierarchy.empty?
410
+ head, hierarchy = hierarchy.first, hierarchy[1..(hierarchy.size)]
411
+ if(head.is_a? Array)
412
+ return (get_leaves_relations(head) + get_leaves_relations(hierarchy)).compact
413
+ elsif(hierarchy.nil? || hierarchy.empty?)
414
+ return [head]
415
+ else
416
+ return get_leaves_relations(hierarchy).compact
424
417
  end
425
- statement = " AS #{col_name}"
426
- statement = "#{value} #{statement}"
427
- arr << " #{statement}"
428
418
  end
429
- columns_select = columns_select.join(", ")
430
- rel_klass_name = Tutuf::ClassTableReflection.get_klass_for_table(rel)
431
- where_clause = " WHERE #{quoted_inheritance_column} = '#{rel_klass_name}'"
432
- ["SELECT", columns_select, "FROM #{rel} #{where_clause}"].join(" ")
433
- end
419
+
420
+ def generate_single_table_inheritanche_union_clause(rel, column_names, conflict_column_names, columns_hash, quoted_inheritance_column)
421
+ relation_columns = columns(rel).collect{|c| c.name}
422
+ columns_select = column_names.inject([]) do |arr, col_name|
423
+ sql_type = conflict_column_names.include?(col_name) ? 'text' : columns_hash[col_name].sql_type
424
+ value = "NULL::#{sql_type}"
425
+ if(relation_columns.include?(col_name))
426
+ value = col_name
427
+ value = "#{value}::text" if conflict_column_names.include?(col_name)
428
+ end
429
+ statement = " AS #{col_name}"
430
+ statement = "#{value} #{statement}"
431
+ arr << " #{statement}"
432
+ end
433
+ columns_select = columns_select.join(", ")
434
+ rel_klass_name = Tutuf::ClassTableReflection.get_klass_for_table(rel)
435
+ where_clause = " WHERE #{quoted_inheritance_column} = '#{rel_klass_name}'"
436
+ ["SELECT", columns_select, "FROM #{rel} #{where_clause}"].join(" ")
437
+ end
438
+ end
434
439
  end
435
440
  end
436
441
  end
@@ -1,3 +1,3 @@
1
1
  module UpdateableViewsInheritance
2
- VERSION = "1.4.1"
2
+ VERSION = "1.4.2"
3
3
  end
data/test/content_test.rb CHANGED
@@ -3,9 +3,6 @@ require File.join(File.dirname(__FILE__), 'test_helper')
3
3
  class UpdateableViewsInheritanceContentTest < ActiveSupport::TestCase
4
4
  def setup
5
5
  ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 5)
6
- end
7
-
8
- def teardown
9
6
  ActiveRecord::FixtureSet.reset_cache
10
7
  end
11
8
 
@@ -15,7 +15,7 @@ class DeepHierarchyTest < ActiveSupport::TestCase
15
15
  end
16
16
 
17
17
  def test_deeper_hierarchy
18
- assert_equal [["boats"], ["railed_vehicles", ["trains", ["electric_trains", ["maglev_trains"]], ["rack_trains"], ["steam_trains"]]], ["wheeled_vehicles", ["bicycles"], ["cars"]]].sort,
18
+ assert_equal [["boats"], ["railed_vehicles", ["trains", ["steam_trains"], ["rack_trains"], ["electric_trains", ["maglev_trains"]]]], ["wheeled_vehicles", ["bicycles"], ["cars"]]].sort,
19
19
  @connection.send(:get_view_hierarchy_for, :vehicles).sort
20
20
  end
21
21
 
@@ -30,4 +30,6 @@ Dummy::Application.configure do
30
30
  config.active_support.deprecation = :stderr;
31
31
 
32
32
  config.eager_load = false
33
+
34
+ config.active_support.test_order = :random
33
35
  end
@@ -4,4 +4,4 @@
4
4
  # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5
5
 
6
6
  # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7
- # Rails.backtrace_cleaner.remove_silencers!
7
+ Rails.backtrace_cleaner.remove_silencers!
data/test/schema_test.rb CHANGED
@@ -16,7 +16,7 @@ class UpdateableViewsInheritanceSchemaTest < ActiveSupport::TestCase
16
16
 
17
17
 
18
18
  def test_content_columns
19
- assert !SteamLocomotive.content_columns.include?("id")
19
+ assert !SteamLocomotive.content_columns.map(&:name).include?("id")
20
20
  end
21
21
 
22
22
  def test_views
data/test/test_helper.rb CHANGED
@@ -5,10 +5,6 @@ require File.expand_path("../dummy/config/environment.rb", __FILE__)
5
5
  require 'rails/test_help'
6
6
  require 'updateable_views_inheritance'
7
7
 
8
- # get full stack trace on errors
9
- require "minitest/reporters"
10
- Minitest::Reporters.use!
11
-
12
8
  begin
13
9
  if RUBY_VERSION > "2"
14
10
  require 'byebug'
@@ -18,12 +18,11 @@ Gem::Specification.new do |s|
18
18
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
19
19
  s.require_paths = ["lib"]
20
20
 
21
- s.add_dependency "activerecord", ">= 4.0", "< 5"
21
+ s.add_dependency "activerecord", "~> 4.2.8"
22
22
  s.add_dependency "pg"
23
23
 
24
24
  s.add_development_dependency 'minitest'
25
- s.add_development_dependency 'minitest-reporters'
26
- s.add_development_dependency "rails", ' ~> 4.1.16' # ">= 4.0", "< 5"
25
+ s.add_development_dependency "rails", ' ~> 4.2.8'
27
26
  s.add_development_dependency "bundler", "~> 1.3"
28
27
  s.add_development_dependency "rake"
29
28
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: updateable_views_inheritance
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sava Chankov
@@ -9,28 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-03-20 00:00:00.000000000 Z
12
+ date: 2017-03-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - ">="
19
- - !ruby/object:Gem::Version
20
- version: '4.0'
21
- - - "<"
18
+ - - "~>"
22
19
  - !ruby/object:Gem::Version
23
- version: '5'
20
+ version: 4.2.8
24
21
  type: :runtime
25
22
  prerelease: false
26
23
  version_requirements: !ruby/object:Gem::Requirement
27
24
  requirements:
28
- - - ">="
29
- - !ruby/object:Gem::Version
30
- version: '4.0'
31
- - - "<"
25
+ - - "~>"
32
26
  - !ruby/object:Gem::Version
33
- version: '5'
27
+ version: 4.2.8
34
28
  - !ruby/object:Gem::Dependency
35
29
  name: pg
36
30
  requirement: !ruby/object:Gem::Requirement
@@ -59,34 +53,20 @@ dependencies:
59
53
  - - ">="
60
54
  - !ruby/object:Gem::Version
61
55
  version: '0'
62
- - !ruby/object:Gem::Dependency
63
- name: minitest-reporters
64
- requirement: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- type: :development
70
- prerelease: false
71
- version_requirements: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
56
  - !ruby/object:Gem::Dependency
77
57
  name: rails
78
58
  requirement: !ruby/object:Gem::Requirement
79
59
  requirements:
80
60
  - - "~>"
81
61
  - !ruby/object:Gem::Version
82
- version: 4.1.16
62
+ version: 4.2.8
83
63
  type: :development
84
64
  prerelease: false
85
65
  version_requirements: !ruby/object:Gem::Requirement
86
66
  requirements:
87
67
  - - "~>"
88
68
  - !ruby/object:Gem::Version
89
- version: 4.1.16
69
+ version: 4.2.8
90
70
  - !ruby/object:Gem::Dependency
91
71
  name: bundler
92
72
  requirement: !ruby/object:Gem::Requirement