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