active_record-mti 0.3.2 → 0.4.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -6
- data/{LICENSE.txt → LICENSE} +0 -0
- data/README.md +47 -10
- data/lib/active_record/mti.rb +56 -56
- data/lib/active_record/mti/config.rb +26 -0
- data/lib/active_record/mti/connection_adapters/postgresql/adapter.rb +0 -8
- data/lib/active_record/mti/connection_adapters/postgresql/schema_statements.rb +30 -11
- data/lib/active_record/mti/core_extension.rb +170 -0
- data/lib/active_record/mti/railtie.rb +9 -4
- data/lib/active_record/mti/relation.rb +95 -0
- data/lib/active_record/mti/schema_dumper.rb +4 -4
- data/lib/active_record/mti/table.rb +19 -0
- data/lib/active_record/mti/table_oid.rb +6 -0
- data/lib/active_record/mti/version.rb +1 -1
- data/lib/core_ext/array.rb +14 -0
- data/lib/core_ext/hash.rb +1 -3
- data/lib/core_ext/thread.rb +14 -0
- metadata +94 -95
- data/.gitignore +0 -78
- data/.rspec +0 -2
- data/.travis.yml +0 -38
- data/Gemfile +0 -21
- data/Rakefile +0 -6
- data/active_record-mti.gemspec +0 -42
- data/gemfiles/activerecord-4.0.Gemfile +0 -3
- data/gemfiles/activerecord-4.1.Gemfile +0 -3
- data/gemfiles/activerecord-4.2.Gemfile +0 -3
- data/gemfiles/activerecord-5.0.Gemfile +0 -3
- data/gemfiles/activerecord-5.1.Gemfile +0 -3
- data/lib/active_record/mti/calculations.rb +0 -23
- data/lib/active_record/mti/inheritance.rb +0 -127
- data/lib/active_record/mti/model_schema.rb +0 -55
- data/lib/active_record/mti/query_methods.rb +0 -40
- data/lib/active_record/mti/querying.rb +0 -7
- data/lib/active_record/mti/registry.rb +0 -23
- data/spec/active_record/mti/calculations_spec.rb +0 -56
- data/spec/active_record/mti/inheritance_spec.rb +0 -184
- data/spec/active_record/mti/model_schema_spec.rb +0 -11
- data/spec/active_record/mti/query_methods_spec.rb +0 -12
- data/spec/active_record/mti/schema_dumper_spec.rb +0 -22
- data/spec/active_record/mti_spec.rb +0 -24
- data/spec/active_record/sti/inheritance_spec.rb +0 -24
- data/spec/spec_helper.rb +0 -28
- data/spec/support/rails/app/models/admin.rb +0 -3
- data/spec/support/rails/app/models/comment.rb +0 -4
- data/spec/support/rails/app/models/post.rb +0 -4
- data/spec/support/rails/app/models/transportation/military/vehicle.rb +0 -7
- data/spec/support/rails/app/models/transportation/truck.rb +0 -5
- data/spec/support/rails/app/models/transportation/vehicle.rb +0 -4
- data/spec/support/rails/app/models/user.rb +0 -6
- data/spec/support/rails/config/database.yml +0 -4
- data/spec/support/rails/config/routes.rb +0 -3
- data/spec/support/rails/db/schema.rb +0 -51
- data/spec/support/rails/log/.gitignore +0 -1
- data/spec/support/rails/public/favicon.ico +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e639bd5df85ba2ea0f83ed1b43b4429dd1a40ee627e2a43e17d329ce64c85486
|
4
|
+
data.tar.gz: 7c2103fc368f13f065a2004accfe8f0b83808446c12cc074c30b5bc588bb8f81
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b19553d393bf02d1292215d8d36bacdddb8d1f5901e8e0e184a4a3cb0e9b488c8d6f73fe9c827ad306742b60c206f6f55cd87641d455b7937eaaf0a1496905ed
|
7
|
+
data.tar.gz: ab4c52d72f15bef11c1ed261546bd43175a6f7a084e5b29f31c8cdf0bd3d0dd1de2ad8f2e67b5653baff6b8a783b7ce1d290a3400ec192d3623fd7256658d037
|
data/CHANGELOG.md
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
# ActiveRecord::MTI
|
2
2
|
|
3
|
-
## 0.
|
4
|
-
-
|
5
|
-
-
|
6
|
-
|
7
|
-
|
3
|
+
## 0.4.0 _(Unreleased)_
|
4
|
+
- Major overhaul to improve injection hiegine and performance
|
5
|
+
- Refactored to improve inheritance detection at boot
|
6
|
+
- Simplified a lot of the core inheritence logic
|
7
|
+
- Improved Registry of parent/child tables
|
8
|
+
- Removed `uses_mti`
|
9
|
+
|
10
|
+
## 0.3.0 _(Unreleased)_
|
8
11
|
- Greatly improved future-proofing injection strategy.
|
9
12
|
- No longer overwriting (and maintaining) ActiveRecord Calculation sub-routines.
|
10
|
-
- Improve order of grouping and projection so other gems have more information to work with. (Like [`DeletedAt`](https://github.com/TwilightCoders/deleted_at))
|
11
13
|
- Instead of injecting at `build_select`, we're injecting at `build_arel` with one additional new sub-routine (`build_mti`)
|
12
14
|
- `build_mti` sub-routine detects if an MTI projection is needed based on grouping and selecting from query being built.
|
13
15
|
- No longer need to use `uses_mti`
|
data/{LICENSE.txt → LICENSE}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -1,18 +1,22 @@
|
|
1
|
-
[![Version ](https://img.shields.io/gem/v/active_record-mti.svg
|
1
|
+
[![Version ](https://img.shields.io/gem/v/active_record-mti.svg)](https://rubygems.org/gems/active_record-mti)
|
2
2
|
[![Build Status ](https://travis-ci.org/TwilightCoders/active_record-mti.svg)](https://travis-ci.org/TwilightCoders/active_record-mti)
|
3
3
|
[![Code Climate ](https://api.codeclimate.com/v1/badges/27b02e09b5da0a7ed2fc/maintainability)](https://codeclimate.com/github/TwilightCoders/active_record-mti/maintainability)
|
4
4
|
[![Test Coverage](https://codeclimate.com/github/TwilightCoders/active_record-mti/badges/coverage.svg)](https://codeclimate.com/github/TwilightCoders/active_record-mti/coverage)
|
5
|
-
[![Dependencies ](https://gemnasium.com/badges/github.com/TwilightCoders/active_record-mti.svg)](https://gemnasium.com/github.com/TwilightCoders/active_record-mti)
|
6
5
|
|
7
6
|
# ActiveRecord::MTI
|
8
7
|
|
9
8
|
ActiveRecord support for PostgreSQL's native inherited tables (multi-table inheritance)
|
10
9
|
|
11
|
-
|
10
|
+
# Requirements
|
12
11
|
|
13
|
-
|
12
|
+
- Ruby `2.3`+
|
13
|
+
- ActiveRecord `4.2`+
|
14
14
|
|
15
|
-
|
15
|
+
**Confirmed Rails `4.2` use in production**
|
16
|
+
|
17
|
+
_Note: Be sure to check the builds to be sure your version is in-fact supported. The requirements are left unbounded on the upper constraint for posterity, but may not be gaurenteed to work._
|
18
|
+
|
19
|
+
## Installation
|
16
20
|
|
17
21
|
Add this line to your application's Gemfile:
|
18
22
|
|
@@ -26,29 +30,62 @@ Or install it yourself as:
|
|
26
30
|
|
27
31
|
$ gem install active_record-mti
|
28
32
|
|
29
|
-
|
30
|
-
|
31
|
-
In most cases, you shouldn't have to do anything beyond installing the gem. `ActiveRecord::MTI` will do it's best to determine the nature of inheritance in your models. If your models map to their own tables, `ActiveRecord::MTI` will step in and make sure inheritance is treated appropriately. Otherwise it will gracefully aquiece to `ActiveRecord`'s built-in `STI`.
|
33
|
+
## Usage
|
32
34
|
|
33
35
|
```ruby
|
34
36
|
class Account < ::ActiveRecord::Base
|
37
|
+
# table_name is 'accounts'
|
35
38
|
# ...
|
36
39
|
end
|
37
40
|
|
38
41
|
class User < Account
|
42
|
+
# table_name is 'account/users'
|
39
43
|
# ...
|
40
44
|
end
|
41
45
|
|
42
46
|
class Developer < Account
|
47
|
+
# table_name is 'account/developers'
|
48
|
+
# ...
|
49
|
+
end
|
50
|
+
|
51
|
+
class Admin < User
|
52
|
+
self.table_name = 'admins'
|
53
|
+
# ...
|
54
|
+
end
|
55
|
+
|
56
|
+
class Hacker < Developer
|
57
|
+
# table_name is 'account/developer/hackers'
|
43
58
|
# ...
|
44
59
|
end
|
45
60
|
```
|
46
61
|
|
62
|
+
In most cases, you shouldn't have to do anything beyond installing the gem. `ActiveRecord::MTI` will do it's best to determine the nature of inheritance in your models. If your models map to their own tables, `ActiveRecord::MTI` will step in and make sure inheritance is treated appropriately. Otherwise it will gracefully acquiesce to `ActiveRecord`'s built-in `STI`. _(see Table Names section below)_.
|
63
|
+
|
64
|
+
### Queries
|
65
|
+
|
47
66
|
`ActiveRecord` queries work as usual with the following differences:
|
48
67
|
|
49
68
|
- The default query of "\*" is changed to include the OID of each row for subclass discrimination. The default select will be `SELECT "accounts"."tableoid" AS tableoid, "accounts".*` (for example)
|
50
69
|
|
51
|
-
|
70
|
+
### Table Names
|
71
|
+
|
72
|
+
Conventionally—to indicate the nature of inheritance—`ActiveRecord::MTI` expects the `table_name` of a child model to follow the `singular_parent_table_name/plural_child_table_name` pattern. As always, if you need to deviate from this, you can explicitly set the `table_name` as shown below, or configure `ActiveRecord::MTI` using the configure block.
|
73
|
+
|
74
|
+
Note, `ActiveRecord::MTI` will fall back on the unnested `table_name` if it's unable to find the nested form, and short of that, it will use the superclass's `table_name`.
|
75
|
+
|
76
|
+
### Configuration
|
77
|
+
`ActiveRecord::MTI` can be configured using a configure block.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
# config/initializers/active_record_mti.rb
|
81
|
+
|
82
|
+
ActiveRecord::MTI.configure do |config|
|
83
|
+
config.table_name_nesting = true
|
84
|
+
config.nesting_seperator = '/'
|
85
|
+
config.singular_parent = true
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
52
89
|
### Migrations
|
53
90
|
|
54
91
|
In your migrations define a table to inherit from another table:
|
@@ -103,7 +140,7 @@ end
|
|
103
140
|
|
104
141
|
## Contributing
|
105
142
|
|
106
|
-
1. Fork it ( https://github.com/
|
143
|
+
1. Fork it ( https://github.com/TwilightCoders/active_record-mti/fork )
|
107
144
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
108
145
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
109
146
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/lib/active_record/mti.rb
CHANGED
@@ -1,81 +1,81 @@
|
|
1
1
|
require 'active_record/mti/version'
|
2
|
-
|
3
|
-
require 'active_support/all'
|
4
|
-
|
5
|
-
require 'active_record'
|
6
|
-
require 'active_record/connection_handling'
|
7
|
-
|
8
|
-
require 'core_ext/hash'
|
9
|
-
|
10
|
-
require 'active_record/mti/schema_dumper'
|
11
|
-
require 'active_record/mti/registry'
|
12
|
-
require 'active_record/mti/inheritance'
|
13
|
-
require 'active_record/mti/model_schema'
|
14
|
-
require 'active_record/mti/query_methods'
|
15
|
-
require 'active_record/mti/calculations'
|
16
|
-
require 'active_record/mti/connection_adapters/postgresql/schema_statements'
|
17
|
-
require 'active_record/mti/connection_adapters/postgresql/adapter'
|
18
|
-
|
19
2
|
require 'active_record/mti/railtie' if defined?(Rails::Railtie)
|
20
3
|
|
4
|
+
require 'registry'
|
5
|
+
require 'active_record/mti/config'
|
6
|
+
require 'active_record/mti/table'
|
7
|
+
require 'core_ext/thread'
|
8
|
+
|
21
9
|
module ActiveRecord
|
22
10
|
module MTI
|
23
11
|
|
24
12
|
# Rails likes to make breaking changes in it's minor versions (like 4.1 - 4.2) :P
|
25
|
-
mattr_accessor :oid_class
|
26
|
-
|
27
|
-
|
28
|
-
|
13
|
+
mattr_accessor :oid_class do
|
14
|
+
begin
|
15
|
+
ActiveModel::Type::Integer
|
16
|
+
rescue NameError
|
17
|
+
ActiveRecord::Type::Integer
|
18
|
+
end
|
19
|
+
end
|
29
20
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
log.level = Logger::INFO
|
34
|
-
end
|
21
|
+
def self.child_tables
|
22
|
+
@child_tables ||= create_registry(ChildTable, SQL_FOR_CHILD_TABLES).tap do |r|
|
23
|
+
r.index(:name, :oid, :inhparent)
|
35
24
|
end
|
36
25
|
end
|
37
26
|
|
38
|
-
def self.
|
39
|
-
@
|
27
|
+
def self.parent_tables
|
28
|
+
@parent_tables ||= create_registry(ParentTable, SQL_FOR_PARENT_TABLES).tap do |r|
|
29
|
+
r.index(:oid, :name)
|
30
|
+
end
|
40
31
|
end
|
41
32
|
|
42
|
-
def self.
|
43
|
-
::ActiveRecord::Base.
|
44
|
-
|
45
|
-
|
46
|
-
::ActiveRecord::Relation.send :prepend, Calculations
|
47
|
-
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :prepend, ConnectionAdapters::PostgreSQL::Adapter
|
48
|
-
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :prepend, ConnectionAdapters::PostgreSQL::SchemaStatements
|
49
|
-
::ActiveRecord::SchemaDumper.send :prepend, SchemaDumper
|
33
|
+
def self.postgresql_version
|
34
|
+
@postgresql_version ||= Gem::Version.new(ActiveRecord::Base.connection.execute(<<-SQL, 'SCHEMA').to_a.first['server_version'])
|
35
|
+
SHOW server_version;
|
36
|
+
SQL
|
50
37
|
end
|
51
38
|
|
52
|
-
def self.
|
53
|
-
|
39
|
+
def self.[](key)
|
40
|
+
registry[key]
|
54
41
|
end
|
55
42
|
|
56
|
-
|
43
|
+
def self.[]=(key, value)
|
44
|
+
if (self[key] && value != nil)
|
45
|
+
raise "Already assigned"
|
46
|
+
else
|
47
|
+
registry[key]=value
|
48
|
+
end
|
49
|
+
end
|
57
50
|
|
58
|
-
|
51
|
+
private
|
59
52
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
'::ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Integer', # 4.2
|
64
|
-
'::ActiveRecord::Type::Integer' # 5.0, 5.1
|
65
|
-
]
|
53
|
+
def self.registry
|
54
|
+
@registry ||= {}
|
55
|
+
end
|
66
56
|
|
67
|
-
def self.
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
true
|
72
|
-
rescue NameError
|
73
|
-
false
|
74
|
-
end
|
75
|
-
}.constantize
|
57
|
+
def self.create_registry(klass, sql)
|
58
|
+
Registry.new(ActiveRecord::Base.connection.execute(sql).to_a.map do |row|
|
59
|
+
klass.new(*row.values).freeze
|
60
|
+
end)
|
76
61
|
end
|
77
62
|
|
78
|
-
|
63
|
+
ChildTable = Struct.new(:inhrelid, :inhparent, :inhseqno, :oid, :name, :parent_table_name)
|
64
|
+
ParentTable = Struct.new(:oid, :name)
|
65
|
+
|
66
|
+
SQL_FOR_CHILD_TABLES = (<<-SQL).gsub(/\s+/, " ").strip
|
67
|
+
SELECT "pg_inherits".*, "child".oid AS oid, "child".relname AS name, "parent".relname AS parent_table_name
|
68
|
+
FROM "pg_inherits"
|
69
|
+
JOIN "pg_class" AS "child" ON ("child".oid = "pg_inherits".inhrelid)
|
70
|
+
JOIN "pg_class" AS "parent" ON ("parent".oid = "pg_inherits".inhparent)
|
71
|
+
SQL
|
72
|
+
|
73
|
+
SQL_FOR_PARENT_TABLES = (<<-SQL).gsub(/\s+/, " ").strip
|
74
|
+
SELECT DISTINCT("pg_class".oid) AS oid, "pg_class".relname AS name
|
75
|
+
FROM "pg_class", "pg_inherits"
|
76
|
+
WHERE "pg_class".oid = "pg_inherits".inhparent
|
77
|
+
SQL
|
79
78
|
|
79
|
+
private_constant :ChildTable, :ParentTable, :SQL_FOR_CHILD_TABLES, :SQL_FOR_PARENT_TABLES
|
80
80
|
end
|
81
81
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module MTI
|
3
|
+
class << self
|
4
|
+
attr_accessor :configuration
|
5
|
+
end
|
6
|
+
|
7
|
+
DEFAULT_CONFIG = {
|
8
|
+
table_name_nesting: true,
|
9
|
+
nesting_seperator: '/',
|
10
|
+
singular_parent: true,
|
11
|
+
prefix_parent: true,
|
12
|
+
suffix_parent: false,
|
13
|
+
namespace_depth: 0 # -1 for all
|
14
|
+
}
|
15
|
+
|
16
|
+
def self.reset_configuration
|
17
|
+
self.configuration = OpenStruct.new(DEFAULT_CONFIG)
|
18
|
+
end
|
19
|
+
|
20
|
+
self.reset_configuration
|
21
|
+
|
22
|
+
def self.configure
|
23
|
+
yield(configuration)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -3,13 +3,6 @@ module ActiveRecord
|
|
3
3
|
module ConnectionAdapters
|
4
4
|
module PostgreSQL
|
5
5
|
module Adapter
|
6
|
-
|
7
|
-
def version
|
8
|
-
Gem::Version.new exec_query(<<-SQL, 'SCHEMA').rows.first.first
|
9
|
-
SHOW server_version;
|
10
|
-
SQL
|
11
|
-
end
|
12
|
-
|
13
6
|
def column_definitions(table_name) # :nodoc:
|
14
7
|
exec_query(<<-SQL, 'SCHEMA').rows
|
15
8
|
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
@@ -22,7 +15,6 @@ module ActiveRecord
|
|
22
15
|
ORDER BY a.attnum
|
23
16
|
SQL
|
24
17
|
end
|
25
|
-
|
26
18
|
end
|
27
19
|
end
|
28
20
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'active_support/core_ext/hash/slice'
|
1
2
|
require 'active_record/connection_adapters/postgresql_adapter'
|
2
3
|
|
3
4
|
module ActiveRecord
|
@@ -5,6 +6,7 @@ module ActiveRecord
|
|
5
6
|
module ConnectionAdapters
|
6
7
|
module PostgreSQL
|
7
8
|
module SchemaStatements
|
9
|
+
|
8
10
|
# Creates a new table with the name +table_name+. +table_name+ may either
|
9
11
|
# be a String or a Symbol.
|
10
12
|
#
|
@@ -21,29 +23,27 @@ module ActiveRecord
|
|
21
23
|
options.delete(:primary_key)
|
22
24
|
end
|
23
25
|
|
24
|
-
if (schema = options.delete(:schema))
|
25
|
-
# If we specify a schema then we only create it if it doesn't exist
|
26
|
-
# and we only force create it if only the specific schema is in the search path
|
27
|
-
table_name = %Q("#{schema}"."#{table_name}")
|
28
|
-
end
|
29
|
-
|
30
26
|
if (inherited_table = options.delete(:inherits))
|
31
27
|
# options[:options] = options[:options].sub("INHERITS", "() INHERITS") if td.columns.empty?
|
32
|
-
options[:options] = [%
|
28
|
+
options[:options] = [%(INHERITS ("#{inherited_table}")), options[:options]].compact.join
|
33
29
|
end
|
34
30
|
|
35
31
|
results = super(table_name, options)
|
36
32
|
|
37
33
|
if inherited_table
|
38
34
|
inherited_table_primary_key = primary_key(inherited_table)
|
39
|
-
execute %
|
35
|
+
execute %(ALTER TABLE "#{table_name}" ADD PRIMARY KEY ("#{inherited_table_primary_key}"))
|
40
36
|
|
41
37
|
indexes(inherited_table).each do |index|
|
42
|
-
attributes = index
|
38
|
+
attributes = index_attributes(index)
|
43
39
|
|
44
40
|
# Why rails insists on being inconsistant with itself is beyond me.
|
45
41
|
attributes[:order] = attributes.delete(:orders)
|
46
42
|
|
43
|
+
if (index_name = build_index_name(attributes.delete(:name), inherited_table, table_name))
|
44
|
+
attributes[:name] = index_name
|
45
|
+
end
|
46
|
+
|
47
47
|
add_index table_name, index.columns, attributes
|
48
48
|
end
|
49
49
|
end
|
@@ -51,16 +51,35 @@ module ActiveRecord
|
|
51
51
|
results
|
52
52
|
end
|
53
53
|
|
54
|
+
def index_attributes(index)
|
55
|
+
[:unique, :using, :where, :orders, :name].inject({}) do |hash, attribute|
|
56
|
+
hash.tap do |h|
|
57
|
+
h[attribute] = index.send(attribute)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def build_index_name(index_name, inherited_table, table_name)
|
63
|
+
return unless index_name
|
64
|
+
schema_name, index_name = index_name.match(/((?<schema>.*)\.)?(?<index>.*)/).captures
|
65
|
+
if (index_name.match(inherited_table.to_s))
|
66
|
+
index_name.gsub!(inherited_table.to_s, table_name.to_s)
|
67
|
+
else
|
68
|
+
index_name = "#{table_name}/#{index_name}"
|
69
|
+
end
|
70
|
+
[schema_name, index_name].compact.join('.')
|
71
|
+
end
|
72
|
+
|
54
73
|
# Parent of inherited table
|
55
74
|
def parent_tables(table_name)
|
56
|
-
result = exec_query(<<-SQL,
|
75
|
+
result = exec_query(<<-SQL, 'SCHEMA')
|
57
76
|
SELECT pg_namespace.nspname, pg_class.relname
|
58
77
|
FROM pg_catalog.pg_inherits
|
59
78
|
INNER JOIN pg_catalog.pg_class ON (pg_inherits.inhparent = pg_class.oid)
|
60
79
|
INNER JOIN pg_catalog.pg_namespace ON (pg_class.relnamespace = pg_namespace.oid)
|
61
80
|
WHERE inhrelid = '#{table_name}'::regclass
|
62
81
|
SQL
|
63
|
-
result.map{|a| a['relname']}
|
82
|
+
result.map { |a| a['relname'] }
|
64
83
|
end
|
65
84
|
|
66
85
|
def parent_table(table_name)
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/object/blank'
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
|
5
|
+
require 'active_record/mti/relation'
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
module MTI
|
9
|
+
module CoreExtension
|
10
|
+
|
11
|
+
def self.prepended(base)
|
12
|
+
base.singleton_class.prepend(ClassMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods #:nodoc:
|
16
|
+
|
17
|
+
def sti_or_mti?
|
18
|
+
!abstract_class? && self != base_class
|
19
|
+
end
|
20
|
+
|
21
|
+
def mti?
|
22
|
+
!mti_table.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def mti_table
|
26
|
+
reset_mti_table unless defined?(@mti_table)
|
27
|
+
@mti_table
|
28
|
+
end
|
29
|
+
|
30
|
+
def mti_table_name
|
31
|
+
mti_table&.name
|
32
|
+
end
|
33
|
+
|
34
|
+
def reset_mti_information
|
35
|
+
# This might be "dangerous" if other gems have modified them as well.
|
36
|
+
# It might be more prudent to call "inherited" which calls this as a
|
37
|
+
# shared injection point, to play nice with other gems. (DeletedAt?)
|
38
|
+
reinitialize_relation_delegate_cache
|
39
|
+
|
40
|
+
# ActiveRecord::MTI.registry[mti_table&.oid] = self # maybe follow schema_cache pattern for this stuff
|
41
|
+
# connection.mti_cache.clear_table_cache!(table_name)
|
42
|
+
# ActiveRecord::MTI.delete(mti_table.oid)
|
43
|
+
ActiveRecord::MTI[mti_table.oid] = nil if mti?
|
44
|
+
@mti_table = nil
|
45
|
+
@columns_hash&.delete("tableoid")
|
46
|
+
end
|
47
|
+
|
48
|
+
def reset_column_information
|
49
|
+
super.tap do
|
50
|
+
reset_mti_information
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def tableoid?
|
55
|
+
!Thread.currently?(:skip_tableoid_cast) && mti?
|
56
|
+
end
|
57
|
+
|
58
|
+
def tableoid
|
59
|
+
mti_table&.oid
|
60
|
+
end
|
61
|
+
|
62
|
+
def mti_table=(value)
|
63
|
+
# if defined?(@mti_table)
|
64
|
+
# return if value == @mti_table
|
65
|
+
# reset_column_information if connected?
|
66
|
+
# end
|
67
|
+
|
68
|
+
@mti_table = value
|
69
|
+
|
70
|
+
if mti_table
|
71
|
+
self.attribute :tableoid, ActiveRecord::MTI.oid_class.new
|
72
|
+
|
73
|
+
# TODO: Use the list to retrieve ActiveRecord_Relation?
|
74
|
+
ActiveRecord::MTI.registry[mti_table.oid] = self
|
75
|
+
|
76
|
+
@relation_delegate_cache.each do |klass, delegate|
|
77
|
+
delegate.prepend(::ActiveRecord::MTI::Relation)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def table_name=(value)
|
83
|
+
super.tap do
|
84
|
+
reset_mti_table if connected?
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# NOTE: 5.0+ only
|
89
|
+
def load_schema!
|
90
|
+
super.tap do |attributes|
|
91
|
+
add_tableoid_column if mti?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def reset_mti_table
|
96
|
+
mti_table_name = defined?(@table_name) ? @table_name : compute_mti_table_name
|
97
|
+
# mti_table_name = @table_name || compute_mti_table_name
|
98
|
+
self.mti_table = ActiveRecord::MTI::Table.find(self, mti_table_name)
|
99
|
+
end
|
100
|
+
|
101
|
+
def compute_table_name
|
102
|
+
mti_table_name || superclass.mti_table_name || super
|
103
|
+
end
|
104
|
+
|
105
|
+
def compute_mti_table_name
|
106
|
+
# contained = (parent_name || '').split('::').join('/') { |part| part.downcase.singularize }
|
107
|
+
if superclass < ::ActiveRecord::Base && !superclass.abstract_class?
|
108
|
+
contained = superclass.table_name
|
109
|
+
contained = contained.singularize if superclass.pluralize_table_names
|
110
|
+
contained += '/'
|
111
|
+
end
|
112
|
+
"#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}"
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns +true+ if this does not need STI type condition. Returns
|
116
|
+
# +false+ if STI type condition needs to be applied.
|
117
|
+
# def descends_from_active_record?
|
118
|
+
# a = mti?
|
119
|
+
# b = super
|
120
|
+
# c = superclass.respond_to?(:descends_from_active_record?) ? superclass.descends_from_active_record? : true
|
121
|
+
# # !(a || b || c) || !(!b || c) || (a && b && c)
|
122
|
+
# (!a && !b && c) || b
|
123
|
+
# end
|
124
|
+
|
125
|
+
# Called by +instantiate+ to decide which class to use for a new
|
126
|
+
# record instance. For single-table inheritance, we check the record
|
127
|
+
# for a +type+ column and return the corresponding class.
|
128
|
+
def discriminate_class_for_record(record)
|
129
|
+
if (mti_class = ::ActiveRecord::MTI[record.delete('tableoid')])
|
130
|
+
mti_class.discriminate_class_for_record(record)
|
131
|
+
else
|
132
|
+
super
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Type condition only applies if it's STI, otherwise it's
|
137
|
+
# done for free by querying the inherited table in MTI
|
138
|
+
|
139
|
+
protected
|
140
|
+
|
141
|
+
def add_tableoid_column
|
142
|
+
# missing_columns = (attributes.keys - @columns_hash.keys)
|
143
|
+
# [column_name, type, default, notnull, oid, fmod, collation, comment]
|
144
|
+
# field = ["tableoid", "integer", nil, false, 23, -1]
|
145
|
+
# column = connection.send(:new_column_from_field, table_name, field)
|
146
|
+
# Until support for 5.0 is dropped, we need this, because the internal API changed.
|
147
|
+
column = ::ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new(
|
148
|
+
'tableoid',
|
149
|
+
nil,
|
150
|
+
23,
|
151
|
+
false,
|
152
|
+
table_name,
|
153
|
+
nil
|
154
|
+
)
|
155
|
+
|
156
|
+
columns_hash["tableoid"] ||= column
|
157
|
+
end
|
158
|
+
|
159
|
+
def reinitialize_relation_delegate_cache
|
160
|
+
@relation_delegate_cache.each do |klass, delegate|
|
161
|
+
mangled_name = klass.name.gsub("::".freeze, "_".freeze)
|
162
|
+
remove_const(mangled_name)
|
163
|
+
end
|
164
|
+
initialize_relation_delegate_cache
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|