-
Notifications
You must be signed in to change notification settings - Fork 21.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Don't rely on underscore-prefixed attribute methods.
Define singleton methods on the attributes module instead. This reduces method pollution on the actual model classes. It also seems to make something faster, I am unsure why! O_o
- Loading branch information
1 parent
365e10b
commit 3dcb127
Showing
3 changed files
with
80 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,60 +29,91 @@ def cache_attribute?(attr_name) | |
cached_attributes.include?(attr_name) | ||
end | ||
|
||
def undefine_attribute_methods | ||
if base_class == self | ||
generated_attribute_methods.module_eval do | ||
public_methods(false).each do |m| | ||
singleton_class.send(:undef_method, m) if m.to_s =~ /^cast_/ | ||
end | ||
end | ||
end | ||
|
||
super | ||
end | ||
|
||
protected | ||
# Where possible, generate the method by evalling a string, as this will result in | ||
# faster accesses because it avoids the block eval and then string eval incurred | ||
# by the second branch. | ||
# | ||
# The second, slower, branch is necessary to support instances where the database | ||
# returns columns with extra stuff in (like 'my_column(omg)'). | ||
def define_method_attribute(attr_name) | ||
define_read_method(attr_name, attr_name, columns_hash[attr_name]) | ||
access_code = attribute_access_code(attr_name) | ||
cast_code = "v && (#{attribute_cast_code(attr_name)})" | ||
|
||
if attr_name =~ ActiveModel::AttributeMethods::NAME_COMPILABLE_REGEXP | ||
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ | ||
def #{attr_name} | ||
#{access_code} | ||
end | ||
def self.cast_#{attr_name}(v) | ||
#{cast_code} | ||
end | ||
alias _#{attr_name} #{attr_name} | ||
STR | ||
else | ||
generated_attribute_methods.module_eval do | ||
define_method(attr_name) do | ||
eval(access_code) | ||
end | ||
|
||
singleton_class.send(:define_method, "cast_#{attr_name}") do |v| | ||
eval(cast_code) | ||
end | ||
|
||
alias_method("_#{attr_name}", attr_name) | ||
end | ||
end | ||
end | ||
|
||
private | ||
def cacheable_column?(column) | ||
attribute_types_cached_by_default.include?(column.type) | ||
end | ||
|
||
# Define an attribute reader method. Cope with nil column. | ||
# method_name is the same as attr_name except when a non-standard primary key is used, | ||
# we still define #id as an accessor for the key | ||
def define_read_method(method_name, attr_name, column) | ||
cast_code = column.type_cast_code('v') | ||
access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}" | ||
def attribute_access_code(attr_name) | ||
access_code = "(v=@attributes['#{attr_name}']) && #{attribute_cast_code(attr_name)}" | ||
|
||
unless attr_name.to_s == self.primary_key.to_s | ||
unless attr_name == self.primary_key | ||
access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ") | ||
end | ||
|
||
if cache_attribute?(attr_name) | ||
access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})" | ||
end | ||
|
||
# Where possible, generate the method by evalling a string, as this will result in | ||
# faster accesses because it avoids the block eval and then string eval incurred | ||
# by the second branch. | ||
# | ||
# The second, slower, branch is necessary to support instances where the database | ||
# returns columns with extra stuff in (like 'my_column(omg)'). | ||
if method_name =~ ActiveModel::AttributeMethods::NAME_COMPILABLE_REGEXP | ||
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ | ||
def _#{method_name} | ||
#{access_code} | ||
end | ||
access_code | ||
end | ||
|
||
alias #{method_name} _#{method_name} | ||
STR | ||
else | ||
generated_attribute_methods.module_eval do | ||
define_method("_#{method_name}") { eval(access_code) } | ||
alias_method(method_name, "_#{method_name}") | ||
end | ||
end | ||
def attribute_cast_code(attr_name) | ||
columns_hash[attr_name].type_cast_code('v') | ||
end | ||
end | ||
|
||
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, | ||
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). | ||
def read_attribute(attr_name) | ||
method = "_#{attr_name}" | ||
if respond_to? method | ||
send method if @attributes.has_key?(attr_name.to_s) | ||
attr_name = attr_name.to_s | ||
caster = "cast_#{attr_name}" | ||
methods = self.class.generated_attribute_methods | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
jonleighton
Author
Member
|
||
|
||
if methods.respond_to?(caster) | ||
if @attributes.has_key?(attr_name) | ||
@attributes_cache[attr_name] || methods.send(caster, @attributes[attr_name]) | ||
end | ||
else | ||
_read_attribute attr_name | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Single table inheritance is broken here.