2017-07-09 13:41:28 -04:00
# frozen_string_literal: true
2016-08-06 12:24:04 -04:00
require " active_support/core_ext/hash/indifferent_access "
2013-01-06 04:40:12 -05:00
2011-12-15 15:07:41 -05:00
module ActiveRecord
2014-06-30 11:29:32 -04:00
# == Single table inheritance
#
# Active Record allows inheritance by storing the name of the class in a column that by
# default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
# This means that an inheritance looking like this:
#
# class Company < ActiveRecord::Base; end
# class Firm < Company; end
# class Client < Company; end
# class PriorityClient < Client; end
#
# When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in
# the companies table with type = "Firm". You can then fetch this row again using
# <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object.
#
2014-07-02 08:22:24 -04:00
# Be aware that because the type column is an attribute on the record every new
2014-06-30 11:29:32 -04:00
# subclass will instantly be marked as dirty and the type column will be included
2014-07-02 08:22:24 -04:00
# in the list of changed attributes on the record. This is different from non
2016-06-23 09:12:17 -04:00
# Single Table Inheritance(STI) classes:
2014-06-30 11:29:32 -04:00
#
# Company.new.changed? # => false
2014-07-02 08:22:24 -04:00
# Firm.new.changed? # => true
# Firm.new.changes # => {"type"=>["","Firm"]}
2014-06-30 11:29:32 -04:00
#
# If you don't have a type column defined in your table, single-table inheritance won't
# be triggered. In that case, it'll work just like normal subclasses with no special magic
# for differentiating between them or reloading the right type with find.
#
# Note, all the attributes for all the cases are kept in the same table. Read more:
2017-08-21 19:54:47 -04:00
# https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
2014-06-30 11:29:32 -04:00
#
2011-12-15 15:07:41 -05:00
module Inheritance
extend ActiveSupport :: Concern
included do
2020-06-20 22:42:56 -04:00
class_attribute :store_full_class_name , instance_writer : false , default : true
2013-05-27 14:51:06 -04:00
# Determines whether to store the full constant name including namespace when using STI.
2016-06-23 09:12:17 -04:00
# This is true, by default.
2017-05-29 12:01:50 -04:00
class_attribute :store_full_sti_class , instance_writer : false , default : true
2011-12-15 15:07:41 -05:00
end
module ClassMethods
2012-04-05 15:21:48 -04:00
# Determines if one of the attributes passed in is the inheritance column,
# and if the inheritance column is attr accessible, it initializes an
2013-05-27 14:51:06 -04:00
# instance of the given subclass instead of the base class.
2017-12-11 00:53:35 -05:00
def new ( attributes = nil , & block )
2013-02-27 18:17:29 -05:00
if abstract_class? || self == Base
2014-01-03 17:02:31 -05:00
raise NotImplementedError , " #{ self } is an abstract class and cannot be instantiated. "
2013-02-27 18:17:29 -05:00
end
2014-01-14 08:23:45 -05:00
2020-05-31 21:24:51 -04:00
if _has_attribute? ( inheritance_column )
2017-12-11 00:53:35 -05:00
subclass = subclass_from_attributes ( attributes )
2016-01-27 13:26:20 -05:00
2018-09-11 10:05:40 -04:00
if subclass . nil? && scope_attributes = current_scope & . scope_for_create
subclass = subclass_from_attributes ( scope_attributes )
end
2018-04-02 07:17:24 -04:00
if subclass . nil? && base_class?
2016-01-27 13:26:20 -05:00
subclass = subclass_from_attributes ( column_defaults )
end
2014-01-14 08:23:45 -05:00
end
2015-03-24 11:09:46 -04:00
if subclass && subclass != self
2017-12-11 00:53:35 -05:00
subclass . new ( attributes , & block )
2014-01-14 08:23:45 -05:00
else
super
2012-04-05 15:21:48 -04:00
end
end
2013-05-27 14:51:06 -04:00
# Returns +true+ if this does not need STI type condition. Returns
# +false+ if STI type condition needs to be applied.
2011-12-15 15:07:41 -05:00
def descends_from_active_record?
2012-10-26 10:51:02 -04:00
if self == Base
2011-12-28 13:07:08 -05:00
false
2012-10-26 10:51:02 -04:00
elsif superclass . abstract_class?
superclass . descends_from_active_record?
2011-12-15 15:07:41 -05:00
else
2012-10-26 10:51:02 -04:00
superclass == Base || ! columns_hash . include? ( inheritance_column )
2011-12-15 15:07:41 -05:00
end
end
def finder_needs_type_condition? #:nodoc:
# This is like this because benchmarking justifies the strange :false stuff
:true == ( @finder_needs_type_condition || = descends_from_active_record? ? :false : :true )
end
2012-10-26 10:51:02 -04:00
# Returns the class descending directly from ActiveRecord::Base, or
# an abstract class, if any, in the inheritance hierarchy.
2012-07-26 13:46:49 -04:00
#
2015-09-07 10:27:51 -04:00
# If A extends ActiveRecord::Base, A.base_class will return A. If B descends from A
2011-12-15 15:07:41 -05:00
# through some arbitrarily deep hierarchy, B.base_class will return A.
#
# If B < A and C < B and if A is an abstract_class then both B.base_class
# and C.base_class would return B as the answer since A is an abstract_class.
def base_class
2012-10-26 10:51:02 -04:00
unless self < Base
2012-07-26 13:46:49 -04:00
raise ActiveRecordError , " #{ name } doesn't belong in a hierarchy descending from ActiveRecord "
end
2012-10-26 10:51:02 -04:00
if superclass == Base || superclass . abstract_class?
2012-07-26 13:46:49 -04:00
self
else
2012-10-26 10:51:02 -04:00
superclass . base_class
2012-07-26 13:46:49 -04:00
end
2011-12-15 15:07:41 -05:00
end
2018-04-02 07:17:24 -04:00
# Returns whether the class is a base class.
# See #base_class for more information.
def base_class?
base_class == self
end
2017-11-16 17:20:46 -05:00
# Set this to +true+ if this is an abstract class (see
# <tt>abstract_class?</tt>).
# If you are using inheritance with Active Record and don't want a class
# to be considered as part of the STI hierarchy, you must set this to
# true.
# +ApplicationRecord+, for example, is generated as an abstract class.
2012-03-22 11:02:08 -04:00
#
2017-11-16 17:20:46 -05:00
# Consider the following default behaviour:
#
# Shape = Class.new(ActiveRecord::Base)
# Polygon = Class.new(Shape)
# Square = Class.new(Polygon)
#
# Shape.table_name # => "shapes"
# Polygon.table_name # => "shapes"
# Square.table_name # => "shapes"
# Shape.create! # => #<Shape id: 1, type: nil>
# Polygon.create! # => #<Polygon id: 2, type: "Polygon">
# Square.create! # => #<Square id: 3, type: "Square">
#
# However, when using <tt>abstract_class</tt>, +Shape+ is omitted from
# the hierarchy:
#
# class Shape < ActiveRecord::Base
2012-03-22 11:02:08 -04:00
# self.abstract_class = true
# end
2017-11-16 17:20:46 -05:00
# Polygon = Class.new(Shape)
# Square = Class.new(Polygon)
2012-03-22 11:02:08 -04:00
#
2017-11-16 17:20:46 -05:00
# Shape.table_name # => nil
# Polygon.table_name # => "polygons"
# Square.table_name # => "polygons"
# Shape.create! # => NotImplementedError: Shape is an abstract class and cannot be instantiated.
# Polygon.create! # => #<Polygon id: 1, type: nil>
# Square.create! # => #<Square id: 2, type: "Square">
2012-03-22 11:02:08 -04:00
#
2017-11-16 17:20:46 -05:00
# Note that in the above example, to disallow the creation of a plain
# +Polygon+, you should use <tt>validates :type, presence: true</tt>,
# instead of setting it as an abstract class. This way, +Polygon+ will
# stay in the hierarchy, and Active Record will continue to correctly
# derive the table name.
2011-12-15 15:07:41 -05:00
attr_accessor :abstract_class
# Returns whether this class is an abstract class or not.
def abstract_class?
defined? ( @abstract_class ) && @abstract_class == true
end
2021-01-25 10:52:56 -05:00
# Sets the application record class for Active Record
#
# This is useful if your application uses a different class than
# ApplicationRecord for your primary abstract class. This class
# will share a database connection with Active Record. It is the class
# that connects to your primary database.
def primary_abstract_class
2021-02-05 14:54:31 -05:00
if Base . application_record_class && Base . application_record_class . name != name
raise ArgumentError , " The `primary_abstract_class` is already set to #{ Base . application_record_class . inspect } . There can only be one `primary_abstract_class` in an application. "
2021-01-25 10:52:56 -05:00
end
self . abstract_class = true
Base . application_record_class = self
end
2019-10-17 16:43:36 -04:00
# Returns the value to be stored in the inheritance column for STI.
2011-12-15 15:07:41 -05:00
def sti_name
2020-06-20 22:42:56 -04:00
store_full_sti_class && store_full_class_name ? name : name . demodulize
2011-12-15 15:07:41 -05:00
end
2019-10-17 16:43:36 -04:00
# Returns the class for the provided +type_name+.
#
# It is used to find the class correspondent to the value stored in the inheritance column.
def sti_class_for ( type_name )
2020-06-20 22:42:56 -04:00
if store_full_sti_class && store_full_class_name
2019-10-17 16:43:36 -04:00
ActiveSupport :: Dependencies . constantize ( type_name )
else
compute_type ( type_name )
end
rescue NameError
raise SubclassNotFound ,
" The single-table inheritance mechanism failed to locate the subclass: ' #{ type_name } '. " \
" This error is raised because the column ' #{ inheritance_column } ' is reserved for storing the class in case of inheritance. " \
" Please rename this column if you didn't intend it to be used for storing the inheritance class " \
" or overwrite #{ name } .inheritance_column to use another column for that information. "
end
# Returns the value to be stored in the polymorphic type column for Polymorphic Associations.
2018-02-27 13:07:56 -05:00
def polymorphic_name
2020-06-20 22:42:56 -04:00
store_full_class_name ? base_class . name : base_class . name . demodulize
2018-02-27 13:07:56 -05:00
end
2019-10-17 16:43:36 -04:00
# Returns the class for the provided +name+.
#
# It is used to find the class correspondent to the value stored in the polymorphic type column.
def polymorphic_class_for ( name )
2020-06-20 22:42:56 -04:00
if store_full_class_name
ActiveSupport :: Dependencies . constantize ( name )
else
compute_type ( name )
end
2019-10-17 16:43:36 -04:00
end
2016-12-31 13:42:53 -05:00
def inherited ( subclass )
subclass . instance_variable_set ( :@_type_candidates_cache , Concurrent :: Map . new )
super
end
2011-12-15 15:07:41 -05:00
protected
2016-09-14 04:57:52 -04:00
# Returns the class type of the record using the current module as a prefix. So descendants of
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
2016-08-06 13:55:02 -04:00
def compute_type ( type_name )
2018-02-27 23:33:37 -05:00
if type_name . start_with? ( " :: " )
2016-08-06 13:55:02 -04:00
# If the type is prefixed with a scope operator then we assume that
# the type_name is an absolute reference.
ActiveSupport :: Dependencies . constantize ( type_name )
else
2016-12-31 13:42:53 -05:00
type_candidate = @_type_candidates_cache [ type_name ]
if type_candidate && type_constant = ActiveSupport :: Dependencies . safe_constantize ( type_candidate )
return type_constant
end
2016-08-06 13:55:02 -04:00
# Build a list of candidates to search for
candidates = [ ]
name . scan ( / ::|$ / ) { candidates . unshift " #{ $` } :: #{ type_name } " }
candidates << type_name
2011-12-15 15:07:41 -05:00
2016-08-06 13:55:02 -04:00
candidates . each do | candidate |
constant = ActiveSupport :: Dependencies . safe_constantize ( candidate )
2016-12-31 13:42:53 -05:00
if candidate == constant . to_s
@_type_candidates_cache [ type_name ] = candidate
return constant
end
2016-08-06 13:55:02 -04:00
end
raise NameError . new ( " uninitialized constant #{ candidates . first } " , candidates . first )
end
2011-12-15 15:07:41 -05:00
end
private
2016-09-14 04:57:52 -04:00
# Called by +instantiate+ to decide which class to use for a new
# record instance. For single-table inheritance, we check the record
# for a +type+ column and return the corresponding class.
2016-08-06 13:55:02 -04:00
def discriminate_class_for_record ( record )
if using_single_table_inheritance? ( record )
find_sti_class ( record [ inheritance_column ] )
else
super
end
2012-11-29 10:45:31 -05:00
end
2016-08-06 13:55:02 -04:00
def using_single_table_inheritance? ( record )
2020-05-31 21:24:51 -04:00
record [ inheritance_column ] . present? && _has_attribute? ( inheritance_column )
2016-08-06 13:55:02 -04:00
end
2012-11-29 10:45:31 -05:00
2016-08-06 13:55:02 -04:00
def find_sti_class ( type_name )
type_name = base_class . type_for_attribute ( inheritance_column ) . cast ( type_name )
2019-10-17 16:43:36 -04:00
subclass = sti_class_for ( type_name )
2016-08-06 13:55:02 -04:00
unless subclass == self || descendants . include? ( subclass )
raise SubclassNotFound , " Invalid single-table inheritance type: #{ subclass . name } is not a subclass of #{ name } "
end
2019-10-17 16:43:36 -04:00
2016-08-06 13:55:02 -04:00
subclass
2011-12-15 15:07:41 -05:00
end
2016-08-06 13:55:02 -04:00
def type_condition ( table = arel_table )
Move Arel attribute normalization into `arel_table`
In Active Record internal, `arel_table` is not directly used but
`arel_attribute` is used, since `arel_table` doesn't normalize an
attribute name as a string, and doesn't resolve attribute aliases.
For the above reason, `arel_attribute` should be used rather than
`arel_table`, but most people directly use `arel_table`, both
`arel_table` and `arel_attribute` are private API though.
Although I'd not recommend using private API, `arel_table` is actually
widely used, and it is also problematic for unscopeable queries and
hash-like relation merging friendly, as I explained at #39863.
To resolve the issue, this change moves Arel attribute normalization
(attribute name as a string, and attribute alias resolution) into
`arel_table`.
2020-07-19 07:00:42 -04:00
sti_column = table [ inheritance_column ]
2016-08-06 13:55:02 -04:00
sti_names = ( [ self ] + descendants ) . map ( & :sti_name )
2011-12-15 15:07:41 -05:00
2019-02-17 11:39:06 -05:00
predicate_builder . build ( sti_column , sti_names )
2016-08-06 13:55:02 -04:00
end
2012-04-05 15:21:48 -04:00
2016-09-14 04:57:52 -04:00
# Detect the subclass from the inheritance column of attrs. If the inheritance column value
# is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
2016-08-06 13:55:02 -04:00
def subclass_from_attributes ( attrs )
attrs = attrs . to_h if attrs . respond_to? ( :permitted? )
if attrs . is_a? ( Hash )
2017-05-18 16:43:49 -04:00
subclass_name = attrs [ inheritance_column ] || attrs [ inheritance_column . to_sym ]
2016-08-06 13:55:02 -04:00
if subclass_name . present?
find_sti_class ( subclass_name )
end
2015-12-02 07:31:20 -05:00
end
2015-12-02 05:38:19 -05:00
end
2011-12-15 15:07:41 -05:00
end
2014-01-22 12:55:46 -05:00
def initialize_dup ( other )
super
ensure_proper_type
end
2011-12-15 15:07:41 -05:00
private
2016-08-06 13:55:02 -04:00
def initialize_internals_callback
super
ensure_proper_type
end
2014-01-22 12:55:46 -05:00
2016-09-14 04:57:52 -04:00
# Sets the attribute used for single table inheritance to this class name if this is not the
# ActiveRecord::Base descendant.
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
# do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
# No such attribute would be set for objects of the Message class in that example.
2016-08-06 13:55:02 -04:00
def ensure_proper_type
klass = self . class
if klass . finder_needs_type_condition?
2017-06-18 10:37:06 -04:00
_write_attribute ( klass . inheritance_column , klass . sti_name )
2016-08-06 13:55:02 -04:00
end
2011-12-15 15:07:41 -05:00
end
end
end