updateable_views_inheritance 1.4.1 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- 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>).