aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSean Griffin <sean@seantheprogrammer.com>2015-09-25 07:52:37 -0600
committerSean Griffin <sean@seantheprogrammer.com>2015-09-25 07:54:38 -0600
commit37661bfc810da7384ac6650d60be8669baa16813 (patch)
treedb005f8a472a46f19ef5be12e61fe98323f1d69b
parent73eec7ac4008c178b6cacf71a20457fcfbe7c515 (diff)
downloadrails-37661bfc810da7384ac6650d60be8669baa16813.tar.gz
rails-37661bfc810da7384ac6650d60be8669baa16813.tar.bz2
rails-37661bfc810da7384ac6650d60be8669baa16813.zip
`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.
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb55
-rw-r--r--activerecord/CHANGELOG.md5
-rw-r--r--activerecord/test/cases/validations_test.rb11
3 files changed, 67 insertions, 4 deletions
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 1bcfedb35d..c5c0cd4636 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -14,16 +14,63 @@ module ActiveModel
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
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 45c330b8af..6c0722e43c 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,8 @@
+* 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*
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index a429d06aad..d04f4f7ce7 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -168,4 +168,15 @@ class ValidationsTest < ActiveRecord::TestCase
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