Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 263 lines (224 sloc) 9.387 kB
e8550ee @jeremy Cherry-pick core extensions
jeremy authored
1 require 'active_support/core_ext/enumerable'
1a421ce @jonleighton Deprecate using method_missing for attributes that are columns.
jonleighton authored
2 require 'active_support/deprecation'
e8550ee @jeremy Cherry-pick core extensions
jeremy authored
3
2b3cc24 @jeremy r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
jeremy authored
4 module ActiveRecord
4ad6103 @rizwanreza Adds title and basic description where needed.
rizwanreza authored
5 # = Active Record Attribute Methods
2b3cc24 @jeremy r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
jeremy authored
6 module AttributeMethods #:nodoc:
4e50a35 @josh Break up DependencyModule's dual function of providing a "depend_on" …
josh authored
7 extend ActiveSupport::Concern
f8d3c72 @josh Extract generic attribute method generation to AMo
josh authored
8 include ActiveModel::AttributeMethods
a2875be @brynary Use DependencyModule for included hooks in ActiveRecord
brynary authored
9
17ad71e @jonleighton Let AttributeMethods do its own including etc
jonleighton authored
10 included do
11 include Read
12 include Write
13 include BeforeTypeCast
14 include Query
15 include PrimaryKey
16 include TimeZoneConversion
17 include Dirty
18 include Serialization
19 include DeprecatedUnderscoreRead
20
21 # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
22 # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
23 # (Alias for the protected read_attribute method).
bb78804 @spastorino Merge pull request #4408 from tomstuart/read-and-write-attribute-aliases
spastorino authored
24 def [](attr_name)
25 read_attribute(attr_name)
26 end
17ad71e @jonleighton Let AttributeMethods do its own including etc
jonleighton authored
27
28 # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
29 # (Alias for the protected write_attribute method).
bb78804 @spastorino Merge pull request #4408 from tomstuart/read-and-write-attribute-aliases
spastorino authored
30 def []=(attr_name, value)
31 write_attribute(attr_name, value)
32 end
17ad71e @jonleighton Let AttributeMethods do its own including etc
jonleighton authored
33 end
34
2b3cc24 @jeremy r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
jeremy authored
35 module ClassMethods
46f30f9 @lifo Merge documentation changes from docrails.
lifo authored
36 # Generates all the attribute related methods for columns in the database
37 # accessors, mutators and query methods.
5b801b5 @NZKoz Change the implementation of ActiveRecord's attribute reader and writ…
NZKoz authored
38 def define_attribute_methods
0bfc504 @jonleighton Add workaround and deprecation if the inherited hook is not executed.…
jonleighton authored
39 unless defined?(@attribute_methods_mutex)
40 ActiveSupport::Deprecation.warn(
41 "It looks like something (probably a gem/plugin) is removing or overriding the " \
42 "ActiveRecord::Base.inherited method. It is important that this hook executes so " \
43 "that your models are set up correctly. A workaround has been added to stop this " \
44 "causing an error in 3.2, but future versions will simply not work if the hook is " \
45 "overridden."
46 )
47
48 @attribute_methods_mutex = Mutex.new
49 end
50
70b762d @jonleighton Fix race condition :bomb:
jonleighton authored
51 # Use a mutex; we don't want two thread simaltaneously trying to define
52 # attribute methods.
53 @attribute_methods_mutex.synchronize do
54 return if attribute_methods_generated?
55 superclass.define_attribute_methods unless self == base_class
56 super(column_names)
57 @attribute_methods_generated = true
58 end
24faddd @wildchild Move ActiveModel::AttributeMethods#attribute_methods_generated? to Ac…
wildchild authored
59 end
60
61 def attribute_methods_generated?
7bb754e @jonleighton Fix #4046.
jonleighton authored
62 @attribute_methods_generated ||= false
f1a534a @jonleighton Remove the need for type_cast_attribute.
jonleighton authored
63 end
64
7bb754e @jonleighton Fix #4046.
jonleighton authored
65 # We will define the methods as instance methods, but will call them as singleton
66 # methods. This allows us to use method_defined? to check if the method exists,
67 # which is fast and won't give any false positives from the ancestors (because
68 # there are no ancestors).
bb44e5a @jonleighton Use a separate module for 'external' attribute methods.
jonleighton authored
69 def generated_external_attribute_methods
7bb754e @jonleighton Fix #4046.
jonleighton authored
70 @generated_external_attribute_methods ||= Module.new { extend self }
bb44e5a @jonleighton Use a separate module for 'external' attribute methods.
jonleighton authored
71 end
72
365e10b @jonleighton Remove unnecessary *args
jonleighton authored
73 def undefine_attribute_methods
7bb754e @jonleighton Fix #4046.
jonleighton authored
74 super
75 @attribute_methods_generated = false
e129c56 @josh Wrap up attribute method reset concerns in 'undefine_attribute_methods'
josh authored
76 end
77
b31aa63 Allow column accessors to be created even if Kernel. or Object# metho…
Tobias Lütke authored
78 def instance_method_already_implemented?(method_name)
55da28d @jonleighton We don't need to build a set for DangerousAttributeError.
jonleighton authored
79 if dangerous_attribute_method?(method_name)
80 raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
81 end
82
7bb754e @jonleighton Fix #4046.
jonleighton authored
83 if superclass == Base
84 super
85 else
0a2e379 @jonleighton Fix situation where id method didn't get defined causing postgres to …
jonleighton authored
86 # If B < A and A defines its own attribute method, then we don't want to overwrite that.
87 defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
88 defined && !ActiveRecord::Base.method_defined?(method_name) || super
7bb754e @jonleighton Fix #4046.
jonleighton authored
89 end
b31aa63 Allow column accessors to be created even if Kernel. or Object# metho…
Tobias Lütke authored
90 end
d916c62 @wycats eliminate alias_method_chain from ActiveRecord
wycats authored
91
55da28d @jonleighton We don't need to build a set for DangerousAttributeError.
jonleighton authored
92 # A method name is 'dangerous' if it is already defined by Active Record, but
93 # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
7bb754e @jonleighton Fix #4046.
jonleighton authored
94 def dangerous_attribute_method?(name)
95 method_defined_within?(name, Base)
96 end
55da28d @jonleighton We don't need to build a set for DangerousAttributeError.
jonleighton authored
97
7bb754e @jonleighton Fix #4046.
jonleighton authored
98 def method_defined_within?(name, klass, sup = klass.superclass)
99 if klass.method_defined?(name) || klass.private_method_defined?(name)
100 if sup.method_defined?(name) || sup.private_method_defined?(name)
101 klass.instance_method(name).owner != sup.instance_method(name).owner
102 else
103 true
104 end
105 else
106 false
107 end
d916c62 @wycats eliminate alias_method_chain from ActiveRecord
wycats authored
108 end
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored
109
110 def attribute_method?(attribute)
111 super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
112 end
113
114 # Returns an array of column names as strings if it's not
115 # an abstract class and table exists.
116 # Otherwise it returns an empty array.
117 def attribute_names
118 @attribute_names ||= if !abstract_class? && table_exists?
119 column_names
120 else
121 []
122 end
123 end
c30a0ce @paulgillard Modified ActiveRecord::AttributeMethods to allow classes to specify a…
paulgillard authored
124 end
64eecdd @josh whitespace
josh authored
125
ac687ed @jonleighton Let Ruby deal with method visibility.
jonleighton authored
126 # If we haven't generated any methods yet, generate them, then
127 # see if we've created the method we're looking for.
128 def method_missing(method, *args, &block)
129 unless self.class.attribute_methods_generated?
5b801b5 @NZKoz Change the implementation of ActiveRecord's attribute reader and writ…
NZKoz authored
130 self.class.define_attribute_methods
ac687ed @jonleighton Let Ruby deal with method visibility.
jonleighton authored
131
132 if respond_to_without_attributes?(method)
133 send(method, *args, &block)
134 else
135 super
136 end
364a8f3 @jeremy No need to check for generated method, just redispatch
jeremy authored
137 else
138 super
5b801b5 @NZKoz Change the implementation of ActiveRecord's attribute reader and writ…
NZKoz authored
139 end
140 end
141
1a421ce @jonleighton Deprecate using method_missing for attributes that are columns.
jonleighton authored
142 def attribute_missing(match, *args, &block)
143 if self.class.columns_hash[match.attr_name]
144 ActiveSupport::Deprecation.warn(
145 "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
146 "dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
147 "is a column of the table. If this error has happened through normal usage of Active " \
148 "Record (rather than through your own code or external libraries), please report it as " \
149 "a bug."
150 )
151 end
152
153 super
154 end
155
6a283d5 @tenderlove match method signature of the superclass
tenderlove authored
156 def respond_to?(name, include_private = false)
e0e3adf @jeremy Cheaper attribute reads and respond_to?. Add underscore-prefixed meth…
jeremy authored
157 self.class.define_attribute_methods unless self.class.attribute_methods_generated?
5b801b5 @NZKoz Change the implementation of ActiveRecord's attribute reader and writ…
NZKoz authored
158 super
159 end
2b3cc24 @jeremy r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
jeremy authored
160
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored
161 # Returns true if the given attribute is in the attributes hash
162 def has_attribute?(attr_name)
163 @attributes.has_key?(attr_name.to_s)
164 end
165
166 # Returns an array of names for the attributes available on this object.
167 def attribute_names
168 @attributes.keys
169 end
170
171 # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
172 def attributes
173 Hash[@attributes.map { |name, _| [name, read_attribute(name)] }]
174 end
175
176 # Returns an <tt>#inspect</tt>-like string for the value of the
177 # attribute +attr_name+. String attributes are truncated upto 50
178 # characters, and Date and Time attributes are returned in the
179 # <tt>:db</tt> format. Other attributes return the value of
180 # <tt>#inspect</tt> without modification.
181 #
182 # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
183 #
184 # person.attribute_for_inspect(:name)
185 # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
186 #
187 # person.attribute_for_inspect(:created_at)
188 # # => '"2009-01-12 04:48:57"'
189 def attribute_for_inspect(attr_name)
190 value = read_attribute(attr_name)
191
192 if value.is_a?(String) && value.length > 50
193 "#{value[0..50]}...".inspect
194 elsif value.is_a?(Date) || value.is_a?(Time)
195 %("#{value.to_s(:db)}")
196 else
197 value.inspect
198 end
199 end
200
201 # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
202 # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
203 def attribute_present?(attribute)
204 value = read_attribute(attribute)
205 !value.nil? || (value.respond_to?(:empty?) && !value.empty?)
206 end
207
208 # Returns the column object for the named attribute.
209 def column_for_attribute(name)
210 self.class.columns_hash[name.to_s]
211 end
212
f8d3c72 @josh Extract generic attribute method generation to AMo
josh authored
213 protected
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored
214
215 def clone_attributes(reader_method = :read_attribute, attributes = {})
216 attribute_names.each do |name|
217 attributes[name] = clone_attribute_value(reader_method, name)
218 end
219 attributes
220 end
221
222 def clone_attribute_value(reader_method, attribute_name)
223 value = send(reader_method, attribute_name)
224 value.duplicable? ? value.clone : value
225 rescue TypeError, NoMethodError
226 value
227 end
228
229 # Returns a copy of the attributes hash where all the values have been safely quoted for use in
230 # an Arel insert/update method.
231 def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
232 attrs = {}
233 klass = self.class
234 arel_table = klass.arel_table
235
236 attribute_names.each do |name|
237 if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
238
239 if include_readonly_attributes || !self.class.readonly_attributes.include?(name)
240
241 value = if klass.serialized_attributes.include?(name)
242 @attributes[name].serialized_value
243 else
244 # FIXME: we need @attributes to be used consistently.
245 # If the values stored in @attributes were already type
246 # casted, this code could be simplified
247 read_attribute(name)
248 end
249
250 attrs[arel_table[name]] = value
251 end
252 end
5b801b5 @NZKoz Change the implementation of ActiveRecord's attribute reader and writ…
NZKoz authored
253 end
ceb33f8 @jonleighton Split out most of the AR::Base code into separate modules :cake:
jonleighton authored
254
255 attrs
256 end
257
258 def attribute_method?(attr_name)
259 attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
260 end
2b3cc24 @jeremy r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
jeremy authored
261 end
262 end
Something went wrong with that request. Please try again.