updateable_views_inheritance 1.4.1 → 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
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