updateable_views_inheritance 1.4.2 → 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 +5 -5
- data/.gitignore +2 -1
- data/CHANGELOG.md +18 -8
- data/README.md +163 -0
- data/lib/updateable_views_inheritance/active_record.rb +3 -1
- data/lib/updateable_views_inheritance/postgresql_adapter.rb +188 -179
- data/lib/updateable_views_inheritance/version.rb +1 -1
- data/test/dummy/app/assets/config/manifest.js +0 -0
- data/test/instantiation_test.rb +40 -0
- data/test/schema_test.rb +18 -0
- data/updateable_views_inheritance.gemspec +5 -3
- metadata +51 -20
- data/README.rdoc +0 -121
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d1310b2e6e48b891171acd2c8eaebb3f194ee6cd9fac97ebbaa5a6eca5cc7ceb
|
4
|
+
data.tar.gz: 0d240ed0c411840f800e0e01a52e05933c5e64903d4ec648a39279fb358c8c53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdddba487660479c4ed5530764be9bee03aa13bea8ff658319d48938c34e8a276c9e5f7ba6d9769111a4c2e93e1d0b35632ccd111811402eb9188fce12e20d56
|
7
|
+
data.tar.gz: 4a50e703717eb50314beed8563134dc20cdd430533aead0398c42c7fd2082b63b00375dc0db39b012688d0d6ec6ad3a1b49c7a5b9e2873a90b556bdcada6515c
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
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
|
+
|
1
11
|
## 1.4.2 (28 March 2017)
|
2
12
|
|
3
13
|
Upgrade to Rails 4.2
|
@@ -15,31 +25,31 @@ Upgrade to Rails 4
|
|
15
25
|
|
16
26
|
Features:
|
17
27
|
|
18
|
-
-
|
28
|
+
- Rebuild views in all inheritance chains (must be run when upgrading from <= 1.2.1)
|
19
29
|
|
20
30
|
## 1.2.2 (18 August 2015)
|
21
31
|
|
22
32
|
Bugfixes:
|
23
33
|
|
24
|
-
-
|
34
|
+
- Fixed compatibility with Rails 3.2.19+ and ActiveRecord's prepared statements
|
25
35
|
|
26
36
|
## 1.2.1 (27 August 2014)
|
27
37
|
|
28
38
|
Bugfixes:
|
29
39
|
|
30
|
-
-
|
40
|
+
- Parent relations can be in a schema
|
31
41
|
|
32
42
|
## 1.2.0 (27 August 2014)
|
33
43
|
|
34
44
|
Features:
|
35
45
|
|
36
|
-
-
|
46
|
+
- Support for PostgreSQL schemas
|
37
47
|
|
38
48
|
## 1.1.2 (14 June 2013)
|
39
49
|
|
40
50
|
Bugfixes:
|
41
51
|
|
42
|
-
-
|
52
|
+
- Fixed generating migration on installation
|
43
53
|
|
44
54
|
Documentation:
|
45
55
|
|
@@ -49,17 +59,17 @@ Documentation:
|
|
49
59
|
|
50
60
|
Features:
|
51
61
|
|
52
|
-
-
|
62
|
+
- Gemified and released on rubygems.org
|
53
63
|
|
54
64
|
## 1.1.0 (13 June 2013)
|
55
65
|
|
56
66
|
Features:
|
57
67
|
|
58
|
-
-
|
68
|
+
- Updated for Rails 3.2.x
|
59
69
|
|
60
70
|
## 1.0.0 (14 September 2009)
|
61
71
|
|
62
72
|
Features:
|
63
73
|
|
64
74
|
- class_table_inheritance plugin has behaved stably in production for a year
|
65
|
-
-
|
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,6 +1,5 @@
|
|
1
1
|
require 'active_record/connection_adapters/postgresql/utils'
|
2
2
|
|
3
|
-
|
4
3
|
module ActiveRecord #:nodoc:
|
5
4
|
module ConnectionAdapters #:nodoc:
|
6
5
|
module PostgreSQL
|
@@ -9,30 +8,38 @@ module ActiveRecord #:nodoc:
|
|
9
8
|
# Options:
|
10
9
|
# [:parent]
|
11
10
|
# parent relation
|
12
|
-
# [:
|
11
|
+
# [:table]
|
13
12
|
# default is <tt>"#{child_view}_data"</tt>
|
13
|
+
# [:skip_creating_child_table]
|
14
|
+
# use together with :table option
|
14
15
|
def create_child(child_view, options)
|
15
16
|
raise 'Please call me with a parent, for example: create_child(:steam_locomotives, :parent => :locomotives)' unless options[:parent]
|
16
17
|
|
17
|
-
unqualified_child_view_name = Utils.extract_schema_qualified_name(child_view).identifier
|
18
|
-
|
19
18
|
parent_relation = options[:parent].to_s
|
20
|
-
if is_view?(parent_relation) # interpreted as inheritance chain deeper than two levels
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
parent_table = if is_view?(parent_relation) # interpreted as inheritance chain deeper than two levels
|
20
|
+
query(<<~SQL)[0][0]
|
21
|
+
SELECT child_relation
|
22
|
+
FROM updateable_views_inheritance
|
23
|
+
WHERE child_aggregate_view = #{quote(parent_relation)}
|
24
|
+
SQL
|
25
|
+
else
|
26
|
+
parent_relation
|
27
|
+
end
|
25
28
|
|
26
29
|
child_table = options[:table] || quote_table_name("#{child_view}_data")
|
27
|
-
child_table_pk = "#{unqualified_child_view_name.singularize}_id"
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
31
|
+
unless options.key?(:skip_creating_child_table)
|
32
|
+
unqualified_child_view_name = Utils.extract_schema_qualified_name(child_view).identifier
|
33
|
+
child_table_pk = "#{unqualified_child_view_name.singularize}_id"
|
34
|
+
|
35
|
+
create_table(child_table, :id => false) do |t|
|
36
|
+
t.integer child_table_pk, :null => false
|
37
|
+
yield t
|
38
|
+
end
|
39
|
+
execute "ALTER TABLE #{child_table} ADD PRIMARY KEY (#{child_table_pk})"
|
40
|
+
execute "ALTER TABLE #{child_table} ADD FOREIGN KEY (#{child_table_pk})
|
41
|
+
REFERENCES #{parent_table} ON DELETE CASCADE ON UPDATE CASCADE"
|
32
42
|
end
|
33
|
-
execute "ALTER TABLE #{child_table} ADD PRIMARY KEY (#{child_table_pk})"
|
34
|
-
execute "ALTER TABLE #{child_table} ADD FOREIGN KEY (#{child_table_pk})
|
35
|
-
REFERENCES #{parent_table} ON DELETE CASCADE ON UPDATE CASCADE"
|
36
43
|
|
37
44
|
create_child_view(parent_relation, child_view, child_table)
|
38
45
|
end
|
@@ -130,7 +137,7 @@ module ActiveRecord #:nodoc:
|
|
130
137
|
# Return the list of all views in the schema search path.
|
131
138
|
def views(name=nil)
|
132
139
|
schemas = schema_search_path.split(/,\s*/).map { |p| quote(p) }.join(',')
|
133
|
-
query(
|
140
|
+
query(<<~SQL, name).map { |row| row[0] }
|
134
141
|
SELECT viewname
|
135
142
|
FROM pg_views
|
136
143
|
WHERE schemaname IN (#{schemas})
|
@@ -139,7 +146,7 @@ module ActiveRecord #:nodoc:
|
|
139
146
|
|
140
147
|
# Checks whether relation +name+ is a view.
|
141
148
|
def is_view?(name)
|
142
|
-
result = query(
|
149
|
+
result = query(<<~SQL, name).map { |row| row[0] }
|
143
150
|
SELECT viewname
|
144
151
|
FROM pg_views
|
145
152
|
WHERE viewname = '#{name}'
|
@@ -234,7 +241,7 @@ module ActiveRecord #:nodoc:
|
|
234
241
|
create_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view)
|
235
242
|
end
|
236
243
|
|
237
|
-
# Overriden - it must return false, otherwise deleting fixtures won't work
|
244
|
+
# Overriden - it solargraph-must return false, otherwise deleting fixtures won't work
|
238
245
|
def supports_disable_referential_integrity?
|
239
246
|
false
|
240
247
|
end
|
@@ -247,194 +254,196 @@ module ActiveRecord #:nodoc:
|
|
247
254
|
module Tutuf #:nodoc:
|
248
255
|
class ClassTableReflection
|
249
256
|
class << self
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
@@klasses.uniq
|
257
|
+
# Returns all models' class objects that are ActiveRecord::Base descendants
|
258
|
+
def all_db_klasses
|
259
|
+
return @@klasses if defined?(@@klasses)
|
260
|
+
|
261
|
+
@@klasses = []
|
262
|
+
# load model classes so that inheritance_column is set correctly where defined
|
263
|
+
model_filenames.collect{|m| load "#{Rails.root}/app/models/#{m}";m.match(%r{([^/]+?)\.rb$})[1].camelize.constantize }.each do |klass|
|
264
|
+
@@klasses << klass if klass < ActiveRecord::Base
|
259
265
|
end
|
266
|
+
@@klasses.uniq
|
267
|
+
end
|
260
268
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
269
|
+
# Returns the class object for +table_name+
|
270
|
+
def get_klass_for_table(table_name)
|
271
|
+
klass_for_tables()[table_name.to_s]
|
272
|
+
end
|
265
273
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
@@tables_klasses = {}
|
271
|
-
all_db_klasses.each do |klass|
|
272
|
-
@@tables_klasses[klass.table_name] = klass if klass.respond_to?(:table_name)
|
273
|
-
end
|
274
|
-
@@tables_klasses
|
275
|
-
end
|
274
|
+
# Returns hash with tables and thier corresponding class.
|
275
|
+
# {table_name1 => ClassName1, ...}
|
276
|
+
def klass_for_tables
|
277
|
+
return @@tables_klasses if defined?(@@tables_klasses)
|
276
278
|
|
277
|
-
|
278
|
-
|
279
|
-
|
279
|
+
@@tables_klasses = {}
|
280
|
+
all_db_klasses.each do |klass|
|
281
|
+
@@tables_klasses[klass.table_name] = klass if klass.respond_to?(:table_name)
|
280
282
|
end
|
283
|
+
@@tables_klasses
|
284
|
+
end
|
285
|
+
|
286
|
+
# Returns filenames for models in the current Rails application
|
287
|
+
def model_filenames
|
288
|
+
Dir.chdir("#{Rails.root}/app/models"){ Dir["**/*.rb"] }
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Set default values from the table columns for a view
|
295
|
+
def set_defaults(view_name, table_name)
|
296
|
+
column_definitions(table_name).each do |column_name, type, default, notnull|
|
297
|
+
if !default.nil?
|
298
|
+
execute("ALTER TABLE #{quote_table_name(view_name)} ALTER #{quote_column_name(column_name)} SET DEFAULT #{default}")
|
281
299
|
end
|
282
300
|
end
|
283
301
|
end
|
284
302
|
|
285
303
|
private
|
286
304
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
305
|
+
def do_create_child_view(parent_table, parent_columns, parent_pk, child_view, child_columns, child_pk, child_table)
|
306
|
+
view_columns = parent_columns + child_columns
|
307
|
+
execute <<-end_sql
|
308
|
+
CREATE OR REPLACE VIEW #{child_view} AS (
|
309
|
+
SELECT parent.#{parent_pk},
|
310
|
+
#{ view_columns.join(",") }
|
311
|
+
FROM #{parent_table} parent
|
312
|
+
INNER JOIN #{child_table} child
|
313
|
+
ON ( parent.#{parent_pk}=child.#{child_pk} )
|
314
|
+
)
|
315
|
+
end_sql
|
316
|
+
end
|
299
317
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
318
|
+
# Creates rules for +INSERT+, +UPDATE+ and +DELETE+ on the view
|
319
|
+
def make_child_view_updateable(parent_table, parent_columns, parent_pk, parent_pk_seq, child_view, child_columns, child_pk, child_table)
|
320
|
+
# insert
|
321
|
+
# NEW.#{parent_pk} can be explicitly specified and when it is null every call to it increments the sequence.
|
322
|
+
# Setting the sequence to its value (explicitly supplied or the default) covers both cases.
|
323
|
+
execute <<-end_sql
|
324
|
+
CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_insert")} AS
|
325
|
+
ON INSERT TO #{child_view} DO INSTEAD (
|
326
|
+
INSERT INTO #{parent_table}
|
327
|
+
( #{ [parent_pk, parent_columns].flatten.join(", ") } )
|
328
|
+
VALUES( DEFAULT #{ parent_columns.empty? ? '' : ' ,' + parent_columns.collect{ |col| "NEW." + col}.join(", ") } ) ;
|
329
|
+
INSERT INTO #{child_table}
|
330
|
+
( #{ [child_pk, child_columns].flatten.join(",")} )
|
331
|
+
VALUES( currval('#{parent_pk_seq}') #{ child_columns.empty? ? '' : ' ,' + child_columns.collect{ |col| "NEW." + col}.join(", ") } )
|
332
|
+
#{insert_returning_clause(parent_pk, child_pk, child_view)}
|
333
|
+
)
|
334
|
+
end_sql
|
317
335
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
336
|
+
# delete
|
337
|
+
execute <<-end_sql
|
338
|
+
CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_delete")} AS
|
339
|
+
ON DELETE TO #{child_view} DO INSTEAD
|
340
|
+
DELETE FROM #{parent_table} WHERE #{parent_pk} = OLD.#{parent_pk}
|
341
|
+
end_sql
|
324
342
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
343
|
+
# update
|
344
|
+
execute <<-end_sql
|
345
|
+
CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_update")} AS
|
346
|
+
ON UPDATE TO #{child_view} DO INSTEAD (
|
347
|
+
#{ parent_columns.empty? ? '':
|
348
|
+
"UPDATE #{parent_table}
|
349
|
+
SET #{ parent_columns.collect{ |col| col + "= NEW." + col }.join(", ") }
|
350
|
+
WHERE #{parent_pk} = OLD.#{parent_pk};"}
|
351
|
+
#{ child_columns.empty? ? '':
|
352
|
+
"UPDATE #{child_table}
|
353
|
+
SET #{ child_columns.collect{ |col| col + " = NEW." + col }.join(", ") }
|
354
|
+
WHERE #{child_pk} = OLD.#{parent_pk}"
|
355
|
+
}
|
356
|
+
)
|
357
|
+
end_sql
|
358
|
+
end
|
341
359
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
360
|
+
def insert_returning_clause(parent_pk, child_pk, child_view)
|
361
|
+
columns_cast_to_null = columns(child_view)
|
362
|
+
.reject { |c| c.name == parent_pk}
|
363
|
+
.map { |c| "CAST (NULL AS #{c.sql_type})" }
|
364
|
+
.join(", ")
|
365
|
+
"RETURNING #{child_pk}, #{columns_cast_to_null}"
|
366
|
+
end
|
349
367
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
368
|
+
def create_system_table_records(parent_relation, child_aggregate_view, child_relation)
|
369
|
+
parent_relation, child_aggregate_view, child_relation = [parent_relation, child_aggregate_view, child_relation].collect{|rel| quote(rel.to_s)}
|
370
|
+
exists = query <<~SQL
|
371
|
+
SELECT parent_relation, child_aggregate_view, child_relation
|
372
|
+
FROM updateable_views_inheritance
|
373
|
+
WHERE parent_relation = #{parent_relation}
|
374
|
+
AND child_aggregate_view = #{child_aggregate_view}
|
375
|
+
AND child_relation = #{child_relation}
|
376
|
+
SQL
|
377
|
+
# log "res: #{exists}"
|
378
|
+
if exists.nil? or exists.empty?
|
379
|
+
execute "INSERT INTO updateable_views_inheritance (parent_relation, child_aggregate_view, child_relation)" +
|
380
|
+
"VALUES( #{parent_relation}, #{child_aggregate_view}, #{child_relation} )"
|
357
381
|
end
|
382
|
+
end
|
358
383
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
SELECT parent_relation
|
384
|
+
def parent_table(relation)
|
385
|
+
if table_exists?('updateable_views_inheritance')
|
386
|
+
res = query(<<-end_sql, 'Parent relation')[0]
|
387
|
+
SELECT parent_relation
|
363
388
|
FROM updateable_views_inheritance
|
364
|
-
WHERE
|
365
|
-
AND child_aggregate_view = #{child_aggregate_view}
|
366
|
-
AND child_relation = #{child_relation}
|
389
|
+
WHERE child_aggregate_view = '#{relation}'
|
367
390
|
end_sql
|
368
|
-
|
369
|
-
if exists.nil? or exists.empty?
|
370
|
-
execute "INSERT INTO updateable_views_inheritance (parent_relation, child_aggregate_view, child_relation)" +
|
371
|
-
"VALUES( #{parent_relation}, #{child_aggregate_view}, #{child_relation} )"
|
372
|
-
end
|
373
|
-
end
|
374
|
-
|
375
|
-
def parent_table(relation)
|
376
|
-
if table_exists?('updateable_views_inheritance')
|
377
|
-
res = query(<<-end_sql, 'Parent relation')[0]
|
378
|
-
SELECT parent_relation
|
379
|
-
FROM updateable_views_inheritance
|
380
|
-
WHERE child_aggregate_view = '#{relation}'
|
381
|
-
end_sql
|
382
|
-
res[0] if res
|
383
|
-
end
|
391
|
+
res[0] if res
|
384
392
|
end
|
393
|
+
end
|
385
394
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
end
|
405
|
-
hierarchy
|
395
|
+
# Single Table Inheritance Aggregate View
|
396
|
+
|
397
|
+
# Nested list for the +parent_relation+ inheritance hierarchy
|
398
|
+
# Every descendant relation is presented as an array with relation's name as first element
|
399
|
+
# and the other elements are the relation's children presented in the same way as lists.
|
400
|
+
# For example:
|
401
|
+
# [[child_view1, [grandchild11,[...]], [grandchild12]],
|
402
|
+
# [child_view2, [...]
|
403
|
+
# ]
|
404
|
+
def get_view_hierarchy_for(parent_relation)
|
405
|
+
hierarchy = []
|
406
|
+
children = query(<<-end_sql)
|
407
|
+
SELECT parent_relation, child_aggregate_view, child_relation
|
408
|
+
FROM updateable_views_inheritance
|
409
|
+
WHERE parent_relation = '#{parent_relation}'
|
410
|
+
end_sql
|
411
|
+
children.each do |child|
|
412
|
+
hierarchy << [child[1], *get_view_hierarchy_for(child[1])]
|
406
413
|
end
|
414
|
+
hierarchy
|
415
|
+
end
|
407
416
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
end
|
417
|
+
def get_leaves_relations(hierarchy)
|
418
|
+
return [] if hierarchy.nil? || hierarchy.empty?
|
419
|
+
head, hierarchy = hierarchy.first, hierarchy[1..(hierarchy.size)]
|
420
|
+
if(head.is_a? Array)
|
421
|
+
return (get_leaves_relations(head) + get_leaves_relations(hierarchy)).compact
|
422
|
+
elsif(hierarchy.nil? || hierarchy.empty?)
|
423
|
+
return [head]
|
424
|
+
else
|
425
|
+
return get_leaves_relations(hierarchy).compact
|
418
426
|
end
|
427
|
+
end
|
419
428
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
end
|
429
|
-
statement = " AS #{col_name}"
|
430
|
-
statement = "#{value} #{statement}"
|
431
|
-
arr << " #{statement}"
|
429
|
+
def generate_single_table_inheritanche_union_clause(rel, column_names, conflict_column_names, columns_hash, quoted_inheritance_column)
|
430
|
+
relation_columns = columns(rel).collect{|c| c.name}
|
431
|
+
columns_select = column_names.inject([]) do |arr, col_name|
|
432
|
+
sql_type = conflict_column_names.include?(col_name) ? 'text' : columns_hash[col_name].sql_type
|
433
|
+
value = "NULL::#{sql_type}"
|
434
|
+
if(relation_columns.include?(col_name))
|
435
|
+
value = col_name
|
436
|
+
value = "#{value}::text" if conflict_column_names.include?(col_name)
|
432
437
|
end
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
["SELECT", columns_select, "FROM #{rel} #{where_clause}"].join(" ")
|
438
|
+
statement = " AS #{col_name}"
|
439
|
+
statement = "#{value} #{statement}"
|
440
|
+
arr << " #{statement}"
|
437
441
|
end
|
442
|
+
columns_select = columns_select.join(", ")
|
443
|
+
rel_klass_name = Tutuf::ClassTableReflection.get_klass_for_table(rel)
|
444
|
+
where_clause = " WHERE #{quoted_inheritance_column} = '#{rel_klass_name}'"
|
445
|
+
["SELECT", columns_select, "FROM #{rel} #{where_clause}"].join(" ")
|
446
|
+
end
|
438
447
|
end
|
439
448
|
end
|
440
449
|
end
|
File without changes
|
@@ -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
@@ -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
|
@@ -19,10 +19,12 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
21
|
s.add_dependency "activerecord", "~> 4.2.8"
|
22
|
-
s.add_dependency "pg"
|
22
|
+
s.add_dependency "pg", "~> 0.21"
|
23
23
|
|
24
24
|
s.add_development_dependency 'minitest'
|
25
|
-
s.add_development_dependency "rails", '
|
26
|
-
s.add_development_dependency "bundler"
|
25
|
+
s.add_development_dependency "rails", '= 4.2.11.1'
|
26
|
+
s.add_development_dependency "bundler"
|
27
27
|
s.add_development_dependency "rake"
|
28
|
+
s.add_development_dependency 'bigdecimal', '1.3.5'
|
29
|
+
s.add_development_dependency "solargraph"
|
28
30
|
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: updateable_views_inheritance
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.4.
|
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:
|
12
|
+
date: 2024-10-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -29,16 +29,16 @@ dependencies:
|
|
29
29
|
name: pg
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
|
-
- - "
|
32
|
+
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '0'
|
34
|
+
version: '0.21'
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
|
-
- - "
|
39
|
+
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: '0'
|
41
|
+
version: '0.21'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: minitest
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -57,30 +57,30 @@ dependencies:
|
|
57
57
|
name: rails
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
|
-
- -
|
60
|
+
- - '='
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: 4.2.
|
62
|
+
version: 4.2.11.1
|
63
63
|
type: :development
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
|
-
- -
|
67
|
+
- - '='
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: 4.2.
|
69
|
+
version: 4.2.11.1
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: bundler
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
73
73
|
requirements:
|
74
|
-
- - "
|
74
|
+
- - ">="
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: '
|
76
|
+
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
|
-
- - "
|
81
|
+
- - ">="
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version: '
|
83
|
+
version: '0'
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
85
|
name: rake
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,6 +95,34 @@ dependencies:
|
|
95
95
|
- - ">="
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: bigdecimal
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - '='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 1.3.5
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - '='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: 1.3.5
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: solargraph
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
98
126
|
description: Class table inheritance for ActiveRecord based on updatable views in
|
99
127
|
the database that join parent and children tables
|
100
128
|
email:
|
@@ -108,7 +136,7 @@ files:
|
|
108
136
|
- CHANGELOG.md
|
109
137
|
- Gemfile
|
110
138
|
- MIT-LICENSE
|
111
|
-
- README.
|
139
|
+
- README.md
|
112
140
|
- Rakefile
|
113
141
|
- doc/template/horo.rb
|
114
142
|
- lib/generators/updateable_views_inheritance/install_generator.rb
|
@@ -121,6 +149,7 @@ files:
|
|
121
149
|
- test/content_test.rb
|
122
150
|
- test/deep_hierarchy_test.rb
|
123
151
|
- test/dummy/Rakefile
|
152
|
+
- test/dummy/app/assets/config/manifest.js
|
124
153
|
- test/dummy/app/assets/javascripts/application.js
|
125
154
|
- test/dummy/app/assets/stylesheets/application.css
|
126
155
|
- test/dummy/app/controllers/application_controller.rb
|
@@ -185,6 +214,7 @@ files:
|
|
185
214
|
- test/fixtures/steam_locomotives.yml
|
186
215
|
- test/fixtures/steam_trains.yml
|
187
216
|
- test/install_generator_test.rb
|
217
|
+
- test/instantiation_test.rb
|
188
218
|
- test/migration_test.rb
|
189
219
|
- test/pg_insert_returning_with_rules_spec.rb
|
190
220
|
- test/schema_test.rb
|
@@ -195,7 +225,7 @@ homepage: http://github.com/tutuf/updateable_views_inheritance
|
|
195
225
|
licenses:
|
196
226
|
- MIT
|
197
227
|
metadata: {}
|
198
|
-
post_install_message:
|
228
|
+
post_install_message:
|
199
229
|
rdoc_options: []
|
200
230
|
require_paths:
|
201
231
|
- lib
|
@@ -210,15 +240,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
210
240
|
- !ruby/object:Gem::Version
|
211
241
|
version: '0'
|
212
242
|
requirements: []
|
213
|
-
|
214
|
-
|
215
|
-
signing_key:
|
243
|
+
rubygems_version: 3.4.12
|
244
|
+
signing_key:
|
216
245
|
specification_version: 4
|
217
246
|
summary: Class table inheritance for ActiveRecord
|
218
247
|
test_files:
|
219
248
|
- test/content_test.rb
|
220
249
|
- test/deep_hierarchy_test.rb
|
221
250
|
- test/dummy/Rakefile
|
251
|
+
- test/dummy/app/assets/config/manifest.js
|
222
252
|
- test/dummy/app/assets/javascripts/application.js
|
223
253
|
- test/dummy/app/assets/stylesheets/application.css
|
224
254
|
- test/dummy/app/controllers/application_controller.rb
|
@@ -283,6 +313,7 @@ test_files:
|
|
283
313
|
- test/fixtures/steam_locomotives.yml
|
284
314
|
- test/fixtures/steam_trains.yml
|
285
315
|
- test/install_generator_test.rb
|
316
|
+
- test/instantiation_test.rb
|
286
317
|
- test/migration_test.rb
|
287
318
|
- test/pg_insert_returning_with_rules_spec.rb
|
288
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>).
|