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.
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