Skip to content

HTTPS clone URL

Subversion checkout URL

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