method
inheritance_column=
rails latest stable - Class:
ActiveRecord::ModelSchema
inheritance_column=public
Defines the name of the table column which will store the class name on single-table inheritance situations.
# File activerecord/lib/active_record/model_schema.rb, line 157
included do
class_attribute :primary_key_prefix_type, instance_writer: false
class_attribute :table_name_prefix, instance_writer: false, default: ""
class_attribute :table_name_suffix, instance_writer: false, default: ""
class_attribute :schema_migrations_table_name, instance_accessor: false, default: "schema_migrations"
class_attribute :internal_metadata_table_name, instance_accessor: false, default: "ar_internal_metadata"
class_attribute :pluralize_table_names, instance_writer: false, default: true
class_attribute :implicit_order_column, instance_accessor: false
class_attribute :immutable_strings_by_default, instance_accessor: false
class_attribute :inheritance_column, instance_accessor: false, default: "type"
singleton_class.class_eval do
alias_method :_inheritance_column=, :inheritance_column=
private :_inheritance_column=
alias_method :inheritance_column=, :real_inheritance_column=
end
self.protected_environments = ["production"]
self.ignored_columns = [].freeze
delegate :type_for_attribute, :column_for_attribute, to: :class
initialize_load_schema_monitor
end
# Derives the join table name for +first_table+ and +second_table+. The
# table names appear in alphabetical order. A common prefix is removed
# (useful for namespaced models like Music::Artist and Music::Record):
#
# artists, records => artists_records
# records, artists => artists_records
# music_artists, music_records => music_artists_records
# music.artists, music.records => music.artists_records
def self.derive_join_table_name(first_table, second_table) # :nodoc:
[first_table.to_s, second_table.to_s].sort.join("\00"").gsub(/^(.*[_.])(.+)\00\\11((.+)/, '\1\2_\3').tr("\00"", "_")
end
module ClassMethods
# Guesses the table name (in forced lower-case) based on the name of the class in the
# inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
# looks like: Reply < Message < ActiveRecord::Base, then Message is used
# to guess the table name even when called on Reply. The rules used to do the guess
# are handled by the Inflector class in Active Support, which knows almost all common
# English inflections. You can add new inflections in config/initializers/inflections.rb.
#
# Nested classes are given table names prefixed by the singular form of
# the parent's table name. Enclosing modules are not considered.
#
# ==== Examples
#
# class Invoice < ActiveRecord::Base
# end
#
# file class table_name
# invoice.rb Invoice invoices
#
# class Invoice < ActiveRecord::Base
# class Lineitem < ActiveRecord::Base
# end
# end
#
# file class table_name
# invoice.rb Invoice::Lineitem invoice_lineitems
#
# module Invoice
# class Lineitem < ActiveRecord::Base
# end
# end
#
# file class table_name
# invoice/lineitem.rb Invoice::Lineitem lineitems
#
# Additionally, the class-level +table_name_prefix+ is prepended and the
# +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
# the table name guess for an Invoice class becomes "myapp_invoices".
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
#
# Active Model Naming's +model_name+ is the base name used to guess the
# table name. In case a custom Active Model Name is defined, it will be
# used for the table name as well:
#
# class PostRecord < ActiveRecord::Base
# class << self
# def model_name
# ActiveModel::Name.new(self, nil, "Post")
# end
# end
# end
#
# PostRecord.table_name
# # => "posts"
#
# You can also set your own table name explicitly:
#
# class Mouse < ActiveRecord::Base
# self.table_name = "mice"
# end
def table_name
reset_table_name unless defined?(@table_name)
@table_name
end
# Sets the table name explicitly. Example:
#
# class Project < ActiveRecord::Base
# self.table_name = "project"
# end
def table_name=(value)
value = value && value.to_s
if defined?(@table_name)
return if value == @table_name
reset_column_information if connected?
end
@table_name = value
@quoted_table_name = nil
@arel_table = nil
@sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
@predicate_builder = nil
end
# Returns a quoted version of the table name, used to construct SQL statements.
def quoted_table_name
@quoted_table_name ||= connection.quote_table_name(table_name)
end
# Computes the table name, (re)sets it internally, and returns it.
def reset_table_name # :nodoc:
self.table_name = if self == Base
nil
elsif abstract_class?
superclass.table_name
elsif superclass.abstract_class?
superclass.table_name || compute_table_name
else
compute_table_name
end
end
def full_table_name_prefix # :nodoc:
(module_parents.detect { |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
end
def full_table_name_suffix # :nodoc:
(module_parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
end
# The array of names of environments where destructive actions should be prohibited. By default,
# the value is <tt>["production"]</tt>.
def protected_environments
if defined?(@protected_environments)
@protected_environments
else
superclass.protected_environments
end
end
# Sets an array of names of environments where destructive actions should be prohibited.
def protected_environments=(environments)
@protected_environments = environments.map(&:to_s)
end
def real_inheritance_column=(value) # :nodoc:
self._inheritance_column = value.to_s
end
# The list of columns names the model should ignore. Ignored columns won't have attribute
# accessors defined, and won't be referenced in SQL queries.
def ignored_columns
@ignored_columns || superclass.ignored_columns
end
# Sets the columns names the model should ignore. Ignored columns won't have attribute
# accessors defined, and won't be referenced in SQL queries.
#
# A common usage pattern for this method is to ensure all references to an attribute
# have been removed and deployed, before a migration to drop the column from the database
# has been deployed and run. Using this two step approach to dropping columns ensures there
# is no code that raises errors due to having a cached schema in memory at the time the
# schema migration is run.
#
# For example, given a model where you want to drop the "category" attribute, first mark it
# as ignored:
#
# class Project < ActiveRecord::Base
# # schema:
# # id :bigint
# # name :string, limit: 255
# # category :string, limit: 255
#
# self.ignored_columns += [:category]
# end
#
# The schema still contains "category", but now the model omits it, so any meta-driven code or
# schema caching will not attempt to use the column:
#
# Project.columns_hash["category"] => nil
#
# You will get an error if accessing that attribute directly, so ensure all usages of the
# column are removed (automated tests can help you find any usages).
#
# user = Project.create!(name: "First Project")
# user.category # => raises NoMethodError
def ignored_columns=(columns)
reload_schema_from_cache
@ignored_columns = columns.map(&:to_s).freeze
end
def sequence_name
if base_class?
@sequence_name ||= reset_sequence_name
else
(@sequence_name ||= nil) || base_class.sequence_name
end
end
def reset_sequence_name # :nodoc:
@explicit_sequence_name = false
@sequence_name = connection.default_sequence_name(table_name, primary_key)
end
# Sets the name of the sequence to use when generating ids to the given
# value, or (if the value is +nil+ or +false+) to the value returned by the
# given block. This is required for Oracle and is useful for any
# database which relies on sequences for primary key generation.
#
# If a sequence name is not explicitly set when using Oracle,
# it will default to the commonly used pattern of: #{table_name}_seq
#
# If a sequence name is not explicitly set when using PostgreSQL, it
# will discover the sequence corresponding to your primary key for you.
#
# class Project < ActiveRecord::Base
# self.sequence_name = "projectseq" # default would have been "project_seq"
# end
def sequence_name=(value)
@sequence_name = value.to_s
@explicit_sequence_name = true
end
# Determines if the primary key values should be selected from their
# corresponding sequence before the insert statement.
def prefetch_primary_key?
connection.prefetch_primary_key?(table_name)
end
# Returns the next value that will be used as the primary key on
# an insert statement.
def next_sequence_value
connection.next_sequence_value(sequence_name)
end
# Indicates whether the table associated with this class exists
def table_exists?
connection.schema_cache.data_source_exists?(table_name)
end
def attributes_builder # :nodoc:
unless defined?(@attributes_builder) && @attributes_builder
defaults = _default_attributes.except(*(column_names - [primary_key]))
@attributes_builder = ActiveModel::AttributeSet::Builder.new(attribute_types, defaults)
end
@attributes_builder
end
def columns_hash # :nodoc:
load_schema
@columns_hash
end
def columns
load_schema
@columns ||= columns_hash.values.freeze
end
def _returning_columns_for_insert # :nodoc:
@_returning_columns_for_insert ||= columns.filter_map do |c|
c.name if connection.return_value_after_insert?(c)
end
end
def attribute_types # :nodoc:
load_schema
@attribute_types ||= Hash.new(Type.default_value)
end
def yaml_encoder # :nodoc:
@yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types)
end
# Returns the type of the attribute with the given name, after applying
# all modifiers. This method is the only valid source of information for
# anything related to the types of a model's attributes. This method will
# access the database and load the model's schema if it is required.
#
# The return value of this method will implement the interface described
# by ActiveModel::Type::Value (though the object itself may not subclass
# it).
#
# +attr_name+ The name of the attribute to retrieve the type for. Must be
# a string or a symbol.
def type_for_attribute(attr_name, &block)
attr_name = attr_name.to_s
attr_name = attribute_aliases[attr_name] || attr_name
if block
attribute_types.fetch(attr_name, &block)
else
attribute_types[attr_name]
end
end
# Returns the column object for the named attribute.
# Returns an ActiveRecord::ConnectionAdapters::NullColumn if the
# named attribute does not exist.
#
# class Person < ActiveRecord::Base
# end
#
# person = Person.new
# person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
# # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
#
# person.column_for_attribute(:nothing)
# # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
def column_for_attribute(name)
name = name.to_s
columns_hash.fetch(name) do
ConnectionAdapters::NullColumn.new(name)
end
end
# Returns a hash where the keys are column names and the values are
# default values when instantiating the Active Record object for this table.
def column_defaults
load_schema
@column_defaults ||= _default_attributes.deep_dup.to_hash.freeze
end
def _default_attributes # :nodoc:
load_schema
@default_attributes ||= ActiveModel::AttributeSet.new({})
end
# Returns an array of column names as strings.
def column_names
@column_names ||= columns.map(&:name).freeze
end
def symbol_column_to_string(name_symbol) # :nodoc:
@symbol_column_to_string_name_hash ||= column_names.index_by(&:to_sym)
@symbol_column_to_string_name_hash[name_symbol]
end
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
# and columns used for single table inheritance have been removed.
def content_columns
@content_columns ||= columns.reject do |c|
c.name == primary_key ||
c.name == inheritance_column ||
c.name.end_with?("_id", "_count")
end.freeze
end
# Resets all the cached information about columns, which will cause them
# to be reloaded on the next request.
#
# The most common usage pattern for this method is probably in a migration,
# when just after creating a table you want to populate it with some default
# values, e.g.:
#
# class CreateJobLevels < ActiveRecord::Migration[7.1]
# def up
# create_table :job_levels do |t|
# t.integer :id
# t.string :name
#
# t.timestamps
# end
#
# JobLevel.reset_column_information
# %w{assistant executive manager director}.each do |type|
# JobLevel.create(name: type)
# end
# end
#
# def down
# drop_table :job_levels
# end
# end
def reset_column_information
connection.clear_cache!
([self] + descendants).each(&:undefine_attribute_methods)
connection.schema_cache.clear_data_source_cache!(table_name)
reload_schema_from_cache
initialize_find_by_cache
end
def load_schema # :nodoc:
return if schema_loaded?
@load_schema_monitor.synchronize do
return if @columns_hash
load_schema!
@schema_loaded = true
rescue
reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
raise
end
end
protected
def initialize_load_schema_monitor
@load_schema_monitor = Monitor.new
end
def reload_schema_from_cache(recursive = true)
@_returning_columns_for_insert = nil
@arel_table = nil
@column_names = nil
@symbol_column_to_string_name_hash = nil
@attribute_types = nil
@content_columns = nil
@default_attributes = nil
@column_defaults = nil
@attributes_builder = nil
@columns = nil
@columns_hash = nil
@schema_loaded = false
@attribute_names = nil
@yaml_encoder = nil
if recursive
subclasses.each do |descendant|
descendant.send(:reload_schema_from_cache)
end
end
end
private
def inherited(child_class)
super
child_class.initialize_load_schema_monitor
child_class.reload_schema_from_cache(false)
child_class.class_eval do
@ignored_columns = nil
end
end
def schema_loaded?
defined?(@schema_loaded) && @schema_loaded
end
def load_schema!
unless table_name
raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name="
end
columns_hash = connection.schema_cache.columns_hash(table_name)
columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty?
@columns_hash = columns_hash.freeze
@columns_hash.each do |name, column|
type = connection.lookup_cast_type_from_column(column)
type = _convert_type_from_options(type)
define_attribute(
name,
type,
default: column.default,
user_provided_default: false
)
alias_attribute :id_value, :id if name == "id"
end
end
# Guesses the table name, but does not decorate it with prefix and suffix information.
def undecorated_table_name(model_name)
table_name = model_name.to_s.demodulize.underscore
pluralize_table_names ? table_name.pluralize : table_name
end
# Computes and returns a table name according to default conventions.
def compute_table_name
if base_class?
# Nested classes are prefixed with singular parent table name.
if module_parent < Base && !module_parent.abstract_class?
contained = module_parent.table_name
contained = contained.singularize if module_parent.pluralize_table_names
contained += "_"
end
"#{full_table_name_prefix}#{contained}#{undecorated_table_name(model_name)}#{full_table_name_suffix}"
else
# STI subclasses always use their superclass's table.
base_class.table_name
end
end
def _convert_type_from_options(type)
if immutable_strings_by_default && type.respond_to?(:to_immutable_string)
type.to_immutable_string
else
type
end
end
end
end Related methods
- Instance methods
- id_value
- Class methods
- derive_join_table_name
- immutable_strings_by_default=
- implicit_order_column
- implicit_order_column=
- inheritance_column
- inheritance_column=
- internal_metadata_table_name
- internal_metadata_table_name=
- pluralize_table_names
- pluralize_table_names=
- primary_key_prefix_type
- primary_key_prefix_type=
- schema_migrations_table_name
- schema_migrations_table_name=
- table_name_prefix
- table_name_prefix=
- table_name_suffix
- table_name_suffix=