Skip to content
This repository
Newer
Older
100644 283 lines (259 sloc) 10.88 kb
e8550ee0 »
2009-05-13 Cherry-pick core extensions
1 require 'active_support/core_ext/enumerable'
2
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
3 module ActiveRecord
4 module AttributeMethods #:nodoc:
4e50a35f »
2009-05-28 Break up DependencyModule's dual function of providing a "depend_on" …
5 extend ActiveSupport::Concern
a2875bec »
2009-05-11 Use DependencyModule for included hooks in ActiveRecord
6
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
7 class AttributeMethodMatcher
8 attr_reader :prefix, :suffix
9
10 AttributeMethodMatch = Struct.new(:prefix, :base, :suffix)
11
12 def initialize(options = {})
13 options.symbolize_keys!
14 @prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
15 @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/
16 end
17
18 def match(method_name)
19 if matchdata = @regex.match(method_name)
20 AttributeMethodMatch.new(matchdata[1], matchdata[2], matchdata[3])
21 else
22 nil
23 end
24 end
25 end
26
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
27 # Declare and check for suffixed attribute methods.
28 module ClassMethods
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
29 # Declares a method available for all attributes with the given prefix.
30 # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
31 #
32 # #{prefix}#{attr}(*args, &block)
33 #
34 # to
35 #
36 # #{prefix}attribute(#{attr}, *args, &block)
37 #
38 # An <tt>#{prefix}attribute</tt> instance method must exist and accept at least
39 # the +attr+ argument.
40 #
41 # For example:
42 #
43 # class Person < ActiveRecord::Base
44 # attribute_method_prefix 'clear_'
45 #
46 # private
47 # def clear_attribute(attr)
48 # ...
49 # end
50 # end
51 #
52 # person = Person.find(1)
53 # person.name # => 'Gem'
54 # person.clear_name
55 # person.name # => ''
56 def attribute_method_prefix(*prefixes)
57 attribute_method_matchers.concat(prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix })
58 undefine_attribute_methods
59 end
60
46f30f90 »
2008-05-16 Merge documentation changes from docrails.
61 # Declares a method available for all attributes with the given suffix.
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
62 # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
46f30f90 »
2008-05-16 Merge documentation changes from docrails.
63 #
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
64 # #{attr}#{suffix}(*args, &block)
46f30f90 »
2008-05-16 Merge documentation changes from docrails.
65 #
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
66 # to
46f30f90 »
2008-05-16 Merge documentation changes from docrails.
67 #
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
68 # attribute#{suffix}(#{attr}, *args, &block)
69 #
46f30f90 »
2008-05-16 Merge documentation changes from docrails.
70 # An <tt>attribute#{suffix}</tt> instance method must exist and accept at least
71 # the +attr+ argument.
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
72 #
73 # For example:
46f30f90 »
2008-05-16 Merge documentation changes from docrails.
74 #
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
75 # class Person < ActiveRecord::Base
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
76 # attribute_method_suffix '_short?'
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
77 #
78 # private
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
79 # def attribute_short?(attr)
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
80 # ...
81 # end
82 # end
83 #
84 # person = Person.find(1)
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
85 # person.name # => 'Gem'
86 # person.name_short? # => true
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
87 def attribute_method_suffix(*suffixes)
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
88 attribute_method_matchers.concat(suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix })
1ae7eb52 »
2009-07-30 Make sure to reset defined methods after calling attribute_method_suffix
89 undefine_attribute_methods
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
90 end
91
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
92 # Declares a method available for all attributes with the given prefix
93 # and suffix. Uses +method_missing+ and <tt>respond_to?</tt> to rewrite
94 # the method.
95 #
96 # #{prefix}#{attr}#{suffix}(*args, &block)
97 #
98 # to
99 #
100 # #{prefix}attribute#{suffix}(#{attr}, *args, &block)
101 #
102 # An <tt>#{prefix}attribute#{suffix}</tt> instance method must exist and
103 # accept at least the +attr+ argument.
104 #
105 # For example:
106 #
107 # class Person < ActiveRecord::Base
108 # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
109 #
110 # private
111 # def reset_attribute_to_default!(attr)
112 # ...
113 # end
114 # end
115 #
116 # person = Person.find(1)
117 # person.name # => 'Gem'
118 # person.reset_name_to_default!
119 # person.name # => 'Gemma'
120 def attribute_method_affix(*affixes)
121 attribute_method_matchers.concat(affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] })
122 undefine_attribute_methods
123 end
124
125 def matching_attribute_methods(method_name)
126 attribute_method_matchers.collect { |method| method.match(method_name) }.compact
127 end
128
62fd1d37 »
2009-07-30 Start separating primary key concerns
129 # Defines an "attribute" method (like +inheritance_column+ or
130 # +table_name+). A new (class) method will be created with the
131 # given name. If a value is specified, the new method will
132 # return that value (as a string). Otherwise, the given block
133 # will be used to compute the value of the method.
134 #
135 # The original method will be aliased, with the new name being
136 # prefixed with "original_". This allows the new method to
137 # access the original value.
138 #
139 # Example:
140 #
141 # class A < ActiveRecord::Base
142 # define_attr_method :primary_key, "sysid"
143 # define_attr_method( :inheritance_column ) do
144 # original_inheritance_column + "_id"
145 # end
146 # end
147 def define_attr_method(name, value=nil, &block)
148 sing = metaclass
149 sing.send :alias_method, "original_#{name}", name
150 if block_given?
151 sing.send :define_method, name, &block
152 else
153 # use eval instead of a block to work around a memory leak in dev
154 # mode in fcgi
155 sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
156 end
157 end
158
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
159 def generated_methods #:nodoc:
9b688778 »
2009-08-02 Track generated attribute methods in a separate module
160 @generated_methods ||= begin
161 mod = Module.new
162 include mod
163 mod
164 end
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
165 end
c2b075be »
2009-07-24 Concernify AR AttributeMethods
166
46f30f90 »
2008-05-16 Merge documentation changes from docrails.
167 # Generates all the attribute related methods for columns in the database
168 # accessors, mutators and query methods.
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
169 def define_attribute_methods
9b688778 »
2009-08-02 Track generated attribute methods in a separate module
170 return unless generated_methods.instance_methods.empty?
c2b075be »
2009-07-24 Concernify AR AttributeMethods
171 columns_hash.keys.each do |name|
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
172 attribute_method_matchers.each do |method|
173 method_name = "#{method.prefix}#{name}#{method.suffix}"
c2b075be »
2009-07-24 Concernify AR AttributeMethods
174 unless instance_method_already_implemented?(method_name)
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
175 generate_method = "define_method_#{method.prefix}attribute#{method.suffix}"
176
c2b075be »
2009-07-24 Concernify AR AttributeMethods
177 if respond_to?(generate_method)
178 send(generate_method, name)
179 else
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
180 generated_methods.module_eval("def #{method_name}(*args); send(:#{method.prefix}attribute#{method.suffix}, '#{name}', *args); end", __FILE__, __LINE__)
c2b075be »
2009-07-24 Concernify AR AttributeMethods
181 end
72385a7b »
2008-02-06 Add Time Zone support to ActiveRecord, and config.time_zone property …
182 end
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
183 end
184 end
185 end
acbec3e5 »
2007-09-17 Ensure that custom mutators aren't redefined by define_attribute_meth…
186
e129c567 »
2009-07-29 Wrap up attribute method reset concerns in 'undefine_attribute_methods'
187 def undefine_attribute_methods
9b688778 »
2009-08-02 Track generated attribute methods in a separate module
188 generated_methods.module_eval do
189 instance_methods.each { |m| undef_method(m) }
190 end
e129c567 »
2009-07-29 Wrap up attribute method reset concerns in 'undefine_attribute_methods'
191 end
192
46f30f90 »
2008-05-16 Merge documentation changes from docrails.
193 # Checks whether the method is defined in the model or any of its subclasses
3e58f8e1 »
2009-07-30 Restore DangerousAttributeError
194 # that also derive from Active Record. Raises DangerousAttributeError if the
195 # method is defined by Active Record though.
b31aa639 »
2007-10-03 Allow column accessors to be created even if Kernel. or Object# metho…
196 def instance_method_already_implemented?(method_name)
240b4c52 »
2007-12-27 Ruby 1.9 compat: attribute methods
197 method_name = method_name.to_s
3e58f8e1 »
2009-07-30 Restore DangerousAttributeError
198 @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map {|m| m.to_s }.to_set
199 @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map{|m| m.to_s }.to_set
200 raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
5b2e8b1e »
2007-10-06 Fix that ActiveRecord would create attribute methods and override cus…
201 @_defined_class_methods.include?(method_name)
b31aa639 »
2007-10-03 Allow column accessors to be created even if Kernel. or Object# metho…
202 end
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
203
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
204 private
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
205 # Default to *=, *? and *_before_type_cast
206 def attribute_method_matchers
207 @@attribute_method_matchers ||= []
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
208 end
c2b075be »
2009-07-24 Concernify AR AttributeMethods
209 end
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
210
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
211 # Returns a struct representing the matching attribute method.
212 # The struct's attributes are prefix, base and suffix.
213 def match_attribute_method?(method_name)
214 self.class.matching_attribute_methods(method_name).find do |match|
215 match.base == 'id' || @attributes.include?(match.base)
216 end
217 end
218
46f30f90 »
2008-05-16 Merge documentation changes from docrails.
219 # Allows access to the object attributes, which are held in the <tt>@attributes</tt> hash, as though they
0faa4ca0 »
2007-09-22 Doc fix (closes #9323) [Henrik N]
220 # were first-class methods. So a Person class with a name attribute can use Person#name and
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
221 # Person#name= and never directly use the attributes hash -- except for multiple assigns with
222 # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
46f30f90 »
2008-05-16 Merge documentation changes from docrails.
223 # the completed attribute is not +nil+ or 0.
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
224 #
225 # It's also possible to instantiate related objects, so a Client class belonging to the clients
46f30f90 »
2008-05-16 Merge documentation changes from docrails.
226 # table with a +master_id+ foreign key can instantiate master through Client#master.
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
227 def method_missing(method_id, *args, &block)
228 method_name = method_id.to_s
229
230 # If we haven't generated any methods yet, generate them, then
231 # see if we've created the method we're looking for.
9b688778 »
2009-08-02 Track generated attribute methods in a separate module
232 if self.class.generated_methods.instance_methods.empty?
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
233 self.class.define_attribute_methods
d60d7edc »
2009-07-08 Make it so AR attributes which conflict with object-private methods (…
234 guard_private_attribute_method!(method_name, args)
9b688778 »
2009-08-02 Track generated attribute methods in a separate module
235 if self.class.generated_methods.instance_methods.include?(method_name)
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
236 return self.send(method_id, *args, &block)
237 end
238 end
c2b075be »
2009-07-24 Concernify AR AttributeMethods
239
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
240 if match = match_attribute_method?(method_name)
241 guard_private_attribute_method!(method_name, args)
242 return __send__("#{match.prefix}attribute#{match.suffix}", match.base, *args, &block)
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
243 end
1ae7eb52 »
2009-07-30 Make sure to reset defined methods after calling attribute_method_suffix
244 super
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
245 end
246
1646e8c3 »
2008-09-02 More symbols for send and respond_to?.
247 # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
248 # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
46f30f90 »
2008-05-16 Merge documentation changes from docrails.
249 # which will all return +true+.
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
250 alias :respond_to_without_attributes? :respond_to?
4d9a7ab5 »
2008-09-21 Changed ActiveRecord attributes to respect access control.
251 def respond_to?(method, include_private_methods = false)
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
252 method_name = method.to_s
253 if super
254 return true
f550c862 »
2008-10-03 Fix performance bug in AttibuteMethods#respond_to? in handling of pri…
255 elsif !include_private_methods && super(method, true)
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
256 # If we're here then we haven't found among non-private methods
f550c862 »
2008-10-03 Fix performance bug in AttibuteMethods#respond_to? in handling of pri…
257 # but found among all methods. Which means that given method is private.
4d9a7ab5 »
2008-09-21 Changed ActiveRecord attributes to respect access control.
258 return false
9b688778 »
2009-08-02 Track generated attribute methods in a separate module
259 elsif self.class.generated_methods.instance_methods.empty?
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
260 self.class.define_attribute_methods
9b688778 »
2009-08-02 Track generated attribute methods in a separate module
261 if self.class.generated_methods.instance_methods.include?(method_name)
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
262 return true
263 end
c30a0ce3 »
2009-08-04 Modified ActiveRecord::AttributeMethods to allow classes to specify a…
264 elsif match_attribute_method?(method_name)
265 return true
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
266 end
267 super
268 end
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
269
270 private
d60d7edc »
2009-07-08 Make it so AR attributes which conflict with object-private methods (…
271 # prevent method_missing from calling private methods with #send
272 def guard_private_attribute_method!(method_name, args)
273 if self.class.private_method_defined?(method_name)
274 raise NoMethodError.new("Attempt to call private method", method_name, args)
275 end
276 end
c2b075be »
2009-07-24 Concernify AR AttributeMethods
277
5b801b59 »
2007-08-14 Change the implementation of ActiveRecord's attribute reader and writ…
278 def missing_attribute(attr_name, stack)
279 raise ActiveRecord::MissingAttributeError, "missing attribute: #{attr_name}", stack
280 end
2b3cc247 »
2006-07-31 r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
281 end
282 end
Something went wrong with that request. Please try again.