提交 37661bfc 编写于 作者: S Sean Griffin

`validates_acceptance_of` shouldn't require a database connection

The implementation of `attribute_method?` on Active Record requires
establishing a database connection and querying the schema. As a general
rule, we don't want to require database connections for any class macro,
as the class should be able to be loaded without a database (e.g. for
things like compiling assets).

Instead of eagerly defining these methods, we do it lazily the first
time they are accessed via `method_missing`. This should not cause any
performance hits, as it will only hit `method_missing` once for the
entire class.
上级 73eec7ac
......@@ -14,16 +14,63 @@ def validate_each(record, attribute, value)
end
private
def setup!(klass)
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
klass.send(:attr_reader, *attr_readers)
klass.send(:attr_writer, *attr_writers)
klass.include(LazilyDefineAttributes.new(AttributeDefinition.new(attributes)))
end
def acceptable_option?(value)
Array(options[:accept]).include?(value)
end
class LazilyDefineAttributes < Module
def initialize(attribute_definition)
define_method(:respond_to_missing?) do |method_name, include_private=false|
super(method_name, include_private) || attribute_definition.matches?(method_name)
end
define_method(:method_missing) do |method_name, *args, &block|
if attribute_definition.matches?(method_name)
attribute_definition.define_on(self.class)
send(method_name, *args, &block)
else
super(method_name, *args, &block)
end
end
end
end
class AttributeDefinition
def initialize(attributes)
@attributes = attributes.map(&:to_s)
end
def matches?(method_name)
attr_name = convert_to_reader_name(method_name)
attributes.include?(attr_name)
end
def define_on(klass)
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
klass.send(:attr_reader, *attr_readers)
klass.send(:attr_writer, *attr_writers)
end
protected
attr_reader :attributes
private
def convert_to_reader_name(method_name)
attr_name = method_name.to_s
if attr_name.end_with?("=")
attr_name = attr_name[0..-2]
end
attr_name
end
end
end
module HelperMethods
......
* Don't require a database connection to load a class which uses acceptance
validations.
*Sean Griffin*
* Correctly apply `unscope` when preloading through associations.
*Jimmy Bourassa*
......
......@@ -168,4 +168,15 @@ def test_numericality_validation_with_mutation
ensure
Topic.reset_column_information
end
def test_acceptance_validator_doesnt_require_db_connection
klass = Class.new(ActiveRecord::Base) do
self.table_name = 'posts'
end
klass.reset_column_information
assert_no_queries do
klass.validates_acceptance_of(:foo)
end
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册