updateable_views_inheritance 1.4.1 → 1.4.3

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
- SHA1:
3
- metadata.gz: 9fbbb9446777110e2bf865f105afbcb4e745df87
4
- data.tar.gz: c7b1e21e88f857d6faca65194048044683ffb170
2
+ SHA256:
3
+ metadata.gz: d1310b2e6e48b891171acd2c8eaebb3f194ee6cd9fac97ebbaa5a6eca5cc7ceb
4
+ data.tar.gz: 0d240ed0c411840f800e0e01a52e05933c5e64903d4ec648a39279fb358c8c53
5
5
  SHA512:
6
- metadata.gz: b8326aa19c08ce403a26c7598c56bddf80a888c89bbd5707125d0429dbfe07e90c48674571531310ce16b458f44f42dbb64a9f5102a39c6767b0eada3d739dd0
7
- data.tar.gz: e03ca1453d61313e673f5dcf543b39571fa3813a6e34a23f0d925e3238b9194f482840febb0a2812ef90ddc4b5180a723901c020c676e5745ae055d2294c60f0
6
+ metadata.gz: fdddba487660479c4ed5530764be9bee03aa13bea8ff658319d48938c34e8a276c9e5f7ba6d9769111a4c2e93e1d0b35632ccd111811402eb9188fce12e20d56
7
+ data.tar.gz: 4a50e703717eb50314beed8563134dc20cdd430533aead0398c42c7fd2082b63b00375dc0db39b012688d0d6ec6ad3a1b49c7a5b9e2873a90b556bdcada6515c
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
- Gemfile.lock
2
1
  .bundle/
2
+ .byebug_history
3
+ Gemfile.lock
3
4
  html/*
4
5
  pkg/*
5
6
  test/dummy/log/*.log
data/CHANGELOG.md CHANGED
@@ -1,4 +1,19 @@
1
- ## 1.4.0 (2 0 March 2017)
1
+ ## 1.4.3 (01 October 2024)
2
+
3
+ Features:
4
+
5
+ - Add option to disable inheritance instantiation for less
6
+ database hits when loading large object collections from a
7
+ parent class.
8
+
9
+ - Add option to skip creating child table in migrations.
10
+
11
+ ## 1.4.2 (28 March 2017)
12
+
13
+ Upgrade to Rails 4.2
14
+
15
+
16
+ ## 1.4.1 (20 March 2017)
2
17
 
3
18
  Upgrade to Rails 4.1
4
19
 
@@ -10,31 +25,31 @@ Upgrade to Rails 4
10
25
 
11
26
  Features:
12
27
 
13
- - rebuild views in all inheritance chains (must be run when upgrading from <= 1.2.1)
28
+ - Rebuild views in all inheritance chains (must be run when upgrading from <= 1.2.1)
14
29
 
15
30
  ## 1.2.2 (18 August 2015)
16
31
 
17
32
  Bugfixes:
18
33
 
19
- - fixed compatibility with Rails 3.2.19+ and ActiveRecord's prepared statements
34
+ - Fixed compatibility with Rails 3.2.19+ and ActiveRecord's prepared statements
20
35
 
21
36
  ## 1.2.1 (27 August 2014)
22
37
 
23
38
  Bugfixes:
24
39
 
25
- - parent relations can be in a schema
40
+ - Parent relations can be in a schema
26
41
 
27
42
  ## 1.2.0 (27 August 2014)
28
43
 
29
44
  Features:
30
45
 
31
- - support for PostgreSQL schemas
46
+ - Support for PostgreSQL schemas
32
47
 
33
48
  ## 1.1.2 (14 June 2013)
34
49
 
35
50
  Bugfixes:
36
51
 
37
- - fixed generating migration on installation
52
+ - Fixed generating migration on installation
38
53
 
39
54
  Documentation:
40
55
 
@@ -44,17 +59,17 @@ Documentation:
44
59
 
45
60
  Features:
46
61
 
47
- - gemified and released on rubygems.org
62
+ - Gemified and released on rubygems.org
48
63
 
49
64
  ## 1.1.0 (13 June 2013)
50
65
 
51
66
  Features:
52
67
 
53
- - updated for rails 3.2.x
68
+ - Updated for Rails 3.2.x
54
69
 
55
70
  ## 1.0.0 (14 September 2009)
56
71
 
57
72
  Features:
58
73
 
59
74
  - class_table_inheritance plugin has behaved stably in production for a year
60
- - supports rails 2.1, 2.2 and 2.3
75
+ - Supports Rails 2.1, 2.2 and 2.3
data/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # Class Table Inheritance
2
+
3
+ Class Table Inheritance for ActiveRecord using updateable views
4
+
5
+ More about the pattern on
6
+ http://www.martinfowler.com/eaaCatalog/classTableInheritance.html. This gem
7
+ messes very little with Rails inheritance mechanism. Instead it relies on
8
+ updatable views in the database to represent classes in the inheritance chain.
9
+ The approach was [first suggested by John
10
+ Wilger](http://web.archive.org/web/20060408145717/johnwilger.com/articles/2005/09/29/class-table-inheritance-in-rails-with-postgresql).
11
+
12
+
13
+ # Requirements
14
+
15
+ Rails: 4.x
16
+
17
+ Ruby: 1.9.3+
18
+
19
+ Database: PostgreSQL only. Patches for other DBMS are welcome. Note that you are
20
+ not required to use updateable views, children relations can be tables with
21
+ some triggers involved.
22
+
23
+ # Usage
24
+
25
+ ## Setup
26
+
27
+ * Add `gem 'updateable_views_inheritance'` to your `Gemfile`
28
+ * Run `rails generate updateable_views_inheritance:install && rake db:migrate`
29
+ * In `config/environment.rb` set `config.active_record.schema_format = :sql`
30
+
31
+ ## Example
32
+
33
+ The database migration:
34
+
35
+ ```ruby
36
+ class CtiExample < ActiveRecord::Migration
37
+ def self.up
38
+ create_table :locomotives do |t|
39
+ t.column :name, :string
40
+ t.column :max_speed, :integer
41
+ t.column :type, :string
42
+ end
43
+
44
+ create_child(:steam_locomotives, parent: :locomotives) do |t|
45
+ t.decimal :water_consumption, precision: 6, scale: 2
46
+ t.decimal :coal_consumption, precision: 6, scale: 2
47
+ end
48
+
49
+ create_child(:electric_locomotives,
50
+ table: :raw_electric_locomotives,
51
+ parent: :locomotives) do |t|
52
+ t.decimal :electricity_consumption, precision: 6, scale: 2
53
+ end
54
+ end
55
+
56
+ def self.down
57
+ drop_child :steam_locomotives
58
+ drop_child :electric_locomotives
59
+ drop_table :locomotives
60
+ end
61
+ end
62
+ ```
63
+
64
+ And the models:
65
+
66
+ ```ruby
67
+ class Locomotive
68
+ end
69
+
70
+ class SteamLocomotive < Locomotive
71
+ self.table_name = :steam_locomotives
72
+ end
73
+
74
+ class ElectricLocomotive < Locomotive
75
+ self.table_name = :electric_locomotives
76
+ end
77
+ ```
78
+
79
+ Note that models of children classes must specify table name explicitly.
80
+
81
+ ### Changing Columns in Underlying Tables
82
+
83
+ ```ruby
84
+ class RemoveColumnInParentTable < ActiveRecord::Migration
85
+ def self.up
86
+ remove_parent_and_children_views(:locomotives)
87
+ remove_column(:locomotives, :max_speed)
88
+ rename_column(:name, :title)
89
+ rebuild_parent_and_children_views(:locomotives)
90
+ end
91
+ end
92
+ ```
93
+
94
+ ### Renaming Underlying Tables
95
+
96
+ ```ruby
97
+ remove_parent_and_children_views(:old_name)
98
+ rename_table(:old_name,:new_name)
99
+ execute "UPDATE updateable_views_inheritance SET child_aggregate_view = 'new_name' WHERE child_aggregate_view = 'old_name'"
100
+ execute "UPDATE updateable_views_inheritance SET parent_relation = 'new_name' WHERE parent_relation = 'old_name'"
101
+ rebuild_parent_and_children_views(:new_name)
102
+ ```
103
+
104
+ ### Removing Classes
105
+
106
+ Note that you should remove only leaf classes (i.e. those that do not have
107
+ descendants). If you want to erase a whole chain or part of chain you have to
108
+ remove first the leaves and then their ancestors. Use `drop_child(child_view)`
109
+ in migrations.
110
+
111
+ ### Using parent class without instantiating subclass
112
+
113
+ If you don't want to make a second SQL query to the subclass table when you instantiate
114
+ parent class with `Locomotive.find(1)` use
115
+ ```ruby
116
+ class Locomotive
117
+ self.disable_inheritance_instantiation = true
118
+ end
119
+ ```
120
+ Quite handy for flat and wide class hierarchies (one parent class, many subclasses).
121
+
122
+ ### Using existing table for inherited class
123
+
124
+ ```ruby
125
+ class CreateIkarusBus < ActiveRecord::Migration
126
+ def self.up
127
+ # table `tbl_ikarus_buses` exists in the database
128
+ end
129
+ create_child(:ikarus_buses,
130
+ table: :tbl_ikarus_buses,
131
+ parent: :buses,
132
+ skip_creating_child_table: true)
133
+ end
134
+ end
135
+ ```
136
+ Useful when converting legacy DB schema to use inheritance.
137
+
138
+ ## Compatibility with Single Table Inheritance
139
+
140
+ The approach of this gem is completely independent from Rails built-in Single
141
+ Table Inheritance. STI and CLTI can safely be mixed in one inheritance chain.
142
+
143
+ ## Testing Your App
144
+
145
+ If you use fixtures, you must run `rake updateable_views_inheritance:fixture` to
146
+ generate fixture for the updateable_views_inheritance table after you
147
+ add/remove classes from the hierarchy or change underlying table or view names.
148
+ **Without it primary key sequence for inheritors' tables won't be bumped to the
149
+ max and it might not be possible to save objects!** If you don't use fixtures
150
+ for the classes in the hierarchy you don't need to do that.
151
+
152
+ This gem re-enables referential integrity on fixture loading. This means that
153
+ `fixtures :all` may fail when there are foreign key constraints on tables. To
154
+ fix this, explicitly declare fixture load order in `test_helper.rb`:
155
+
156
+ ```
157
+ fixtures :roots, :trunks, :leafs, ...
158
+ ```
159
+ for all fixtures you want to load.
160
+
161
+ ## Gem Development & Testing
162
+
163
+ In order to run gem tests, you have to be a superuser in PostgreSQL.
@@ -1,10 +1,12 @@
1
1
  module ActiveRecord #:nodoc:
2
2
  class Base #:nodoc:
3
3
  class << self
4
+ attr_accessor :disable_inheritance_instantiation
5
+
4
6
  private
5
7
  def instantiate_with_updateable_views_inheritance_support(attributes, column_types = {})
6
8
  object = instantiate_without_updateable_views_inheritance_support(attributes, column_types = {})
7
- if object.class.name == self.name
9
+ if object.class.name == self.name || self.disable_inheritance_instantiation
8
10
  object
9
11
  else
10
12
  object.class.find(attributes.with_indifferent_access[:id])
@@ -1,251 +1,263 @@
1
+ require 'active_record/connection_adapters/postgresql/utils'
2
+
1
3
  module ActiveRecord #:nodoc:
2
4
  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
21
-
22
- child_table = options[:table] || quote_table_name("#{child_view}_data")
23
- child_table_pk = "#{unqualified_child_view_name.singularize}_id"
5
+ module PostgreSQL
6
+ module SchemaStatements
7
+ # Use this in migration to create child table and view.
8
+ # Options:
9
+ # [:parent]
10
+ # parent relation
11
+ # [:table]
12
+ # default is <tt>"#{child_view}_data"</tt>
13
+ # [:skip_creating_child_table]
14
+ # use together with :table option
15
+ def create_child(child_view, options)
16
+ raise 'Please call me with a parent, for example: create_child(:steam_locomotives, :parent => :locomotives)' unless options[:parent]
17
+
18
+ parent_relation = options[:parent].to_s
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
28
+
29
+ child_table = options[:table] || quote_table_name("#{child_view}_data")
30
+
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"
42
+ end
24
43
 
25
- create_table(child_table, :id => false) do |t|
26
- t.integer child_table_pk, :null => false
27
- yield t
44
+ create_child_view(parent_relation, child_view, child_table)
28
45
  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"
32
46
 
33
- create_child_view(parent_relation, child_view, child_table)
34
- end
35
-
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
47
+ # Drop child view and table
48
+ def drop_child(child_view)
49
+ drop_view(child_view)
50
+ child_table = query("SELECT child_relation FROM updateable_views_inheritance WHERE child_aggregate_view = #{quote(child_view)}")[0][0]
51
+ drop_table(child_table)
52
+ execute "DELETE FROM updateable_views_inheritance WHERE child_aggregate_view = #{quote(child_view)}"
53
+ end
43
54
 
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"
55
+ # Creates aggregate updateable view of parent and child relations. The convention for naming child tables is
56
+ # <tt>"#{child_view}_data"</tt>. If you don't follow it, supply +child_table_name+ as third argument.
57
+ def create_child_view(parent_table, child_view, child_table=nil)
58
+ child_table ||= child_view.to_s + "_data"
48
59
 
49
- parent_columns = columns(parent_table)
50
- child_columns = columns(child_table)
60
+ parent_columns = columns(parent_table)
61
+ child_columns = columns(child_table)
51
62
 
52
- child_column_names = child_columns.collect{|c| c.name}
53
- parent_column_names = parent_columns.collect{|c| c.name}
63
+ child_column_names = child_columns.collect{|c| c.name}
64
+ parent_column_names = parent_columns.collect{|c| c.name}
54
65
 
55
- child_pk = pk_and_sequence_for(child_table)[0]
56
- child_column_names.delete(child_pk)
66
+ child_pk = pk_and_sequence_for(child_table)[0]
67
+ child_column_names.delete(child_pk)
57
68
 
58
- parent_pk, parent_pk_seq = pk_and_sequence_for(parent_table)
59
- parent_column_names.delete(parent_pk)
69
+ parent_pk, parent_pk_seq = pk_and_sequence_for(parent_table)
70
+ parent_column_names.delete(parent_pk)
60
71
 
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)
72
+ do_create_child_view(parent_table, parent_column_names, parent_pk, child_view, child_column_names, child_pk, child_table)
73
+ make_child_view_updateable(parent_table, parent_column_names, parent_pk, parent_pk_seq, child_view, child_column_names, child_pk, child_table)
63
74
 
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
75
+ # assign default values for table columns on the view - it is not automatic in Postgresql 8.1
76
+ set_defaults(child_view, parent_table)
77
+ set_defaults(child_view, child_table)
78
+ create_system_table_records(parent_table, child_view, child_table)
79
+ end
69
80
 
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
81
+ # 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).
82
+ def reset_pk_sequence!(table, pk = nil, sequence = nil)
83
+ parent = parent_table(table)
84
+ if parent
85
+ reset_pk_sequence!(parent, pk, sequence)
86
+ else
87
+ unless pk and sequence
88
+ default_pk, default_sequence = pk_and_sequence_for(table)
89
+ pk ||= default_pk
90
+ sequence ||= default_sequence
91
+ end
92
+ if pk
93
+ if sequence
94
+ select_value <<-end_sql, 'Reset sequence'
95
+ SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
96
+ end_sql
97
+ else
98
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
99
+ end
88
100
  end
89
101
  end
90
102
  end
91
- end
92
103
 
93
- def primary_key(relation)
94
- res = pk_and_sequence_for(relation)
95
- res && res.first
96
- end
104
+ def primary_key(relation)
105
+ res = pk_and_sequence_for(relation)
106
+ res && res.first
107
+ end
108
+
109
+ # Returns a relation's primary key and belonging sequence. If +relation+ is a table the result is its PK and sequence.
110
+ # When it is a view, PK and sequence of the table at the root of the inheritance chain are returned.
111
+ def pk_and_sequence_for(relation)
112
+ result = query(<<-end_sql, 'PK')[0]
113
+ SELECT attr.attname
114
+ FROM pg_attribute attr,
115
+ pg_constraint cons
116
+ WHERE cons.conrelid = attr.attrelid
117
+ AND cons.conrelid = '#{relation}'::regclass
118
+ AND cons.contype = 'p'
119
+ AND attr.attnum = ANY(cons.conkey)
120
+ end_sql
121
+ if result.nil? or result.empty?
122
+ parent = parent_table(relation)
123
+ pk_and_sequence_for(parent) if parent
124
+ else
125
+ # log(result[0], "PK for #{relation}") {}
126
+ [result[0], query("SELECT pg_get_serial_sequence('#{relation}', '#{result[0]}') ")[0][0]]
127
+ end
128
+ rescue
129
+ nil
130
+ end
97
131
 
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]]
132
+ # Drops a view from the database.
133
+ def drop_view(name)
134
+ execute "DROP VIEW #{name}"
116
135
  end
117
- rescue
118
- nil
119
- end
120
136
 
121
- # Drops a view from the database.
122
- def drop_view(name)
123
- execute "DROP VIEW #{name}"
124
- end
137
+ # Return the list of all views in the schema search path.
138
+ def views(name=nil)
139
+ schemas = schema_search_path.split(/,\s*/).map { |p| quote(p) }.join(',')
140
+ query(<<~SQL, name).map { |row| row[0] }
141
+ SELECT viewname
142
+ FROM pg_views
143
+ WHERE schemaname IN (#{schemas})
144
+ SQL
145
+ end
125
146
 
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
147
+ # Checks whether relation +name+ is a view.
148
+ def is_view?(name)
149
+ result = query(<<~SQL, name).map { |row| row[0] }
150
+ SELECT viewname
151
+ FROM pg_views
152
+ WHERE viewname = '#{name}'
153
+ SQL
154
+ !result.empty?
155
+ end
135
156
 
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
157
+ # Recursively delete +parent_relation+ (if it is a view) and the children views the depend on it.
158
+ def remove_parent_and_children_views(parent_relation)
159
+ children_views = query(<<-end_sql).map{|row| row[0]}
160
+ SELECT child_aggregate_view
161
+ FROM updateable_views_inheritance
162
+ WHERE parent_relation = '#{parent_relation}'
163
+ end_sql
164
+ children_views.each do |cv|
165
+ remove_parent_and_children_views(cv)
166
+ # drop the view only if it wasn't dropped beforehand in recursive call from other method.
167
+ drop_view(cv) if is_view?(cv)
168
+ end
169
+ drop_view(parent_relation) if is_view?(parent_relation)
170
+ end
145
171
 
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)
172
+ # Recreates all views in all hierarchy chains
173
+ def rebuild_all_parent_and_children_views
174
+ parent_relations = select_values('SELECT DISTINCT parent_relation FROM updateable_views_inheritance')
175
+ parent_relations.each { |parent_relation| rebuild_parent_and_children_views(parent_relation) }
157
176
  end
158
- drop_view(parent_relation) if is_view?(parent_relation)
159
- end
160
177
 
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
178
+ # Recreates views in the part of the hierarchy chain starting from the +parent_relation+.
179
+ def rebuild_parent_and_children_views(parent_relation)
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.
181
+ remove_parent_and_children_views(parent_relation)
182
+ children = query(<<-end_sql)
183
+ SELECT parent_relation, child_aggregate_view, child_relation
184
+ FROM updateable_views_inheritance
185
+ WHERE parent_relation = '#{parent_relation}'
186
+ end_sql
187
+
188
+ #if the parent is in the middle of the inheritance chain, it's a view that should be rebuilt as well
189
+ parent = query(<<-end_sql)[0]
190
+ SELECT parent_relation, child_aggregate_view, child_relation
191
+ FROM updateable_views_inheritance
192
+ WHERE child_aggregate_view = '#{parent_relation}'
193
+ end_sql
194
+ create_child_view(parent[0], parent[1], parent[2]) if (parent && !parent.empty?)
166
195
 
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])
196
+ children.each do |child|
197
+ create_child_view(child[0], child[1], child[2])
198
+ rebuild_parent_and_children_views(child[1])
199
+ end
188
200
  end
189
- end
190
201
 
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
202
+ # Creates Single Table Inheritanche-like aggregate view called +sti_aggregate_view+
203
+ # for +parent_relation+ and all its descendants. <i>The view isn't updateable.</i>
204
+ # The order of all or just the first few columns in the aggregate view can be explicitly set
205
+ # by passing array of column names as third argument.
206
+ # If there are columns with the same name but different types in two or more relations
207
+ # they will appear as a single column of type +text+ in the view.
208
+ def create_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view = nil)
209
+ columns_for_view ||= []
210
+ relations_heirarchy = get_view_hierarchy_for(parent_relation)
211
+ relations = relations_heirarchy.flatten
212
+ leaves_relations = get_leaves_relations(relations_heirarchy)
213
+ all_columns = leaves_relations.map{|rel| columns(rel)}.flatten
214
+ columns_hash = {}
215
+ conflict_column_names = []
216
+ all_columns.each do |col|
217
+ c = columns_hash[col.name]
218
+ if(c && col.sql_type != c.sql_type)
219
+ conflict_column_names << col.name
220
+ else
221
+ columns_hash[col.name] = col
222
+ end
211
223
  end
224
+ conflict_column_names = conflict_column_names.uniq.sort if !conflict_column_names.empty?
225
+ sorted_column_names = (columns_for_view + columns_hash.keys.sort).uniq
226
+ parent_klass_name = Tutuf::ClassTableReflection.get_klass_for_table(parent_relation)
227
+ quoted_inheritance_column = quote_column_name(parent_klass_name.inheritance_column)
228
+ queries = relations.map{|rel| generate_single_table_inheritanche_union_clause(rel, sorted_column_names, conflict_column_names, columns_hash, quoted_inheritance_column)}
229
+ unioin_clauses = queries.join("\n UNION ")
230
+ execute <<-end_sql
231
+ CREATE VIEW #{sti_aggregate_view} AS (
232
+ #{unioin_clauses}
233
+ )
234
+ end_sql
212
235
  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
236
 
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
237
+ # Recreates the Single_Table_Inheritanche-like aggregate view +sti_aggregate_view+
238
+ # for +parent_relation+ and all its descendants.
239
+ def rebuild_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view = nil)
240
+ drop_view(sti_aggregate_view)
241
+ create_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view)
242
+ end
232
243
 
233
- # Overriden - it must return false, otherwise deleting fixtures won't work
234
- def supports_disable_referential_integrity?
235
- false
236
- end
244
+ # Overriden - it solargraph-must return false, otherwise deleting fixtures won't work
245
+ def supports_disable_referential_integrity?
246
+ false
247
+ end
237
248
 
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
249
+ def table_exists_with_updateable_views_inheritance_support?(name)
250
+ is_view?(name) ? true : table_exists_without_updateable_views_inheritance_support?(name)
251
+ end
252
+ alias_method_chain :table_exists?, :updateable_views_inheritance_support
242
253
 
243
- module Tutuf #:nodoc:
244
- class ClassTableReflection
245
- class << self
254
+ module Tutuf #:nodoc:
255
+ class ClassTableReflection
256
+ class << self
246
257
  # Returns all models' class objects that are ActiveRecord::Base descendants
247
258
  def all_db_klasses
248
259
  return @@klasses if defined?(@@klasses)
260
+
249
261
  @@klasses = []
250
262
  # load model classes so that inheritance_column is set correctly where defined
251
263
  model_filenames.collect{|m| load "#{Rails.root}/app/models/#{m}";m.match(%r{([^/]+?)\.rb$})[1].camelize.constantize }.each do |klass|
@@ -263,6 +275,7 @@ module ActiveRecord #:nodoc:
263
275
  # {table_name1 => ClassName1, ...}
264
276
  def klass_for_tables
265
277
  return @@tables_klasses if defined?(@@tables_klasses)
278
+
266
279
  @@tables_klasses = {}
267
280
  all_db_klasses.each do |klass|
268
281
  @@tables_klasses[klass.table_name] = klass if klass.respond_to?(:table_name)
@@ -274,11 +287,20 @@ module ActiveRecord #:nodoc:
274
287
  def model_filenames
275
288
  Dir.chdir("#{Rails.root}/app/models"){ Dir["**/*.rb"] }
276
289
  end
290
+ end
277
291
  end
278
292
  end
279
- end
280
293
 
281
- private
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}")
299
+ end
300
+ end
301
+ end
302
+
303
+ private
282
304
 
283
305
  def do_create_child_view(parent_table, parent_columns, parent_pk, child_view, child_columns, child_pk, child_table)
284
306
  view_columns = parent_columns + child_columns
@@ -307,7 +329,7 @@ module ActiveRecord #:nodoc:
307
329
  INSERT INTO #{child_table}
308
330
  ( #{ [child_pk, child_columns].flatten.join(",")} )
309
331
  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?}
332
+ #{insert_returning_clause(parent_pk, child_pk, child_view)}
311
333
  )
312
334
  end_sql
313
335
 
@@ -324,11 +346,11 @@ module ActiveRecord #:nodoc:
324
346
  ON UPDATE TO #{child_view} DO INSTEAD (
325
347
  #{ parent_columns.empty? ? '':
326
348
  "UPDATE #{parent_table}
327
- SET #{ parent_columns.collect{ |col| col + "= NEW." +col }.join(", ") }
349
+ SET #{ parent_columns.collect{ |col| col + "= NEW." + col }.join(", ") }
328
350
  WHERE #{parent_pk} = OLD.#{parent_pk};"}
329
351
  #{ child_columns.empty? ? '':
330
352
  "UPDATE #{child_table}
331
- SET #{ child_columns.collect{ |col| col + " = NEW." +col }.join(", ") }
353
+ SET #{ child_columns.collect{ |col| col + " = NEW." + col }.join(", ") }
332
354
  WHERE #{child_pk} = OLD.#{parent_pk}"
333
355
  }
334
356
  )
@@ -343,24 +365,15 @@ module ActiveRecord #:nodoc:
343
365
  "RETURNING #{child_pk}, #{columns_cast_to_null}"
344
366
  end
345
367
 
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}")
351
- end
352
- end
353
- end
354
-
355
368
  def create_system_table_records(parent_relation, child_aggregate_view, child_relation)
356
369
  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
370
+ exists = query <<~SQL
358
371
  SELECT parent_relation, child_aggregate_view, child_relation
359
372
  FROM updateable_views_inheritance
360
373
  WHERE parent_relation = #{parent_relation}
361
374
  AND child_aggregate_view = #{child_aggregate_view}
362
375
  AND child_relation = #{child_relation}
363
- end_sql
376
+ SQL
364
377
  # log "res: #{exists}"
365
378
  if exists.nil? or exists.empty?
366
379
  execute "INSERT INTO updateable_views_inheritance (parent_relation, child_aggregate_view, child_relation)" +
@@ -431,6 +444,7 @@ module ActiveRecord #:nodoc:
431
444
  where_clause = " WHERE #{quoted_inheritance_column} = '#{rel_klass_name}'"
432
445
  ["SELECT", columns_select, "FROM #{rel} #{where_clause}"].join(" ")
433
446
  end
447
+ end
434
448
  end
435
449
  end
436
450
  end
@@ -1,3 +1,3 @@
1
1
  module UpdateableViewsInheritance
2
- VERSION = "1.4.1"
2
+ VERSION = "1.4.3"
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
 
File without changes
@@ -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!
@@ -0,0 +1,40 @@
1
+ require File.join(File.dirname(__FILE__), '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
+ end
12
+
13
+ def teardown
14
+ ActiveRecord::FixtureSet.reset_cache
15
+ end
16
+
17
+ class ::Locomotive < ActiveRecord::Base
18
+ self.disable_inheritance_instantiation = true
19
+ end
20
+
21
+ class ::ElectricLocomotive < Locomotive
22
+ self.disable_inheritance_instantiation = false
23
+ end
24
+
25
+ def test_setting_disable_inheritance_instantiation_does_not_load_child_columns
26
+ assert_equal %w[id max_speed name type],
27
+ Locomotive.first.attributes.keys.sort
28
+ end
29
+
30
+ def test_switching_off_disable_inheritance_instantiation_loads_child_columns
31
+ assert_equal %w[electricity_consumption id magnetic_field max_speed name type],
32
+ MaglevLocomotive.first.attributes.keys.sort
33
+ end
34
+
35
+ def test_disable_inheritance_instantiatioon_not_set_loads_child_attributes
36
+ assert_equal %w[id name number_of_gears number_of_wheels vehicle_type],
37
+ Bicycle.first.attributes.keys.sort
38
+
39
+ end
40
+ end
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
@@ -221,4 +221,22 @@ class UpdateableViewsInheritanceSchemaTest < ActiveSupport::TestCase
221
221
  assert @connection.columns(:bicycles).map{ |c| c.name }.include?('wheel_size'),
222
222
  "Newly added column not present in view after rebuild for 2. hierarchy"
223
223
  end
224
+
225
+ class UseExistingTable < ActiveRecord::Migration
226
+ def self.up
227
+ create_table :tbl_diesel_locomotives do |t|
228
+ t.belongs_to :locomotives
229
+ t.integer :num_cylinders
230
+ end
231
+ create_child(:diesel_locomotives,
232
+ table: :tbl_diesel_locomotives,
233
+ parent: :locomotives,
234
+ skip_creating_child_table: true)
235
+ end
236
+ end
237
+
238
+ def test_skip_creating_child_table
239
+ UseExistingTable.up
240
+ assert @connection.columns(:diesel_locomotives).map(&:name).include?("num_cylinders")
241
+ end
224
242
  end
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,13 @@ 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"
22
- s.add_dependency "pg"
21
+ s.add_dependency "activerecord", "~> 4.2.8"
22
+ s.add_dependency "pg", "~> 0.21"
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"
27
- s.add_development_dependency "bundler", "~> 1.3"
25
+ s.add_development_dependency "rails", '= 4.2.11.1'
26
+ s.add_development_dependency "bundler"
28
27
  s.add_development_dependency "rake"
28
+ s.add_development_dependency 'bigdecimal', '1.3.5'
29
+ s.add_development_dependency "solargraph"
29
30
  end
metadata CHANGED
@@ -1,50 +1,44 @@
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.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sava Chankov
8
8
  - Denitsa Belogusheva
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-03-20 00:00:00.000000000 Z
12
+ date: 2024-10-01 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
37
31
  requirements:
38
- - - ">="
32
+ - - "~>"
39
33
  - !ruby/object:Gem::Version
40
- version: '0'
34
+ version: '0.21'
41
35
  type: :runtime
42
36
  prerelease: false
43
37
  version_requirements: !ruby/object:Gem::Requirement
44
38
  requirements:
45
- - - ">="
39
+ - - "~>"
46
40
  - !ruby/object:Gem::Version
47
- version: '0'
41
+ version: '0.21'
48
42
  - !ruby/object:Gem::Dependency
49
43
  name: minitest
50
44
  requirement: !ruby/object:Gem::Requirement
@@ -60,7 +54,21 @@ dependencies:
60
54
  - !ruby/object:Gem::Version
61
55
  version: '0'
62
56
  - !ruby/object:Gem::Dependency
63
- name: minitest-reporters
57
+ name: rails
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '='
61
+ - !ruby/object:Gem::Version
62
+ version: 4.2.11.1
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '='
68
+ - !ruby/object:Gem::Version
69
+ version: 4.2.11.1
70
+ - !ruby/object:Gem::Dependency
71
+ name: bundler
64
72
  requirement: !ruby/object:Gem::Requirement
65
73
  requirements:
66
74
  - - ">="
@@ -74,35 +82,35 @@ dependencies:
74
82
  - !ruby/object:Gem::Version
75
83
  version: '0'
76
84
  - !ruby/object:Gem::Dependency
77
- name: rails
85
+ name: rake
78
86
  requirement: !ruby/object:Gem::Requirement
79
87
  requirements:
80
- - - "~>"
88
+ - - ">="
81
89
  - !ruby/object:Gem::Version
82
- version: 4.1.16
90
+ version: '0'
83
91
  type: :development
84
92
  prerelease: false
85
93
  version_requirements: !ruby/object:Gem::Requirement
86
94
  requirements:
87
- - - "~>"
95
+ - - ">="
88
96
  - !ruby/object:Gem::Version
89
- version: 4.1.16
97
+ version: '0'
90
98
  - !ruby/object:Gem::Dependency
91
- name: bundler
99
+ name: bigdecimal
92
100
  requirement: !ruby/object:Gem::Requirement
93
101
  requirements:
94
- - - "~>"
102
+ - - '='
95
103
  - !ruby/object:Gem::Version
96
- version: '1.3'
104
+ version: 1.3.5
97
105
  type: :development
98
106
  prerelease: false
99
107
  version_requirements: !ruby/object:Gem::Requirement
100
108
  requirements:
101
- - - "~>"
109
+ - - '='
102
110
  - !ruby/object:Gem::Version
103
- version: '1.3'
111
+ version: 1.3.5
104
112
  - !ruby/object:Gem::Dependency
105
- name: rake
113
+ name: solargraph
106
114
  requirement: !ruby/object:Gem::Requirement
107
115
  requirements:
108
116
  - - ">="
@@ -128,7 +136,7 @@ files:
128
136
  - CHANGELOG.md
129
137
  - Gemfile
130
138
  - MIT-LICENSE
131
- - README.rdoc
139
+ - README.md
132
140
  - Rakefile
133
141
  - doc/template/horo.rb
134
142
  - lib/generators/updateable_views_inheritance/install_generator.rb
@@ -141,6 +149,7 @@ files:
141
149
  - test/content_test.rb
142
150
  - test/deep_hierarchy_test.rb
143
151
  - test/dummy/Rakefile
152
+ - test/dummy/app/assets/config/manifest.js
144
153
  - test/dummy/app/assets/javascripts/application.js
145
154
  - test/dummy/app/assets/stylesheets/application.css
146
155
  - test/dummy/app/controllers/application_controller.rb
@@ -205,6 +214,7 @@ files:
205
214
  - test/fixtures/steam_locomotives.yml
206
215
  - test/fixtures/steam_trains.yml
207
216
  - test/install_generator_test.rb
217
+ - test/instantiation_test.rb
208
218
  - test/migration_test.rb
209
219
  - test/pg_insert_returning_with_rules_spec.rb
210
220
  - test/schema_test.rb
@@ -215,7 +225,7 @@ homepage: http://github.com/tutuf/updateable_views_inheritance
215
225
  licenses:
216
226
  - MIT
217
227
  metadata: {}
218
- post_install_message:
228
+ post_install_message:
219
229
  rdoc_options: []
220
230
  require_paths:
221
231
  - lib
@@ -230,15 +240,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
230
240
  - !ruby/object:Gem::Version
231
241
  version: '0'
232
242
  requirements: []
233
- rubyforge_project:
234
- rubygems_version: 2.6.10
235
- signing_key:
243
+ rubygems_version: 3.4.12
244
+ signing_key:
236
245
  specification_version: 4
237
246
  summary: Class table inheritance for ActiveRecord
238
247
  test_files:
239
248
  - test/content_test.rb
240
249
  - test/deep_hierarchy_test.rb
241
250
  - test/dummy/Rakefile
251
+ - test/dummy/app/assets/config/manifest.js
242
252
  - test/dummy/app/assets/javascripts/application.js
243
253
  - test/dummy/app/assets/stylesheets/application.css
244
254
  - test/dummy/app/controllers/application_controller.rb
@@ -303,6 +313,7 @@ test_files:
303
313
  - test/fixtures/steam_locomotives.yml
304
314
  - test/fixtures/steam_trains.yml
305
315
  - test/install_generator_test.rb
316
+ - test/instantiation_test.rb
306
317
  - test/migration_test.rb
307
318
  - test/pg_insert_returning_with_rules_spec.rb
308
319
  - test/schema_test.rb
data/README.rdoc DELETED
@@ -1,121 +0,0 @@
1
- ==Class Table Inheritance
2
-
3
- Class Table Inheritance for ActiveRecord using updateable views.
4
-
5
- More about the pattern on http://www.martinfowler.com/eaaCatalog/classTableInheritance.html. This gem messes very little with Rails inheritance mechanism.
6
- Instead it relies on updatable views in the database to represent classes in the inheritance chain. The approach was {first suggested by John
7
- Wilger}[http://web.archive.org/web/20060408145717/johnwilger.com/articles/2005/09/29/class-table-inheritance-in-rails-with-postgresql].
8
-
9
-
10
- ==Requirements
11
-
12
- Rails: 4.x
13
-
14
- Ruby: 1.9.3+
15
-
16
- Database: PostgreSQL 8.1+ only. Patches for other DBMS are welcome. Note that you are not required to use updateable views, children relations can be tables (with some triggers involved) or materialized views.
17
-
18
- ==Install
19
- Run
20
- gem install updateable_views_inheritance
21
-
22
- ==Usage
23
-
24
- ===Setup
25
-
26
- * In <tt>Gemfile</tt> add <tt>gem 'updateable_views_inheritance'</tt>
27
- * Run <tt>rails generate updateable_views_inheritance:install && rake db:migrate</tt>
28
- * In <tt>config/environment.rb</tt> set <tt>config.active_record.schema_format = :sql</tt>
29
- * In case you're using fixtures, don't forget to run
30
-
31
- rake updateable_views_inheritance:fixture
32
-
33
- after every change to the class hierarchy. Otherwise tests may fail.
34
-
35
- ===Example
36
-
37
- class CtiExample < ActiveRecord::Migration
38
- def self.up
39
- create_table :locomotives do |t|
40
- t.column :name, :string
41
- t.column :max_speed, :integer
42
- t.column :type, :string
43
- end
44
-
45
- create_child(:steam_locomotives, :parent => :locomotives) do |t|
46
- t.decimal :water_consumption, :precision => 6, :scale => 2
47
- t.decimal :coal_consumption, :precision => 6, :scale => 2
48
- end
49
-
50
- create_child(:electric_locomotives, :table => :raw_electric_locomotives, :parent => :locomotives) do |t|
51
- t.decimal :electricity_consumption, :precision => 6, :scale => 2
52
- end
53
- end
54
-
55
- def self.down
56
- drop_child :steam_locomotives
57
- drop_child :electric_locomotives
58
- drop_table :locomotives
59
- end
60
- end
61
-
62
- And the models:
63
- class Locomotive
64
- end
65
-
66
- class SteamLocomotive < Locomotive
67
- self.table_name = :steam_locomotives
68
- end
69
-
70
- class ElectricLocomotive < Locomotive
71
- self.table_name = :electric_locomotives
72
- end
73
-
74
- Note that models of children classes must specify table name explicitly.
75
-
76
- ===Changing Columns in Underlying Tables
77
-
78
- class RemoveColumnInParentTable < ActiveRecord::Migration
79
- def self.up
80
- remove_parent_and_children_views(:locomotives)
81
- remove_column(:locomotives, :max_speed)
82
- rename_column(:name, :title)
83
- rebuild_parent_and_children_views(:locomotives)
84
- end
85
- end
86
-
87
- ===Renaming Underlying Tables
88
-
89
- remove_parent_and_children_views(:old_name)
90
- rename_table(:old_name,:new_name)
91
- execute "UPDATE updateable_views_inheritance SET child_aggregate_view = 'new_name' WHERE child_aggregate_view = 'old_name'"
92
- execute "UPDATE updateable_views_inheritance SET parent_relation = 'new_name' WHERE parent_relation = 'old_name'"
93
- rebuild_parent_and_children_views(:new_name)
94
-
95
- ===Removing Classes
96
-
97
- Note that you should remove only leaf classes (i.e. those that do not have descendants). If you want to erase a whole chain or part of chain you have to remove first the leaves and then their ancestors. Use <tt>drop_child(child_view)</tt> in migrations.
98
-
99
- ==Compatibility with Single Table Inheritance
100
-
101
- The approach of this gem is completely independent from Rails built-in Single Table Inheritance. STI and CLTI can safely be mixed in one inheritance chain.
102
-
103
- ==Testing Your App
104
-
105
- If you use fixtures, you must run <tt>rake updateable_views_inheritance:fixture</tt> to generate fixture for the updateable_views_inheritance table after you add/remove
106
- classes from the hierarchy or change underlying table or view names. <b>Without it primary key sequence for inheritors' tables won't be bumped to the max and it might not be possible to save objects!</b> If you don't use fixtures for the classes in the hierarchy you don't need to do that.
107
-
108
- This gem re-enables referential integrity on fixture loading. This means that
109
-
110
- fixtures :all
111
-
112
- may fail when there are foreign key constraints on tables. To fix this, explicitly declare fixture load order in <tt>test_helper.rb</tt>:
113
-
114
- fixtures :roots, :trunks, :leafs, ...
115
-
116
- for all fixtures you want to load.
117
-
118
- ==Gem Development & Testing
119
-
120
- The gem has a comprehensive test suite. In order to run it, your user must be a superuser in PostgreSQL.
121
- If this is not the case, run <tt>createuser -s pesho</tt> (assuming your Unix account is <tt>pesho</tt>).