Skip to content
Newer
Older
100644 240 lines (210 sloc) 9.07 KB
85afc11 @amatsuda Missing require
amatsuda authored Jan 6, 2013
1 require 'active_support/core_ext/hash/indifferent_access'
2
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
3 module ActiveRecord
088b4c3 @sgrif Move STI docs off of the main Base document, leaving a note
sgrif authored Jun 30, 2014
4 # == Single table inheritance
5 #
6 # Active Record allows inheritance by storing the name of the class in a column that by
7 # default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
8 # This means that an inheritance looking like this:
9 #
10 # class Company < ActiveRecord::Base; end
11 # class Firm < Company; end
12 # class Client < Company; end
13 # class PriorityClient < Client; end
14 #
15 # When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in
16 # the companies table with type = "Firm". You can then fetch this row again using
17 # <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object.
18 #
4cb4e87 @robin850 Tiny follow-up to #15987 and 088b4c3 [ci skip]
robin850 authored Jul 2, 2014
19 # Be aware that because the type column is an attribute on the record every new
088b4c3 @sgrif Move STI docs off of the main Base document, leaving a note
sgrif authored Jun 30, 2014
20 # subclass will instantly be marked as dirty and the type column will be included
4cb4e87 @robin850 Tiny follow-up to #15987 and 088b4c3 [ci skip]
robin850 authored Jul 2, 2014
21 # in the list of changed attributes on the record. This is different from non
088b4c3 @sgrif Move STI docs off of the main Base document, leaving a note
sgrif authored Jun 30, 2014
22 # STI classes:
23 #
24 # Company.new.changed? # => false
4cb4e87 @robin850 Tiny follow-up to #15987 and 088b4c3 [ci skip]
robin850 authored Jul 2, 2014
25 # Firm.new.changed? # => true
26 # Firm.new.changes # => {"type"=>["","Firm"]}
088b4c3 @sgrif Move STI docs off of the main Base document, leaving a note
sgrif authored Jun 30, 2014
27 #
28 # If you don't have a type column defined in your table, single-table inheritance won't
29 # be triggered. In that case, it'll work just like normal subclasses with no special magic
30 # for differentiating between them or reloading the right type with find.
31 #
32 # Note, all the attributes for all the cases are kept in the same table. Read more:
33 # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
34 #
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
35 module Inheritance
36 extend ActiveSupport::Concern
37
38 included do
04f00c6 minor comments cleanup
Neeraj Singh authored May 27, 2013
39 # Determines whether to store the full constant name including namespace when using STI.
9e4c41c @jonleighton Remove ActiveRecord::Model
jonleighton authored Oct 26, 2012
40 class_attribute :store_full_sti_class, instance_writer: false
41 self.store_full_sti_class = true
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
42 end
43
44 module ClassMethods
89b5b31 @diminish7 Added STI support to init and building associations
diminish7 authored Apr 5, 2012
45 # Determines if one of the attributes passed in is the inheritance column,
46 # and if the inheritance column is attr accessible, it initializes an
04f00c6 minor comments cleanup
Neeraj Singh authored May 27, 2013
47 # instance of the given subclass instead of the base class.
89b5b31 @diminish7 Added STI support to init and building associations
diminish7 authored Apr 5, 2012
48 def new(*args, &block)
53f18f2 @HonoreDB More helpful error message when instantiating an abstract class
HonoreDB authored Feb 27, 2013
49 if abstract_class? || self == Base
72bb3fc @tjschuck Change all "can not"s to the correct "cannot".
tjschuck authored Jan 3, 2014
50 raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
53f18f2 @HonoreDB More helpful error message when instantiating an abstract class
HonoreDB authored Feb 27, 2013
51 end
e8d1d84 @ujjwalt Don't try to get the subclass if the inheritance column doesn't exist
ujjwalt authored Jan 14, 2014
52
53 attrs = args.first
4294a7e @senny add `ActiveRecord::Base.has_attribute?`
senny authored Dec 2, 2015
54 if has_attribute?(inheritance_column)
77383fc @sgrif Do not use default attributes for STI when instantiating a subclass
sgrif authored Jan 27, 2016
55 subclass = subclass_from_attributes(attrs)
56
57 if subclass.nil? && base_class == self
58 subclass = subclass_from_attributes(column_defaults)
59 end
e8d1d84 @ujjwalt Don't try to get the subclass if the inheritance column doesn't exist
ujjwalt authored Jan 14, 2014
60 end
61
7c0f8c6 @ccutrer DRY up STI subclass logic
ccutrer authored Mar 24, 2015
62 if subclass && subclass != self
e8d1d84 @ujjwalt Don't try to get the subclass if the inheritance column doesn't exist
ujjwalt authored Jan 14, 2014
63 subclass.new(*args, &block)
64 else
65 super
89b5b31 @diminish7 Added STI support to init and building associations
diminish7 authored Apr 5, 2012
66 end
67 end
68
04f00c6 minor comments cleanup
Neeraj Singh authored May 27, 2013
69 # Returns +true+ if this does not need STI type condition. Returns
70 # +false+ if STI type condition needs to be applied.
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
71 def descends_from_active_record?
9e4c41c @jonleighton Remove ActiveRecord::Model
jonleighton authored Oct 26, 2012
72 if self == Base
dae7b65 @jonleighton Support establishing connection on ActiveRecord::Model.
jonleighton authored Dec 28, 2011
73 false
9e4c41c @jonleighton Remove ActiveRecord::Model
jonleighton authored Oct 26, 2012
74 elsif superclass.abstract_class?
75 superclass.descends_from_active_record?
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
76 else
9e4c41c @jonleighton Remove ActiveRecord::Model
jonleighton authored Oct 26, 2012
77 superclass == Base || !columns_hash.include?(inheritance_column)
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
78 end
79 end
80
81 def finder_needs_type_condition? #:nodoc:
82 # This is like this because benchmarking justifies the strange :false stuff
83 :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
84 end
85
9e4c41c @jonleighton Remove ActiveRecord::Model
jonleighton authored Oct 26, 2012
86 # Returns the class descending directly from ActiveRecord::Base, or
87 # an abstract class, if any, in the inheritance hierarchy.
d0aebd5 @beerlington Refactor ActiveRecord::Inheritance.base_class logic
beerlington authored Jul 26, 2012
88 #
dad0c26 @arvindmehra Replace AR with ActiveRecord to make it more readable [ci skip]
arvindmehra authored Sep 7, 2015
89 # If A extends ActiveRecord::Base, A.base_class will return A. If B descends from A
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
90 # through some arbitrarily deep hierarchy, B.base_class will return A.
91 #
92 # If B < A and C < B and if A is an abstract_class then both B.base_class
93 # and C.base_class would return B as the answer since A is an abstract_class.
94 def base_class
9e4c41c @jonleighton Remove ActiveRecord::Model
jonleighton authored Oct 26, 2012
95 unless self < Base
d0aebd5 @beerlington Refactor ActiveRecord::Inheritance.base_class logic
beerlington authored Jul 26, 2012
96 raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
97 end
98
9e4c41c @jonleighton Remove ActiveRecord::Model
jonleighton authored Oct 26, 2012
99 if superclass == Base || superclass.abstract_class?
d0aebd5 @beerlington Refactor ActiveRecord::Inheritance.base_class logic
beerlington authored Jul 26, 2012
100 self
101 else
9e4c41c @jonleighton Remove ActiveRecord::Model
jonleighton authored Oct 26, 2012
102 superclass.base_class
d0aebd5 @beerlington Refactor ActiveRecord::Inheritance.base_class logic
beerlington authored Jul 26, 2012
103 end
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
104 end
105
106 # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
65a3020 @joegoggins Adding documentation for ActiveRecord::Base.abstract_class to clarify…
joegoggins authored Mar 22, 2012
107 # If you are using inheritance with ActiveRecord and don't want child classes
108 # to utilize the implied STI table name of the parent class, this will need to be true.
109 # For example, given the following:
110 #
111 # class SuperClass < ActiveRecord::Base
112 # self.abstract_class = true
113 # end
114 # class Child < SuperClass
115 # self.table_name = 'the_table_i_really_want'
116 # end
a30b8d3 rename AR::Model::Tag to AR::Tag - fixes #7714
Francesco Rodriguez authored Sep 20, 2012
117 #
65a3020 @joegoggins Adding documentation for ActiveRecord::Base.abstract_class to clarify…
joegoggins authored Mar 22, 2012
118 #
119 # <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>
120 #
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
121 attr_accessor :abstract_class
122
123 # Returns whether this class is an abstract class or not.
124 def abstract_class?
125 defined?(@abstract_class) && @abstract_class == true
126 end
127
128 def sti_name
129 store_full_sti_class ? name : name.demodulize
130 end
131
132 protected
133
134 # Returns the class type of the record using the current module as a prefix. So descendants of
135 # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
136 def compute_type(type_name)
137 if type_name.match(/^::/)
138 # If the type is prefixed with a scope operator then we assume that
139 # the type_name is an absolute reference.
140 ActiveSupport::Dependencies.constantize(type_name)
141 else
142 # Build a list of candidates to search for
143 candidates = []
144 name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
145 candidates << type_name
146
147 candidates.each do |candidate|
c965de3 @arthurnn Dont swallow errors when bad alias_method
arthurnn authored Jun 16, 2014
148 constant = ActiveSupport::Dependencies.safe_constantize(candidate)
149 return constant if candidate == constant.to_s
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
150 end
151
bea44cb @chulkilee Set NameError#name
chulkilee authored Jan 10, 2014
152 raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
153 end
154 end
155
156 private
157
f2902eb @jeremy Move instantiation responsibilities from Inheritance to Persistence. …
jeremy authored Nov 29, 2012
158 # Called by +instantiate+ to decide which class to use for a new
159 # record instance. For single-table inheritance, we check the record
160 # for a +type+ column and return the corresponding class.
161 def discriminate_class_for_record(record)
70d1b5a @sgrif Revert "Improve performance of AR object instantiation"
sgrif authored Nov 14, 2014
162 if using_single_table_inheritance?(record)
163 find_sti_class(record[inheritance_column])
f2902eb @jeremy Move instantiation responsibilities from Inheritance to Persistence. …
jeremy authored Nov 29, 2012
164 else
165 super
166 end
167 end
168
70d1b5a @sgrif Revert "Improve performance of AR object instantiation"
sgrif authored Nov 14, 2014
169 def using_single_table_inheritance?(record)
4294a7e @senny add `ActiveRecord::Base.has_attribute?`
senny authored Dec 2, 2015
170 record[inheritance_column].present? && has_attribute?(inheritance_column)
f2902eb @jeremy Move instantiation responsibilities from Inheritance to Persistence. …
jeremy authored Nov 29, 2012
171 end
172
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
173 def find_sti_class(type_name)
3ea4476 @senny run `type` column through attribtues API type casting.
senny authored Jan 19, 2016
174 type_name = base_class.type_for_attribute(inheritance_column).cast(type_name)
7c0f8c6 @ccutrer DRY up STI subclass logic
ccutrer authored Mar 24, 2015
175 subclass = begin
176 if store_full_sti_class
177 ActiveSupport::Dependencies.constantize(type_name)
178 else
179 compute_type(type_name)
180 end
181 rescue NameError
182 raise SubclassNotFound,
b8832c1 @sgrif Fix a stylistic nitpick in #19501
sgrif authored Oct 29, 2015
183 "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
184 "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \
185 "Please rename this column if you didn't intend it to be used for storing the inheritance class " \
7c0f8c6 @ccutrer DRY up STI subclass logic
ccutrer authored Mar 24, 2015
186 "or overwrite #{name}.inheritance_column to use another column for that information."
187 end
188 unless subclass == self || descendants.include?(subclass)
42b9f3e @sgrif Fix test failures caused by #19501
sgrif authored Oct 29, 2015
189 raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}"
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
190 end
7c0f8c6 @ccutrer DRY up STI subclass logic
ccutrer authored Mar 24, 2015
191 subclass
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
192 end
193
194 def type_condition(table = arel_table)
cdc112e @matthewd Defer Arel attribute lookup to the model class
matthewd authored Feb 4, 2016
195 sti_column = arel_attribute(inheritance_column, table)
d1374f9 @sferik Pass symbol as an argument instead of a block
sferik authored Oct 27, 2014
196 sti_names = ([self] + descendants).map(&:sti_name)
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
197
198 sti_column.in(sti_names)
199 end
89b5b31 @diminish7 Added STI support to init and building associations
diminish7 authored Apr 5, 2012
200
201 # Detect the subclass from the inheritance column of attrs. If the inheritance column value
202 # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
e8d1d84 @ujjwalt Don't try to get the subclass if the inheritance column doesn't exist
ujjwalt authored Jan 14, 2014
203 def subclass_from_attributes(attrs)
85f7d95 @twalpole Update and fix forbidden attributes tests
twalpole authored Jul 23, 2015
204 attrs = attrs.to_h if attrs.respond_to?(:permitted?)
3da890f @senny Merge pull request #17169 from kuldeepaggarwal/fix-STI-default-type
senny authored Dec 2, 2015
205 if attrs.is_a?(Hash)
206 subclass_name = attrs.with_indifferent_access[inheritance_column]
6b18bdd @kuldeepaggarwal STI cast new instances to `default type` on initialize.
kuldeepaggarwal authored Dec 2, 2015
207
3da890f @senny Merge pull request #17169 from kuldeepaggarwal/fix-STI-default-type
senny authored Dec 2, 2015
208 if subclass_name.present?
209 find_sti_class(subclass_name)
210 end
6b18bdd @kuldeepaggarwal STI cast new instances to `default type` on initialize.
kuldeepaggarwal authored Dec 2, 2015
211 end
212 end
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
213 end
214
8cbd500 @kbrock Move changed_attributes into dirty.rb
kbrock authored Jan 22, 2014
215 def initialize_dup(other)
216 super
217 ensure_proper_type
218 end
219
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
220 private
221
8cbd500 @kbrock Move changed_attributes into dirty.rb
kbrock authored Jan 22, 2014
222 def initialize_internals_callback
223 super
224 ensure_proper_type
225 end
226
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored Dec 15, 2011
227 # Sets the attribute used for single table inheritance to this class name if this is not the
228 # ActiveRecord::Base descendant.
229 # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
230 # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
231 # No such attribute would be set for objects of the Message class in that example.
232 def ensure_proper_type
233 klass = self.class
234 if klass.finder_needs_type_condition?
235 write_attribute(klass.inheritance_column, klass.sti_name)
236 end
237 end
238 end
239 end
Something went wrong with that request. Please try again.