Skip to content

HTTPS clone URL

Subversion checkout URL

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