diff options
author | Sean Griffin <sean@seantheprogrammer.com> | 2015-09-25 07:52:37 -0600 |
---|---|---|
committer | Sean Griffin <sean@seantheprogrammer.com> | 2015-09-25 07:54:38 -0600 |
commit | 37661bfc810da7384ac6650d60be8669baa16813 (patch) | |
tree | db005f8a472a46f19ef5be12e61fe98323f1d69b | |
parent | 73eec7ac4008c178b6cacf71a20457fcfbe7c515 (diff) | |
download | rails-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.rb | 55 | ||||
-rw-r--r-- | activerecord/CHANGELOG.md | 5 | ||||
-rw-r--r-- | activerecord/test/cases/validations_test.rb | 11 |
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 |