From 6e67d45274fd4c7ac88e5609ab969e8b097bf8c2 Mon Sep 17 00:00:00 2001 From: Adam Meehan Date: Wed, 29 Sep 2010 08:09:43 +1000 Subject: [PATCH] Validation support for ActiveModel::Validations without a shim move validation helpers into ActiveModel::Validations for default base support add check if attribute methods shim is being used refactor specs for helper and attribute methods separation more mongoid workarounds due to incorrect use of AS::Concern --- .../templates/validates_timeliness.rb | 2 +- lib/validates_timeliness/attribute_methods.rb | 29 +++++++++-------- .../extensions/date_time_select.rb | 4 +-- lib/validates_timeliness/helper_methods.rb | 24 ++++++-------- lib/validates_timeliness/orm/active_record.rb | 1 - lib/validates_timeliness/orm/mongoid.rb | 15 +++++++-- lib/validates_timeliness/validator.rb | 15 +++++++-- spec/spec_helper.rb | 16 +++++----- spec/test_model.rb | 32 ++++++++----------- .../attribute_methods_spec.rb | 17 ++++++++-- .../helper_methods_spec.rb | 14 +------- spec/validates_timeliness/validator_spec.rb | 1 + 12 files changed, 92 insertions(+), 78 deletions(-) diff --git a/lib/generators/validates_timeliness/templates/validates_timeliness.rb b/lib/generators/validates_timeliness/templates/validates_timeliness.rb index e8f6881..17fe414 100644 --- a/lib/generators/validates_timeliness/templates/validates_timeliness.rb +++ b/lib/generators/validates_timeliness/templates/validates_timeliness.rb @@ -1,5 +1,5 @@ ValidatesTimeliness.setup do |config| - # Add plugin to supported ORMs (:active_record, :mongoid) + # Extend ORM/ODMs for full support (:active_record, :mongoid). # config.extend_orms = [ :active_record ] # # User the plugin date/time parser which is stricter and extendable diff --git a/lib/validates_timeliness/attribute_methods.rb b/lib/validates_timeliness/attribute_methods.rb index 98408ad..aae8059 100644 --- a/lib/validates_timeliness/attribute_methods.rb +++ b/lib/validates_timeliness/attribute_methods.rb @@ -2,8 +2,24 @@ module ValidatesTimeliness module AttributeMethods extend ActiveSupport::Concern + included do + class_inheritable_accessor :timeliness_validated_attributes + self.timeliness_validated_attributes = [] + end + module ClassMethods + public + # Override in ORM shim + def timeliness_attribute_timezone_aware?(attr_name) + false + end + + # Override in ORM shim + def timeliness_attribute_type(attr_name) + :datetime + end + protected def define_timeliness_methods(before_type_cast=false) @@ -37,21 +53,9 @@ module ValidatesTimeliness EOV class_eval(method_body, __FILE__, line) end - - # Override in ORM shim - def timeliness_attribute_timezone_aware?(attr_name) - false - end - - # Override in ORM shim - def timeliness_attribute_type(attr_name) - :datetime - end - end module InstanceMethods - def _timeliness_raw_value_for(attr_name) @timeliness_cache && @timeliness_cache[attr_name.to_s] end @@ -59,7 +63,6 @@ module ValidatesTimeliness def _clear_timeliness_cache @timeliness_cache = {} end - end end diff --git a/lib/validates_timeliness/extensions/date_time_select.rb b/lib/validates_timeliness/extensions/date_time_select.rb index 9eacd7f..0ed4fdd 100644 --- a/lib/validates_timeliness/extensions/date_time_select.rb +++ b/lib/validates_timeliness/extensions/date_time_select.rb @@ -3,10 +3,10 @@ module ValidatesTimeliness module DateTimeSelect extend ActiveSupport::Concern - # Intercepts the date and time select helpers to reuse the values from the + # Intercepts the date and time select helpers to reuse the values from # the params rather than the parsed value. This allows invalid date/time # values to be redisplayed instead of blanks to aid correction by the user. - # Its a minor usability improvement which is rarely an issue for the user. + # It's a minor usability improvement which is rarely an issue for the user. included do alias_method_chain :datetime_selector, :timeliness diff --git a/lib/validates_timeliness/helper_methods.rb b/lib/validates_timeliness/helper_methods.rb index 601169d..3db97b7 100644 --- a/lib/validates_timeliness/helper_methods.rb +++ b/lib/validates_timeliness/helper_methods.rb @@ -1,15 +1,7 @@ -module ValidatesTimeliness - module HelperMethods - extend ActiveSupport::Concern +module ActiveModel + module Validations - included do - include ValidationMethods - extend ValidationMethods - class_inheritable_accessor :timeliness_validated_attributes - self.timeliness_validated_attributes = [] - end - - module ValidationMethods + module HelperMethods def validates_date(*attr_names) timeliness_validation_for attr_names, :date end @@ -24,11 +16,13 @@ module ValidatesTimeliness def timeliness_validation_for(attr_names, type) options = _merge_attributes(attr_names).merge(:type => type) - self.timeliness_validated_attributes ||= [] - self.timeliness_validated_attributes += (attr_names - self.timeliness_validated_attributes) - validates_with Validator, options + if respond_to?(:timeliness_validated_attributes) + self.timeliness_validated_attributes ||= [] + self.timeliness_validated_attributes += (attr_names - self.timeliness_validated_attributes) + end + validates_with ValidatesTimeliness::Validator, options end - end + end end diff --git a/lib/validates_timeliness/orm/active_record.rb b/lib/validates_timeliness/orm/active_record.rb index ce63bb1..5d8ccef 100644 --- a/lib/validates_timeliness/orm/active_record.rb +++ b/lib/validates_timeliness/orm/active_record.rb @@ -32,7 +32,6 @@ module ValidatesTimeliness end class ActiveRecord::Base - include ValidatesTimeliness::HelperMethods include ValidatesTimeliness::AttributeMethods include ValidatesTimeliness::ORM::ActiveRecord end diff --git a/lib/validates_timeliness/orm/mongoid.rb b/lib/validates_timeliness/orm/mongoid.rb index 62d1834..b323055 100644 --- a/lib/validates_timeliness/orm/mongoid.rb +++ b/lib/validates_timeliness/orm/mongoid.rb @@ -43,9 +43,18 @@ module ValidatesTimeliness end module Mongoid::Document - include ValidatesTimeliness::HelperMethods - include ValidatesTimeliness::AttributeMethods - include ValidatesTimeliness::ORM::Mongoid + # Due to how Mongoid misuses ActiveSupport::Concern, + # the only way to override a core component method is + # using an append_features hook. + # + module TimelinessConcern + def append_features(base) + super + base.send :include, ValidatesTimeliness::AttributeMethods + base.send :include, ValidatesTimeliness::ORM::Mongoid + end + end + extend TimelinessConcern def reload_with_timeliness _clear_timeliness_cache diff --git a/lib/validates_timeliness/validator.rb b/lib/validates_timeliness/validator.rb index 59afdd0..edeccae 100644 --- a/lib/validates_timeliness/validator.rb +++ b/lib/validates_timeliness/validator.rb @@ -37,10 +37,10 @@ module ValidatesTimeliness end def validate_each(record, attr_name, value) - raw_value = record._timeliness_raw_value_for(attr_name) || value + raw_value = attribute_raw_value(record, attr_name) || value return if (@allow_nil && raw_value.nil?) || (@allow_blank && raw_value.blank?) - @timezone_aware = record.class.timeliness_attribute_timezone_aware?(attr_name) + @timezone_aware = timezone_aware?(record, attr_name) value = parse(raw_value) if value.is_a?(String) || options[:format] value = type_cast_value(value, @type) @@ -66,6 +66,17 @@ module ValidatesTimeliness value.strftime(format) end + def attribute_raw_value(record, attr_name) + if record.respond_to?(:_timeliness_raw_value_for) + record._timeliness_raw_value_for(attr_name) + end + end + + def timezone_aware?(record, attr_name) + record.class.respond_to?(:timeliness_attribute_timezone_aware?) && + record.class.timeliness_attribute_timezone_aware?(attr_name) + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 13d2d0f..56aac25 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -24,15 +24,11 @@ LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/../lib/generators/vali I18n.load_path.unshift(LOCALE_PATH) # Extend TestModel as you would another ORM/ODM module -module TestModel - include ValidatesTimeliness::HelperMethods +module TestModelShim + extend ActiveSupport::Concern include ValidatesTimeliness::AttributeMethods - def self.included(base) - base.extend HookMethods - end - - module HookMethods + module ClassMethods # Hook method for attribute method generation def define_attribute_methods(attr_names) super @@ -57,6 +53,10 @@ class Person define_attribute_methods model_attributes.keys end +class PersonWithShim < Person + include TestModelShim +end + ActiveRecord::Base.time_zone_aware_attributes = true ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'}) ActiveRecord::Migration.verbose = false @@ -82,7 +82,7 @@ Rspec.configure do |c| c.include(RspecTagMatchers) c.before do Person.reset_callbacks(:validate) - Person.timeliness_validated_attributes = [] + PersonWithShim.timeliness_validated_attributes = [] Person._validators.clear Employee.reset_callbacks(:validate) Employee.timeliness_validated_attributes = [] diff --git a/spec/test_model.rb b/spec/test_model.rb index dd7801c..8bc0846 100644 --- a/spec/test_model.rb +++ b/spec/test_model.rb @@ -1,12 +1,10 @@ module TestModel - extend ActiveSupport::Concern + extend ActiveSupport::Concern + extend ActiveModel::Translation + include ActiveModel::Validations + include ActiveModel::AttributeMethods included do - extend ActiveModel::Translation - include ActiveModel::Validations - include ActiveModel::AttributeMethods - include DynamicMethods - attribute_method_suffix "" attribute_method_suffix "=" cattr_accessor :model_attributes @@ -32,18 +30,6 @@ module TestModel end end - module DynamicMethods - def method_missing(method_id, *args, &block) - if !self.class.attribute_methods_generated? - self.class.define_attribute_methods self.class.model_attributes.keys.map(&:to_s) - method_name = method_id.to_s - send(method_id, *args, &block) - else - super - end - end - end - def initialize(attributes = nil) @attributes = self.class.model_attributes.inject({}) do |hash, column| hash[column.to_s] = nil @@ -62,5 +48,15 @@ module TestModel end end + def method_missing(method_id, *args, &block) + if !self.class.attribute_methods_generated? + self.class.define_attribute_methods self.class.model_attributes.keys.map(&:to_s) + method_name = method_id.to_s + send(method_id, *args, &block) + else + super + end + end + end diff --git a/spec/validates_timeliness/attribute_methods_spec.rb b/spec/validates_timeliness/attribute_methods_spec.rb index 1de25ce..cd1f618 100644 --- a/spec/validates_timeliness/attribute_methods_spec.rb +++ b/spec/validates_timeliness/attribute_methods_spec.rb @@ -2,12 +2,24 @@ require 'spec_helper' describe ValidatesTimeliness::AttributeMethods do it 'should define _timeliness_raw_value_for instance method' do - Person.instance_methods.should include('_timeliness_raw_value_for') + PersonWithShim.instance_methods.should include('_timeliness_raw_value_for') + end + + describe ".timeliness_validated_attributes" do + it 'should return attributes validated with plugin validator' do + PersonWithShim.timeliness_validated_attributes = [] + PersonWithShim.validates_date :birth_date + PersonWithShim.validates_time :birth_time + PersonWithShim.validates_datetime :birth_datetime + + PersonWithShim.timeliness_validated_attributes.should == [ :birth_date, :birth_time, :birth_datetime ] + end end context "attribute write method" do class PersonWithCache include TestModel + include TestModelShim attribute :birth_date, :date attribute :birth_time, :time attribute :birth_datetime, :datetime @@ -25,6 +37,7 @@ describe ValidatesTimeliness::AttributeMethods do context "with plugin parser" do class PersonWithParser include TestModel + include TestModelShim attribute :birth_date, :date attribute :birth_time, :time attribute :birth_datetime, :datetime @@ -57,7 +70,7 @@ describe ValidatesTimeliness::AttributeMethods do context "before_type_cast method" do it 'should not be defined if ORM does not support it' do - Person.instance_methods(false).should_not include("birth_datetime_before_type_cast") + PersonWithShim.instance_methods(false).should_not include("birth_datetime_before_type_cast") end end end diff --git a/spec/validates_timeliness/helper_methods_spec.rb b/spec/validates_timeliness/helper_methods_spec.rb index cb0ab97..29b2ff8 100644 --- a/spec/validates_timeliness/helper_methods_spec.rb +++ b/spec/validates_timeliness/helper_methods_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe ValidatesTimeliness::HelperMethods do +describe ValidatesTimeliness, 'HelperMethods' do it 'should define class validation methods' do Person.should respond_to(:validates_date) Person.should respond_to(:validates_time) @@ -18,16 +18,4 @@ describe ValidatesTimeliness::HelperMethods do r.validates_date :birth_date r.errors[:birth_date].should_not be_empty end - - describe ".timeliness_validated_attributes" do - it 'should return attributes validated with plugin validator' do - Person.timeliness_validated_attributes = [] - Person.validates_date :birth_date - Person.validates_time :birth_time - Person.validates_datetime :birth_datetime - - Person.timeliness_validated_attributes.should == [ :birth_date, :birth_time, :birth_datetime ] - end - end - end diff --git a/spec/validates_timeliness/validator_spec.rb b/spec/validates_timeliness/validator_spec.rb index 8968120..8554b58 100644 --- a/spec/validates_timeliness/validator_spec.rb +++ b/spec/validates_timeliness/validator_spec.rb @@ -101,6 +101,7 @@ describe ValidatesTimeliness::Validator do describe ":format option" do class PersonWithFormatOption include TestModel + include TestModelShim attribute :birth_date, :date attribute :birth_time, :time attribute :birth_datetime, :datetime