2013-01-06 04:40:12 -05:00
require 'active_support/core_ext/hash/indifferent_access'
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
2014-06-30 11:29:32 -04:00
# STI classes:
#
# 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:
# http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
#
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.
2012-10-26 10:51:02 -04:00
class_attribute :store_full_sti_class , instance_writer : false
self . store_full_sti_class = 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.
2012-04-05 15:21:48 -04:00
def new ( * args , & 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
attrs = args . first
if subclass_from_attributes? ( attrs )
subclass = subclass_from_attributes ( attrs )
end
2015-03-24 11:09:46 -04:00
if subclass && subclass != self
2014-01-14 08:23:45 -05:00
subclass . new ( * args , & block )
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
protected
# 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.
def compute_type ( type_name )
if type_name . match ( / ^:: / )
# 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
# Build a list of candidates to search for
candidates = [ ]
name . scan ( / ::|$ / ) { candidates . unshift " #{ $` } :: #{ type_name } " }
candidates << type_name
candidates . each do | candidate |
2014-06-16 18:25:09 -04:00
constant = ActiveSupport :: Dependencies . safe_constantize ( candidate )
return constant if candidate == constant . to_s
2011-12-15 15:07:41 -05:00
end
2014-01-10 19:27:18 -05:00
raise NameError . new ( " uninitialized constant #{ candidates . first } " , candidates . first )
2011-12-15 15:07:41 -05:00
end
end
private
2012-11-29 10:45:31 -05: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.
def discriminate_class_for_record ( record )
2014-11-14 12:45:53 -05:00
if using_single_table_inheritance? ( record )
find_sti_class ( record [ inheritance_column ] )
2012-11-29 10:45:31 -05:00
else
super
end
end
2014-11-14 12:45:53 -05:00
def using_single_table_inheritance? ( record )
record [ inheritance_column ] . present? && columns_hash . include? ( inheritance_column )
2012-11-29 10:45:31 -05:00
end
2011-12-15 15:07:41 -05:00
def find_sti_class ( type_name )
2015-03-24 11:09:46 -04:00
subclass = begin
if store_full_sti_class
ActiveSupport :: Dependencies . constantize ( type_name )
else
compute_type ( type_name )
end
rescue NameError
raise SubclassNotFound ,
2015-10-29 11:42:24 -04:00
" 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 " \
2015-03-24 11:09:46 -04:00
" or overwrite #{ name } .inheritance_column to use another column for that information. "
end
unless subclass == self || descendants . include? ( subclass )
2015-10-29 11:37:13 -04:00
raise SubclassNotFound , " Invalid single-table inheritance type: #{ subclass . name } is not a subclass of #{ name } "
2011-12-15 15:07:41 -05:00
end
2015-03-24 11:09:46 -04:00
subclass
2011-12-15 15:07:41 -05:00
end
def type_condition ( table = arel_table )
2013-10-09 19:09:59 -04:00
sti_column = table [ inheritance_column ]
2014-10-27 12:28:53 -04:00
sti_names = ( [ self ] + descendants ) . map ( & :sti_name )
2011-12-15 15:07:41 -05:00
sti_column . in ( sti_names )
end
2012-04-05 15:21:48 -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
# If this is a StrongParameters hash, and access to inheritance_column is not permitted,
# this will ignore the inheritance column and return nil
2014-01-14 08:23:45 -05:00
def subclass_from_attributes? ( attrs )
2015-07-23 09:49:03 -04:00
attribute_names . include? ( inheritance_column ) && ( attrs . is_a? ( Hash ) || attrs . respond_to? ( :permitted? ) )
2014-01-14 08:23:45 -05:00
end
def subclass_from_attributes ( attrs )
2015-07-23 09:49:03 -04:00
attrs = attrs . to_h if attrs . respond_to? ( :permitted? )
2012-04-05 15:21:48 -04:00
subclass_name = attrs . with_indifferent_access [ inheritance_column ]
2013-03-08 09:55:31 -05:00
2015-03-26 13:26:19 -04:00
if subclass_name . present?
2015-03-24 11:09:46 -04:00
find_sti_class ( subclass_name )
2012-04-05 15:21:48 -04:00
end
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
2014-01-22 12:55:46 -05:00
def initialize_internals_callback
super
ensure_proper_type
end
2011-12-15 15:07:41 -05: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.
def ensure_proper_type
klass = self . class
if klass . finder_needs_type_condition?
write_attribute ( klass . inheritance_column , klass . sti_name )
end
end
end
end