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
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
2015-12-02 10:57:29 -05: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
if subclass . nil? && base_class == self
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
# Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
2012-03-22 11:02:08 -04:00
# If you are using inheritance with ActiveRecord and don't want child classes
# to utilize the implied STI table name of the parent class, this will need to be true.
# For example, given the following:
#
# class SuperClass < ActiveRecord::Base
# self.abstract_class = true
# end
# class Child < SuperClass
# self.table_name = 'the_table_i_really_want'
# end
2012-09-20 13:43:12 -04:00
#
2012-03-22 11:02:08 -04:00
#
# <tt>self.abstract_class = true</tt> is required to make <tt>Child<.find,.create, or any Arel method></tt> use <tt>the_table_i_really_want</tt> instead of a table called <tt>super_classes</tt>
#
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
def sti_name
store_full_sti_class ? name : name . demodulize
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 )
2016-12-31 13:42:53 -05:00
if type_name . start_with? ( " :: " . freeze )
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 )
record [ inheritance_column ] . present? && has_attribute? ( inheritance_column )
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 )
subclass = begin
if store_full_sti_class
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. "
2015-03-24 11:09:46 -04:00
end
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
subclass
2011-12-15 15:07:41 -05:00
end
2016-08-06 13:55:02 -04:00
def type_condition ( table = arel_table )
sti_column = arel_attribute ( inheritance_column , table )
sti_names = ( [ self ] + descendants ) . map ( & :sti_name )
2011-12-15 15:07:41 -05:00
2016-08-06 13:55:02 -04:00
sti_column . in ( sti_names )
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