11# frozen_string_literal: true
22
33require "monitor"
4+ require "active_record/model_schema/schema"
45
56module ActiveRecord
67 module ModelSchema
@@ -199,6 +200,21 @@ def self.derive_join_table_name(first_table, second_table) # :nodoc:
199200 end
200201
201202 module ClassMethods
203+ def current_schema_context # :nodoc:
204+ connection_db_config . schema_context
205+ rescue ConnectionNotDefined
206+ "default"
207+ end
208+
209+ def model_schema # :nodoc:
210+ context_key = current_schema_context
211+ model_schemas [ context_key ] ||= Schema . new ( self , context_key )
212+ end
213+
214+ def model_schemas # :nodoc:
215+ @model_schemas ||= { }
216+ end
217+
202218 # Guesses the table name (in forced lower-case) based on the name of the class in the
203219 # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
204220 # looks like: Reply < Message < ActiveRecord::Base, then Message is used
@@ -448,29 +464,19 @@ def table_exists?
448464 end
449465
450466 def attributes_builder # :nodoc:
451- @attributes_builder ||= begin
452- defaults = _default_attributes . except ( *( column_names - [ primary_key ] ) )
453- ActiveModel ::AttributeSet ::Builder . new ( attribute_types , defaults )
454- end
467+ model_schema . attributes_builder
455468 end
456469
457470 def columns_hash # :nodoc:
458- load_schema unless @columns_hash
459- @columns_hash
471+ model_schema . columns_hash
460472 end
461473
462474 def columns
463- @columns ||= columns_hash . values . freeze
475+ model_schema . columns
464476 end
465477
466478 def _returning_columns_for_insert ( connection ) # :nodoc:
467- @_returning_columns_for_insert ||= begin
468- auto_populated_columns = columns . filter_map do |c |
469- c . name if connection . return_value_after_insert? ( c )
470- end
471-
472- auto_populated_columns . empty? ? Array ( primary_key ) : auto_populated_columns
473- end
479+ model_schema . _returning_columns_for_insert ( connection )
474480 end
475481
476482 # Returns the column object for the named attribute.
@@ -496,28 +502,22 @@ def column_for_attribute(name)
496502 # Returns a hash where the keys are column names and the values are
497503 # default values when instantiating the Active Record object for this table.
498504 def column_defaults
499- load_schema
500- @column_defaults ||= _default_attributes . deep_dup . to_hash . freeze
505+ model_schema . column_defaults
501506 end
502507
503508 # Returns an array of column names as strings.
504509 def column_names
505- @column_names ||= columns . map ( & :name ) . freeze
510+ model_schema . column_names
506511 end
507512
508513 def symbol_column_to_string ( name_symbol ) # :nodoc:
509- @symbol_column_to_string_name_hash ||= column_names . index_by ( &:to_sym )
510- @symbol_column_to_string_name_hash [ name_symbol ]
514+ model_schema . symbol_column_to_string ( name_symbol )
511515 end
512516
513517 # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
514518 # and columns used for single table inheritance have been removed.
515519 def content_columns
516- @content_columns ||= columns . reject do |c |
517- c . name == primary_key ||
518- c . name == inheritance_column ||
519- c . name . end_with? ( "_id" , "_count" )
520- end . freeze
520+ model_schema . content_columns
521521 end
522522
523523 # Resets all the cached information about columns, which will cause them
@@ -557,14 +557,25 @@ def reset_column_information
557557
558558 # Load the model's schema information either from the schema cache
559559 # or directly from the database.
560+ #
561+ # This separates two concerns:
562+ # 1. Adapter-specific: populating the current Schema context's columns_hash
563+ # and default_attributes (runs per context, since different adapters may
564+ # return different column types).
565+ # 2. Model-specific: the load_schema! super chain (counter_cache, encryption,
566+ # etc.) that mutates model-class-level state (runs once, since it's the
567+ # same regardless of adapter).
560568 def load_schema
561569 return if schema_loaded?
562570 @load_schema_monitor . synchronize do
563571 return if schema_loaded?
564572
565- load_schema!
573+ model_schema . load_schema!
566574
567- @schema_loaded = true
575+ unless @any_schema_loaded
576+ load_schema!
577+ @any_schema_loaded = true
578+ end
568579 rescue
569580 reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
570581 raise
@@ -577,17 +588,13 @@ def initialize_load_schema_monitor
577588 end
578589
579590 def reload_schema_from_cache ( recursive = true )
580- @_returning_columns_for_insert = nil
591+ @any_schema_loaded = false
581592 @arel_table = nil
582- @column_names = nil
583- @symbol_column_to_string_name_hash = nil
584- @content_columns = nil
585- @column_defaults = nil
586- @attributes_builder = nil
587- @columns = nil
588- @columns_hash = nil
589- @schema_loaded = false
590593 @attribute_names = nil
594+
595+ # Reset all Schema instances
596+ model_schemas . each_value ( &:reload_schema_from_cache )
597+
591598 if recursive
592599 subclasses . each do |descendant |
593600 descendant . send ( :reload_schema_from_cache )
@@ -607,23 +614,17 @@ def inherited(child_class)
607614 end
608615
609616 def schema_loaded?
610- @ schema_loaded
617+ model_schema . schema_loaded?
611618 end
612619
613620 def load_schema!
614- unless table_name
615- raise ActiveRecord ::TableNotSpecified , "#{ self } has no table configured. Set one with #{ self } .table_name="
616- end
617-
618- columns_hash = schema_cache . columns_hash ( table_name )
619- if only_columns . present?
620- columns_hash = columns_hash . slice ( *only_columns )
621- elsif ignored_columns . present?
622- columns_hash = columns_hash . except ( *ignored_columns )
623- end
624- @columns_hash = columns_hash . freeze
625-
626- _default_attributes # Precompute to cache DB-dependent attribute types
621+ # Base implementation is a no-op. The adapter-specific schema loading
622+ # (columns_hash, default_attributes) is handled by model_schema.load_schema!
623+ # which is called directly from load_schema before this super chain.
624+ #
625+ # This method exists as the hook point for the super chain — overrides
626+ # in CounterCache, EncryptableRecord, etc. call super and then do
627+ # model-class-level setup that should only happen once.
627628 end
628629
629630 # Guesses the table name, but does not decorate it with prefix and suffix information.
0 commit comments