# frozen_string_literal: truerequire"active_support/inflector"require"active_support/core_ext/hash/indifferent_access"moduleActiveRecord# = 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.## Be aware that because the type column is an attribute on the record every new# subclass will instantly be marked as dirty and the type column will be included# in the list of changed attributes on the record. This is different from non# Single Table Inheritance(STI) classes:## Company.new.changed? # => false# Firm.new.changed? # => true# Firm.new.changes # => {"type"=>["","Firm"]}## 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:# * https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html#moduleInheritanceextendActiveSupport::Concernincludeddoclass_attribute:store_full_class_name,instance_writer: false,default: true# Determines whether to store the full constant name including namespace when using STI.# This is true, by default.class_attribute:store_full_sti_class,instance_writer: false,default: trueset_base_classendmoduleClassMethods# 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.defnew(attributes=nil,&block)ifabstract_class?||self==BaseraiseNotImplementedError,"#{self} is an abstract class and cannot be instantiated."endif_has_attribute?(inheritance_column)subclass=subclass_from_attributes(attributes)ifsubclass.nil?&&scope_attributes=current_scope&.scope_for_createsubclass=subclass_from_attributes(scope_attributes)endifsubclass.nil?&&base_class?subclass=subclass_from_attributes(column_defaults)endendifsubclass&&subclass!=selfsubclass.new(attributes,&block)elsesuperendend# Returns +true+ if this does not need STI type condition. Returns# +false+ if STI type condition needs to be applied.defdescends_from_active_record?ifself==Basefalseelsifsuperclass.abstract_class?superclass.descends_from_active_record?elsesuperclass==Base||!columns_hash.include?(inheritance_column)endenddeffinder_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# Returns the first class in the inheritance hierarchy that descends from either an# abstract class or from <tt>ActiveRecord::Base</tt>.## Consider the following behaviour:## class ApplicationRecord < ActiveRecord::Base# self.abstract_class = true# end# class Shape < ApplicationRecord# self.abstract_class = true# end# Polygon = Class.new(Shape)# Square = Class.new(Polygon)## ApplicationRecord.base_class # => ApplicationRecord# Shape.base_class # => Shape# Polygon.base_class # => Polygon# Square.base_class # => Polygonattr_reader:base_class# Returns whether the class is a base class.# See #base_class for more information.defbase_class?base_class==selfend# 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.## Consider the following default behavior:## 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# self.abstract_class = true# end# Polygon = Class.new(Shape)# Square = Class.new(Polygon)## 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">## 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.attr_accessor:abstract_class# Returns whether this class is an abstract class or not.defabstract_class?@abstract_class==trueend# 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.defprimary_abstract_classifActiveRecord.application_record_class&&ActiveRecord.application_record_class.name!=nameraiseArgumentError,"The `primary_abstract_class` is already set to #{ActiveRecord.application_record_class.inspect}. There can only be one `primary_abstract_class` in an application."endself.abstract_class=trueActiveRecord.application_record_class=selfend# Returns the value to be stored in the inheritance column for STI.defsti_namestore_full_sti_class&&store_full_class_name?name:name.demodulizeend# Returns the class for the provided +type_name+.## It is used to find the class correspondent to the value stored in the inheritance column.defsti_class_for(type_name)ifstore_full_sti_class&&store_full_class_nametype_name.constantizeelsecompute_type(type_name)endrescueNameErrorraiseSubclassNotFound,"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. "\"If you wish to disable single-table inheritance for #{name} set "\"#{name}.inheritance_column to nil"end# Returns the value to be stored in the polymorphic type column for Polymorphic Associations.defpolymorphic_namestore_full_class_name?base_class.name:base_class.name.demodulizeend# Returns the class for the provided +name+.## It is used to find the class correspondent to the value stored in the polymorphic type column.defpolymorphic_class_for(name)ifstore_full_class_namename.constantizeelsecompute_type(name)endenddefdup# :nodoc:# `initialize_dup` / `initialize_copy` don't work when defined# in the `singleton_class`.other=superother.set_base_classotherenddefinitialize_clone(other)# :nodoc:superset_base_classendprotected# 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.defcompute_type(type_name)iftype_name.start_with?("::")# If the type is prefixed with a scope operator then we assume that# the type_name is an absolute reference.type_name.constantizeelsetype_candidate=@_type_candidates_cache[type_name]iftype_candidate&&type_constant=type_candidate.safe_constantizereturntype_constantend# Build a list of candidates to search forcandidates=[]name.scan(/::|$/){candidates.unshift"#{$`}::#{type_name}"}candidates<<type_namecandidates.eachdo|candidate|constant=candidate.safe_constantizeifcandidate==constant.to_s@_type_candidates_cache[type_name]=candidatereturnconstantendendraiseNameError.new("uninitialized constant #{candidates.first}",candidates.first)endenddefset_base_class# :nodoc:@base_class=ifself==Baseselfelseunlessself<BaseraiseActiveRecordError,"#{name} doesn't belong in a hierarchy descending from ActiveRecord"endifsuperclass==Base||superclass.abstract_class?selfelsesuperclass.base_classendendendprivatedefinherited(subclass)supersubclass.set_base_classsubclass.instance_variable_set(:@_type_candidates_cache,Concurrent::Map.new)subclass.class_evaldo@finder_needs_type_condition=nilendend# 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.defdiscriminate_class_for_record(record)ifusing_single_table_inheritance?(record)find_sti_class(record[inheritance_column])elsesuperendenddefusing_single_table_inheritance?(record)record[inheritance_column].present?&&_has_attribute?(inheritance_column)enddeffind_sti_class(type_name)type_name=base_class.type_for_attribute(inheritance_column).cast(type_name)subclass=sti_class_for(type_name)unlesssubclass==self||descendants.include?(subclass)raiseSubclassNotFound,"Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}"endsubclassenddeftype_condition(table=arel_table)sti_column=table[inheritance_column]sti_names=([self]+descendants).map(&:sti_name)predicate_builder.build(sti_column,sti_names)end# Detect the subclass from the inheritance column of attrs. If the inheritance column value# is not self or a valid subclass, raises ActiveRecord::SubclassNotFounddefsubclass_from_attributes(attrs)attrs=attrs.to_hifattrs.respond_to?(:permitted?)ifattrs.is_a?(Hash)subclass_name=attrs[inheritance_column]||attrs[inheritance_column.to_sym]ifsubclass_name.present?find_sti_class(subclass_name)endendendenddefinitialize_dup(other)superensure_proper_typeendprivatedefinitialize_internals_callbacksuperensure_proper_typeend# 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.defensure_proper_typeklass=self.classifklass.finder_needs_type_condition?_write_attribute(klass.inheritance_column,klass.sti_name)endendendend