mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
r4854@ks: jeremy | 2006-07-30 00:59:18 -0700
Attribute methods r4877@ks: jeremy | 2006-07-30 20:23:53 -0700 Factor the attribute#{suffix} methods out of method_missing for easier extension. r4878@ks: jeremy | 2006-07-30 20:42:23 -0700 More specific method naming, declare many attribute method suffixes, set up default suffixes at module include rather than lazily. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4632 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
efff453148
commit
2b3cc2478f
5 changed files with 114 additions and 9 deletions
|
@ -1,5 +1,7 @@
|
||||||
*SVN*
|
*SVN*
|
||||||
|
|
||||||
|
* Factor the attribute#{suffix} methods out of method_missing for easier extension. [Jeremy Kemper]
|
||||||
|
|
||||||
* Patch sql injection vulnerability when using integer or float columns. [Jamis Buck]
|
* Patch sql injection vulnerability when using integer or float columns. [Jamis Buck]
|
||||||
|
|
||||||
* Allow #count through a has_many association to accept :include. [Dan Peterson]
|
* Allow #count through a has_many association to accept :include. [Dan Peterson]
|
||||||
|
|
|
@ -52,6 +52,7 @@ require 'active_record/migration'
|
||||||
require 'active_record/schema'
|
require 'active_record/schema'
|
||||||
require 'active_record/calculations'
|
require 'active_record/calculations'
|
||||||
require 'active_record/xml_serialization'
|
require 'active_record/xml_serialization'
|
||||||
|
require 'active_record/attribute_methods'
|
||||||
|
|
||||||
ActiveRecord::Base.class_eval do
|
ActiveRecord::Base.class_eval do
|
||||||
include ActiveRecord::Validations
|
include ActiveRecord::Validations
|
||||||
|
@ -69,6 +70,7 @@ ActiveRecord::Base.class_eval do
|
||||||
include ActiveRecord::Acts::NestedSet
|
include ActiveRecord::Acts::NestedSet
|
||||||
include ActiveRecord::Calculations
|
include ActiveRecord::Calculations
|
||||||
include ActiveRecord::XmlSerialization
|
include ActiveRecord::XmlSerialization
|
||||||
|
include ActiveRecord::AttributeMethods
|
||||||
end
|
end
|
||||||
|
|
||||||
unless defined?(RAILS_CONNECTION_ADAPTERS)
|
unless defined?(RAILS_CONNECTION_ADAPTERS)
|
||||||
|
|
75
activerecord/lib/active_record/attribute_methods.rb
Normal file
75
activerecord/lib/active_record/attribute_methods.rb
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
module ActiveRecord
|
||||||
|
module AttributeMethods #:nodoc:
|
||||||
|
DEFAULT_SUFFIXES = %w(= ? _before_type_cast)
|
||||||
|
|
||||||
|
def self.included(base)
|
||||||
|
base.extend ClassMethods
|
||||||
|
base.attribute_method_suffix *DEFAULT_SUFFIXES
|
||||||
|
end
|
||||||
|
|
||||||
|
# Declare and check for suffixed attribute methods.
|
||||||
|
module ClassMethods
|
||||||
|
# Declare a method available for all attributes with the given suffix.
|
||||||
|
# Uses method_missing and respond_to? to rewrite the method
|
||||||
|
# #{attr}#{suffix}(*args, &block)
|
||||||
|
# to
|
||||||
|
# attribute#{suffix}(#{attr}, *args, &block)
|
||||||
|
#
|
||||||
|
# An attribute#{suffix} instance method must exist and accept at least
|
||||||
|
# the attr argument.
|
||||||
|
#
|
||||||
|
# For example:
|
||||||
|
# class Person < ActiveRecord::Base
|
||||||
|
# attribute_method_suffix '_changed?'
|
||||||
|
#
|
||||||
|
# private
|
||||||
|
# def attribute_changed?(attr)
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# person = Person.find(1)
|
||||||
|
# person.name_changed? # => false
|
||||||
|
# person.name = 'Hubert'
|
||||||
|
# person.name_changed? # => true
|
||||||
|
def attribute_method_suffix(*suffixes)
|
||||||
|
attribute_method_suffixes.concat suffixes
|
||||||
|
rebuild_attribute_method_regexp
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns MatchData if method_name is an attribute method.
|
||||||
|
def match_attribute_method?(method_name)
|
||||||
|
rebuild_attribute_method_regexp unless defined?(@@attribute_method_regexp) && @@attribute_method_regexp
|
||||||
|
@@attribute_method_regexp.match(method_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
# Suffixes a, ?, c become regexp /(a|\?|c)$/
|
||||||
|
def rebuild_attribute_method_regexp
|
||||||
|
suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) }
|
||||||
|
@@attribute_method_regexp = /(#{suffixes.join('|')})$/.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
# Default to =, ?, _before_type_cast
|
||||||
|
def attribute_method_suffixes
|
||||||
|
@@attribute_method_suffixes ||= []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
# Handle *? for method_missing.
|
||||||
|
def attribute?(attribute_name)
|
||||||
|
query_attribute(attribute_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Handle *= for method_missing.
|
||||||
|
def attribute=(attribute_name, value)
|
||||||
|
write_attribute(attribute_name, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Handle *_before_type_cast for method_missing.
|
||||||
|
def attribute_before_type_cast(attribute_name)
|
||||||
|
read_attribute_before_type_cast(attribute_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1671,13 +1671,13 @@ module ActiveRecord #:nodoc:
|
||||||
# person.respond_to?("name?") which will all return true.
|
# person.respond_to?("name?") which will all return true.
|
||||||
def respond_to?(method, include_priv = false)
|
def respond_to?(method, include_priv = false)
|
||||||
if @attributes.nil?
|
if @attributes.nil?
|
||||||
return super
|
return super
|
||||||
elsif attr_name = self.class.column_methods_hash[method.to_sym]
|
elsif attr_name = self.class.column_methods_hash[method.to_sym]
|
||||||
return true if @attributes.include?(attr_name) || attr_name == self.class.primary_key
|
return true if @attributes.include?(attr_name) || attr_name == self.class.primary_key
|
||||||
return false if self.class.read_methods.include?(attr_name)
|
return false if self.class.read_methods.include?(attr_name)
|
||||||
elsif @attributes.include?(method_name = method.to_s)
|
elsif @attributes.include?(method_name = method.to_s)
|
||||||
return true
|
return true
|
||||||
elsif md = /(=|\?|_before_type_cast)$/.match(method_name)
|
elsif md = self.class.match_attribute_method?(method.to_s)
|
||||||
return true if @attributes.include?(md.pre_match)
|
return true if @attributes.include?(md.pre_match)
|
||||||
end
|
end
|
||||||
# super must be called at the end of the method, because the inherited respond_to?
|
# super must be called at the end of the method, because the inherited respond_to?
|
||||||
|
@ -1750,6 +1750,7 @@ module ActiveRecord #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Allows access to the object attributes, which are held in the @attributes hash, as were
|
# Allows access to the object attributes, which are held in the @attributes hash, as were
|
||||||
# they first-class methods. So a Person class with a name attribute can use Person#name and
|
# they first-class methods. So a Person class with a name attribute can use Person#name and
|
||||||
# Person#name= and never directly use the attributes hash -- except for multiple assigns with
|
# Person#name= and never directly use the attributes hash -- except for multiple assigns with
|
||||||
|
@ -1767,15 +1768,10 @@ module ActiveRecord #:nodoc:
|
||||||
md ? query_attribute(method_name) : read_attribute(method_name)
|
md ? query_attribute(method_name) : read_attribute(method_name)
|
||||||
elsif self.class.primary_key.to_s == method_name
|
elsif self.class.primary_key.to_s == method_name
|
||||||
id
|
id
|
||||||
elsif md = /(=|_before_type_cast)$/.match(method_name)
|
elsif md = self.class.match_attribute_method?(method_name)
|
||||||
attribute_name, method_type = md.pre_match, md.to_s
|
attribute_name, method_type = md.pre_match, md.to_s
|
||||||
if @attributes.include?(attribute_name)
|
if @attributes.include?(attribute_name)
|
||||||
case method_type
|
__send__("attribute#{method_type}", attribute_name, *args, &block)
|
||||||
when '='
|
|
||||||
write_attribute(attribute_name, args.first)
|
|
||||||
when '_before_type_cast'
|
|
||||||
read_attribute_before_type_cast(attribute_name)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
30
activerecord/test/attribute_methods_test.rb
Executable file
30
activerecord/test/attribute_methods_test.rb
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
require 'abstract_unit'
|
||||||
|
|
||||||
|
class AttributeMethodsTest < Test::Unit::TestCase
|
||||||
|
def setup
|
||||||
|
@target = Class.new(ActiveRecord::Base)
|
||||||
|
@target.table_name = 'topics'
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_match_attribute_method_query_returns_match_data
|
||||||
|
assert_not_nil md = @target.match_attribute_method?('title=')
|
||||||
|
assert_equal 'title', md.pre_match
|
||||||
|
assert_equal ['='], md.captures
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_declared_attribute_method_affects_respond_to_and_method_missing
|
||||||
|
topic = @target.new(:title => 'Budget')
|
||||||
|
assert topic.respond_to?('title')
|
||||||
|
assert_equal 'Budget', topic.title
|
||||||
|
assert !topic.respond_to?('title_hello_world')
|
||||||
|
assert_raise(NoMethodError) { topic.title_hello_world }
|
||||||
|
|
||||||
|
@target.class_eval "def attribute_hello_world(*args) args end"
|
||||||
|
@target.attribute_method_suffix '_hello_world'
|
||||||
|
|
||||||
|
assert topic.respond_to?('title_hello_world')
|
||||||
|
assert_equal ['title'], topic.title_hello_world
|
||||||
|
assert_equal ['title', 'a'], topic.title_hello_world('a')
|
||||||
|
assert_equal ['title', 1, 2, 3], topic.title_hello_world(1, 2, 3)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue