active_record-mti 0.3.2 → 0.4.0.pre.1
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 +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
|
-
[](https://rubygems.org/gems/active_record-mti)
|
2
2
|
[](https://travis-ci.org/TwilightCoders/active_record-mti)
|
3
3
|
[](https://codeclimate.com/github/TwilightCoders/active_record-mti/maintainability)
|
4
4
|
[](https://codeclimate.com/github/TwilightCoders/active_record-mti/coverage)
|
5
|
-
[](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
|