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 )
Attribute assignment and type casting has nothing to do with columns
It's finally finished!!!!!!! The reason the Attributes API was kept
private in 4.2 was due to some publicly visible implementation details.
It was previously implemented by overloading `columns` and
`columns_hash`, to make them return column objects which were modified
with the attribute information.
This meant that those methods LIED! We didn't change the database
schema. We changed the attribute information on the class. That is
wrong! It should be the other way around, where schema loading just
calls the attributes API for you. And now it does!
Yes, this means that there is nothing that happens in automatic schema
loading that you couldn't manually do yourself. (There's still some
funky cases where we hit the connection adapter that I need to handle,
before we can turn off automatic schema detection entirely.)
There were a few weird test failures caused by this that had to be
fixed. The main source came from the fact that the attribute methods are
now defined in terms of `attribute_names`, which has a clause like
`return [] unless table_exists?`. I don't *think* this is an issue,
since the only place this caused failures were in a fake adapter which
didn't override `table_exists?`.
Additionally, there were a few cases where tests were failing because a
migration was run, but the model was not reloaded. I'm not sure why
these started failing from this change, I might need to clear an
additional cache in `reload_schema_from_cache`. Again, since this is not
normal usage, and it's expected that `reset_column_information` will be
called after the table is modified, I don't think it's a problem.
Still, test failures that were unrelated to the change are worrying, and
I need to dig into them further.
Finally, I spent a lot of time debugging issues with the mutex used in
`define_attribute_methods`. I think we can just remove that method
entirely, and define the attribute methods *manually* in the call to
`define_attribute`, which would simplify the code *tremendously*.
Ok. now to make this damn thing public, and work on moving it up to
Active Model.
2015-01-30 16:03:36 -05:00
attribute_names . include? ( inheritance_column ) && attrs . is_a? ( Hash )
2014-01-14 08:23:45 -05:00
end
def subclass_from_attributes ( attrs )
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