diff --git a/lib/validates_timeliness/attribute_methods.rb b/lib/validates_timeliness/attribute_methods.rb index d67e59b..8e0350e 100644 --- a/lib/validates_timeliness/attribute_methods.rb +++ b/lib/validates_timeliness/attribute_methods.rb @@ -4,17 +4,20 @@ module ValidatesTimeliness module ClassMethods + protected + def define_timeliness_methods(before_type_cast=false) return if timeliness_validated_attributes.blank? - timeliness_validated_attributes.each do |attr_name, type| - define_timeliness_write_method(attr_name, type, timeliness_attribute_timezone_aware?(attr_name)) + timeliness_validated_attributes.each do |attr_name| + define_timeliness_write_method(attr_name) define_timeliness_before_type_cast_method(attr_name) if before_type_cast end end - protected + def define_timeliness_write_method(attr_name) + type = timeliness_attribute_type(attr_name) + timezone_aware = timeliness_attribute_timezone_aware?(attr_name) - def define_timeliness_write_method(attr_name, type, timezone_aware) method_body, line = <<-EOV, __LINE__ + 1 def #{attr_name}=(value) @attributes_cache ||= {} @@ -35,12 +38,16 @@ module ValidatesTimeliness class_eval(method_body, __FILE__, line) end - 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 + end module InstanceMethods diff --git a/lib/validates_timeliness/helper_methods.rb b/lib/validates_timeliness/helper_methods.rb index d5d5bb1..601169d 100644 --- a/lib/validates_timeliness/helper_methods.rb +++ b/lib/validates_timeliness/helper_methods.rb @@ -5,8 +5,8 @@ module ValidatesTimeliness included do include ValidationMethods extend ValidationMethods - class_inheritable_hash :timeliness_validated_attributes - self.timeliness_validated_attributes = {} + class_inheritable_accessor :timeliness_validated_attributes + self.timeliness_validated_attributes = [] end module ValidationMethods @@ -23,14 +23,9 @@ module ValidatesTimeliness end def timeliness_validation_for(attr_names, type) - options = _merge_attributes(attr_names) - options[:type] = type - attributes = attr_names.inject({}) {|validated, attr_name| - attr_name = attr_name.to_s - validated[attr_name] = type - validated - } - self.timeliness_validated_attributes = attributes + 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 end diff --git a/lib/validates_timeliness/orm/active_record.rb b/lib/validates_timeliness/orm/active_record.rb index 88bb7ee..aa4d1bf 100644 --- a/lib/validates_timeliness/orm/active_record.rb +++ b/lib/validates_timeliness/orm/active_record.rb @@ -2,13 +2,19 @@ class ActiveRecord::Base include ValidatesTimeliness::HelperMethods include ValidatesTimeliness::AttributeMethods - def self.define_attribute_methods - super - # Define write method and before_type_cast method - define_timeliness_methods(true) - end + class << self + def define_attribute_methods + super + # Define write method and before_type_cast method + define_timeliness_methods(true) + end - def self.timeliness_attribute_timezone_aware?(attr_name) - create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) + def timeliness_attribute_timezone_aware?(attr_name) + create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) + end + + def timeliness_attribute_type(attr_name) + columns_hash[attr_name.to_s].type + end end end diff --git a/lib/validates_timeliness/orm/mongoid.rb b/lib/validates_timeliness/orm/mongoid.rb index f027291..850b24e 100644 --- a/lib/validates_timeliness/orm/mongoid.rb +++ b/lib/validates_timeliness/orm/mongoid.rb @@ -7,12 +7,17 @@ module ValidatesTimeliness # field value in Mongoid. Parser will return nil rather than error. module ClassMethods + # Mongoid has no bulk attribute method definition hook. It defines + # them with each field definition. So we likewise define them after + # each validation is defined. + # def timeliness_validation_for(attr_names, type) super - attr_names.each { |attr_name| define_timeliness_write_method(attr_name, type, false) } + attr_names.each { |attr_name| define_timeliness_write_method(attr_name) } end - def define_timeliness_write_method(attr_name, type, timezone_aware) + def define_timeliness_write_method(attr_name) + type = timeliness_attribute_type(attr_name) method_body, line = <<-EOV, __LINE__ + 1 def #{attr_name}=(value) @attributes_cache ||= {} @@ -23,7 +28,16 @@ module ValidatesTimeliness EOV class_eval(method_body, __FILE__, line) end + + def timeliness_attribute_type(attr_name) + { + Date => :date, + Time => :datetime, + DateTime => :datetime + }[fields[attr_name.to_s].type] || :datetime + end end + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 72cb5d5..13d2d0f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -48,11 +48,13 @@ end class Person include TestModel - self.model_attributes = :birth_date, :birth_time, :birth_datetime + attribute :birth_date, :date + attribute :birth_time, :time + attribute :birth_datetime, :datetime validates_date :birth_date validates_time :birth_time validates_datetime :birth_datetime - define_attribute_methods model_attributes + define_attribute_methods model_attributes.keys end ActiveRecord::Base.time_zone_aware_attributes = true @@ -62,8 +64,8 @@ ActiveRecord::Schema.define(:version => 1) do create_table :employees, :force => true do |t| t.string :first_name t.string :last_name - t.datetime :birth_date - t.datetime :birth_time + t.date :birth_date + t.time :birth_time t.datetime :birth_datetime end end @@ -80,10 +82,10 @@ Rspec.configure do |c| c.include(RspecTagMatchers) c.before do Person.reset_callbacks(:validate) - Person.timeliness_validated_attributes = {} + Person.timeliness_validated_attributes = [] Person._validators.clear Employee.reset_callbacks(:validate) - Employee.timeliness_validated_attributes = {} + Employee.timeliness_validated_attributes = [] Employee._validators.clear end end diff --git a/spec/test_model.rb b/spec/test_model.rb index 169d6fa..dd7801c 100644 --- a/spec/test_model.rb +++ b/spec/test_model.rb @@ -13,24 +13,29 @@ module TestModel end module ClassMethods + def attribute(name, type) + self.model_attributes ||= {} + self.model_attributes[name] = type + end + def define_method_attribute=(attr_name) - generated_attribute_methods.module_eval("def #{attr_name}=(new_value); @attributes['#{attr_name}']=self.class.type_cast(new_value); end", __FILE__, __LINE__) + generated_attribute_methods.module_eval("def #{attr_name}=(new_value); @attributes['#{attr_name}']=self.class.type_cast('#{attr_name}', new_value); end", __FILE__, __LINE__) end def define_method_attribute(attr_name) generated_attribute_methods.module_eval("def #{attr_name}; @attributes['#{attr_name}']; end", __FILE__, __LINE__) end - def type_cast(value) + def type_cast(attr_name, value) return value unless value.is_a?(String) - value.to_time rescue nil + value.send("to_#{model_attributes[attr_name.to_sym]}") rescue nil 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.map(&:to_s) + 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 diff --git a/spec/validates_timeliness/attribute_methods_spec.rb b/spec/validates_timeliness/attribute_methods_spec.rb index 55e6161..1de25ce 100644 --- a/spec/validates_timeliness/attribute_methods_spec.rb +++ b/spec/validates_timeliness/attribute_methods_spec.rb @@ -8,7 +8,9 @@ describe ValidatesTimeliness::AttributeMethods do context "attribute write method" do class PersonWithCache include TestModel - self.model_attributes = :birth_date, :birth_time, :birth_datetime + attribute :birth_date, :date + attribute :birth_time, :time + attribute :birth_datetime, :datetime validates_date :birth_date validates_time :birth_time validates_datetime :birth_datetime @@ -23,7 +25,9 @@ describe ValidatesTimeliness::AttributeMethods do context "with plugin parser" do class PersonWithParser include TestModel - self.model_attributes = :birth_date, :birth_time, :birth_datetime + attribute :birth_date, :date + attribute :birth_time, :time + attribute :birth_datetime, :datetime validates_date :birth_date validates_time :birth_time validates_datetime :birth_datetime diff --git a/spec/validates_timeliness/helper_methods_spec.rb b/spec/validates_timeliness/helper_methods_spec.rb index 4a5a96b..cb0ab97 100644 --- a/spec/validates_timeliness/helper_methods_spec.rb +++ b/spec/validates_timeliness/helper_methods_spec.rb @@ -21,16 +21,12 @@ describe ValidatesTimeliness::HelperMethods do describe ".timeliness_validated_attributes" do it 'should return attributes validated with plugin validator' do - Person.timeliness_validated_attributes = {} + 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" => :date, - "birth_time" => :time, - "birth_datetime" => :datetime - } + Person.timeliness_validated_attributes.should == [ :birth_date, :birth_time, :birth_datetime ] end end diff --git a/spec/validates_timeliness/orm/active_record_spec.rb b/spec/validates_timeliness/orm/active_record_spec.rb index 27402c9..e35946a 100644 --- a/spec/validates_timeliness/orm/active_record_spec.rb +++ b/spec/validates_timeliness/orm/active_record_spec.rb @@ -16,8 +16,8 @@ describe ValidatesTimeliness, 'ActiveRecord' do end end - it 'should define _timeliness_raw_value_for instance method' do - Employee.instance_methods.should include('_timeliness_raw_value_for') + it 'should determine type for attribute' do + Employee.timeliness_attribute_type(:birth_date).should == :date end context "attribute write method" do diff --git a/spec/validates_timeliness/orm/mongoid_spec.rb b/spec/validates_timeliness/orm/mongoid_spec.rb index 8d6139e..992e834 100644 --- a/spec/validates_timeliness/orm/mongoid_spec.rb +++ b/spec/validates_timeliness/orm/mongoid_spec.rb @@ -38,8 +38,8 @@ describe ValidatesTimeliness, 'Mongoid' do end end - it 'should define _timeliness_raw_value_for instance method' do - Article.instance_methods.should include('_timeliness_raw_value_for') + it 'should determine type for attribute' do + Article.timeliness_attribute_type(:publish_date).should == :date end context "attribute write method" do diff --git a/spec/validates_timeliness/validator_spec.rb b/spec/validates_timeliness/validator_spec.rb index 08ce3e9..4d8522d 100644 --- a/spec/validates_timeliness/validator_spec.rb +++ b/spec/validates_timeliness/validator_spec.rb @@ -101,7 +101,9 @@ describe ValidatesTimeliness::Validator do describe ":format option" do class PersonWithFormatOption include TestModel - self.model_attributes = :birth_date, :birth_time, :birth_datetime + attribute :birth_date, :date + attribute :birth_time, :time + attribute :birth_datetime, :datetime validates_date :birth_date, :format => 'dd-mm-yyyy' end