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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -6
  3. data/{LICENSE.txt → LICENSE} +0 -0
  4. data/README.md +47 -10
  5. data/lib/active_record/mti.rb +56 -56
  6. data/lib/active_record/mti/config.rb +26 -0
  7. data/lib/active_record/mti/connection_adapters/postgresql/adapter.rb +0 -8
  8. data/lib/active_record/mti/connection_adapters/postgresql/schema_statements.rb +30 -11
  9. data/lib/active_record/mti/core_extension.rb +170 -0
  10. data/lib/active_record/mti/railtie.rb +9 -4
  11. data/lib/active_record/mti/relation.rb +95 -0
  12. data/lib/active_record/mti/schema_dumper.rb +4 -4
  13. data/lib/active_record/mti/table.rb +19 -0
  14. data/lib/active_record/mti/table_oid.rb +6 -0
  15. data/lib/active_record/mti/version.rb +1 -1
  16. data/lib/core_ext/array.rb +14 -0
  17. data/lib/core_ext/hash.rb +1 -3
  18. data/lib/core_ext/thread.rb +14 -0
  19. metadata +94 -95
  20. data/.gitignore +0 -78
  21. data/.rspec +0 -2
  22. data/.travis.yml +0 -38
  23. data/Gemfile +0 -21
  24. data/Rakefile +0 -6
  25. data/active_record-mti.gemspec +0 -42
  26. data/gemfiles/activerecord-4.0.Gemfile +0 -3
  27. data/gemfiles/activerecord-4.1.Gemfile +0 -3
  28. data/gemfiles/activerecord-4.2.Gemfile +0 -3
  29. data/gemfiles/activerecord-5.0.Gemfile +0 -3
  30. data/gemfiles/activerecord-5.1.Gemfile +0 -3
  31. data/lib/active_record/mti/calculations.rb +0 -23
  32. data/lib/active_record/mti/inheritance.rb +0 -127
  33. data/lib/active_record/mti/model_schema.rb +0 -55
  34. data/lib/active_record/mti/query_methods.rb +0 -40
  35. data/lib/active_record/mti/querying.rb +0 -7
  36. data/lib/active_record/mti/registry.rb +0 -23
  37. data/spec/active_record/mti/calculations_spec.rb +0 -56
  38. data/spec/active_record/mti/inheritance_spec.rb +0 -184
  39. data/spec/active_record/mti/model_schema_spec.rb +0 -11
  40. data/spec/active_record/mti/query_methods_spec.rb +0 -12
  41. data/spec/active_record/mti/schema_dumper_spec.rb +0 -22
  42. data/spec/active_record/mti_spec.rb +0 -24
  43. data/spec/active_record/sti/inheritance_spec.rb +0 -24
  44. data/spec/spec_helper.rb +0 -28
  45. data/spec/support/rails/app/models/admin.rb +0 -3
  46. data/spec/support/rails/app/models/comment.rb +0 -4
  47. data/spec/support/rails/app/models/post.rb +0 -4
  48. data/spec/support/rails/app/models/transportation/military/vehicle.rb +0 -7
  49. data/spec/support/rails/app/models/transportation/truck.rb +0 -5
  50. data/spec/support/rails/app/models/transportation/vehicle.rb +0 -4
  51. data/spec/support/rails/app/models/user.rb +0 -6
  52. data/spec/support/rails/config/database.yml +0 -4
  53. data/spec/support/rails/config/routes.rb +0 -3
  54. data/spec/support/rails/db/schema.rb +0 -51
  55. data/spec/support/rails/log/.gitignore +0 -1
  56. data/spec/support/rails/public/favicon.ico +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 781df2afedf44dd2bdcc4014d8d2e165ebea29eaccea9360573941df5a937ef4
4
- data.tar.gz: 544c4507ae2c1bbc25b59414b97579ac5cfe52b2c449fe7739ee17366bc76866
3
+ metadata.gz: e639bd5df85ba2ea0f83ed1b43b4429dd1a40ee627e2a43e17d329ce64c85486
4
+ data.tar.gz: 7c2103fc368f13f065a2004accfe8f0b83808446c12cc074c30b5bc588bb8f81
5
5
  SHA512:
6
- metadata.gz: e70e8399e5c11b8b8d33fb914debaa072e5e9c7fad8e99aea01385426460a8d461e807be2b7dcea9a8d9aac3108607ad8c3ce17f2bcaacd295ab71b0487451f8
7
- data.tar.gz: f51d31db93ba3c77d026eb673b4315e7e2d927584505eaea7c28e855aaf6978302ad2b05ec9dacd225cd5802bfbe46119033edc70cb7d65018e7c86c7dc12a0a
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.3.2 _(July 15th 2018)_
4
- - 0.3.1 Yanked due to failing specs in spec matrix
5
- - Fixed issue causing fails
6
-
7
- ## 0.3.1 ~_(July 15th 2018)_~ YANKED
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`
File without changes
data/README.md CHANGED
@@ -1,18 +1,22 @@
1
- [![Version ](https://img.shields.io/gem/v/active_record-mti.svg?maxAge=2592000)](https://rubygems.org/gems/active_record-mti)
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
- Compatible with ActiveRecord `4.0`, `4.1`, `4.2`, `5.0`, `5.1`
10
+ # Requirements
12
11
 
13
- Confirmed production use in `4.2`
12
+ - Ruby `2.3`+
13
+ - ActiveRecord `4.2`+
14
14
 
15
- ## Usage
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
- ### Application Code
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
- Note
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/[my-github-username]/active_record-mti/fork )
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`)
@@ -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
- class << self
28
- attr_writer :logger
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
- def logger
31
- @logger ||= Logger.new($stdout).tap do |log|
32
- log.progname = self.name
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.root
39
- @root ||= Pathname.new(File.expand_path('../../', File.dirname(__FILE__)))
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.load
43
- ::ActiveRecord::Base.send :prepend, ModelSchema
44
- ::ActiveRecord::Base.send :prepend, Inheritance
45
- ::ActiveRecord::Relation.send :prepend, QueryMethods
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.testify(value)
53
- value == true || value == 't' || value == 1 || value == '1'
39
+ def self.[](key)
40
+ registry[key]
54
41
  end
55
42
 
56
- private
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
- mattr_accessor :oid_class_candidates
51
+ private
59
52
 
60
- # Cannot assign default inside block because of rails 4.0
61
- self.oid_class_candidates = [
62
- '::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID::Integer', # 4.0, 4.1
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.find_oid_class
68
- oid_class_candidates.find(nil) { |klass|
69
- begin
70
- klass.constantize
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
- self.oid_class = self.find_oid_class
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] = [%Q(INHERITS ("#{inherited_table}")), options[:options]].compact.join
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 %Q(ALTER TABLE "#{table_name}" ADD PRIMARY KEY ("#{inherited_table_primary_key}"))
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.to_h.slice(:unique, :using, :where, :orders)
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, "SCHEMA")
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