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
module Inheritance
extend ActiveSupport :: Concern
included do
2012-10-26 10:51:02 -04:00
# Determine whether to store the full constant name including namespace when using STI
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
# instance of the given subclass instead of the base class
def new ( * args , & block )
if ( attrs = args . first ) . is_a? ( Hash )
if subclass = subclass_from_attrs ( attrs )
return subclass . new ( * args , & block )
end
end
# Delegate to the original .new
super
end
2011-12-15 15:07:41 -05:00
# True if this isn't a concrete subclass needing a STI type condition.
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
def symbolized_base_class
@symbolized_base_class || = base_class . to_s . to_sym
end
def symbolized_sti_name
@symbolized_sti_name || = sti_name . present? ? sti_name . to_sym : symbolized_base_class
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
#
# If A extends AR::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 |
begin
constant = ActiveSupport :: Dependencies . constantize ( candidate )
return constant if candidate == constant . to_s
rescue NameError = > e
# We don't want to swallow NoMethodError < NameError errors
raise e unless e . instance_of? ( NameError )
end
end
raise NameError , " uninitialized constant #{ candidates . first } "
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 )
if using_single_table_inheritance? ( record )
find_sti_class ( record [ inheritance_column ] )
else
super
end
end
def using_single_table_inheritance? ( record )
record [ inheritance_column ] . present? && columns_hash . include? ( inheritance_column )
end
2011-12-15 15:07:41 -05:00
def find_sti_class ( type_name )
2012-11-29 10:45:31 -05:00
if store_full_sti_class
ActiveSupport :: Dependencies . constantize ( type_name )
2011-12-15 15:07:41 -05:00
else
2012-11-29 10:45:31 -05:00
compute_type ( type_name )
2011-12-15 15:07:41 -05:00
end
2012-11-29 10:45:31 -05:00
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. "
2011-12-15 15:07:41 -05:00
end
def type_condition ( table = arel_table )
sti_column = table [ inheritance_column . to_sym ]
sti_names = ( [ self ] + descendants ) . map { | model | model . sti_name }
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
def subclass_from_attrs ( attrs )
subclass_name = attrs . with_indifferent_access [ inheritance_column ]
return nil if subclass_name . blank? || subclass_name == self . name
unless subclass = subclasses . detect { | sub | sub . name == subclass_name }
raise ActiveRecord :: SubclassNotFound . new ( " Invalid single-table inheritance type: #{ subclass_name } is not a subclass of #{ name } " )
end
subclass
end
2011-12-15 15:07:41 -05:00
end
private
# 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