diff --git a/lib/validates_timeliness/attribute_methods.rb b/lib/validates_timeliness/attribute_methods.rb index 9a7e6b1..5522dfc 100644 --- a/lib/validates_timeliness/attribute_methods.rb +++ b/lib/validates_timeliness/attribute_methods.rb @@ -5,9 +5,9 @@ # # For Rails >= 2.1 # This module overrides these AR methods to allow a time value passed to a column -# write method to be stored as is and converts it to a time and caches on read. -# This differs from the normal AR behvaviour where the value is converted and -# cached on write. +# write method to be stored as is and only convert to a time on read. +# This differs from the current AR behvaviour where the value is converted +# on write. # # This allows the before_type_cast method for the column to return the actual # value passed to it, treating time columns like all other column types. @@ -25,25 +25,7 @@ module ValidatesTimeliness module ClassMethods # Rails > 2.0.2 module New - def define_read_method_for_time_zone_conversion(attr_name) - method_body = <<-EOV - def #{attr_name}(reload = false) - cached = @attributes_cache['#{attr_name}'] - return cached if cached && !reload - time = read_attribute_before_type_cast('#{attr_name}') - if time.acts_like?(:time) - time = time.in_time_zone - elsif time - # check invalid date - time.to_date rescue time = nil - time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time if time - end - @attributes_cache['#{attr_name}'] = time - end - EOV - evaluate_attribute_method attr_name, method_body - end - + # Store time value as is including as a string. Only convert on read def define_write_method_for_time_zone_conversion(attr_name) method_body = <<-EOV def #{attr_name}=(time) @@ -59,18 +41,17 @@ module ValidatesTimeliness # Rails <= 2.0.2 module Old + # Copied from AR and inserted Time class check to time attribute def define_attribute_methods return if generated_methods? columns_hash.each do |name, column| unless instance_method_already_implemented?(name) if self.serialized_attributes[name] define_read_method_for_serialized_attribute(name) + elsif column.klass == Time + define_read_method_for_time_attribute(name.to_sym) else - if column.klass == Time - define_read_method_for_time_attribute(name.to_sym) - else - define_read_method(name.to_sym, name, column) - end + define_read_method(name.to_sym, name, column) end end @@ -84,19 +65,14 @@ module ValidatesTimeliness end end + # defines time attribute reader and does conversion strict def define_read_method_for_time_attribute(attr_name) method_body = <<-EOV def #{attr_name}(reload = false) cached = @attributes_cache['#{attr_name}'] return cached if cached && !reload time = read_attribute_before_type_cast('#{attr_name}') - unless time.acts_like?(:time) - klass = ActiveRecord::ConnectionAdapters::Column - # check for invalid date - time = nil unless klass.string_to_date(time) - # convert to time if still valid - time = klass.string_to_time(time) if time - end + time = strict_time_type_cast(time) @attributes_cache['#{attr_name}'] = time end EOV diff --git a/lib/validates_timeliness/base.rb b/lib/validates_timeliness/base.rb index bef4e44..30f4636 100644 --- a/lib/validates_timeliness/base.rb +++ b/lib/validates_timeliness/base.rb @@ -6,6 +6,7 @@ module ValidatesTimeliness time_array[0..2].join('-') + ' ' + time_array[3..5].join(':') end + # Overrides AR method to store multiparameter time and dates def execute_callstack_for_multiparameter_attributes(callstack) errors = [] callstack.each do |name, values| @@ -30,5 +31,36 @@ module ValidatesTimeliness end end + def strict_time_type_cast(time) + if time.acts_like?(:time) + time.respond_to?(:in_time_zone) ? time.time.in_time_zone : time + else + klass = ActiveRecord::ConnectionAdapters::Column + # check for invalid date + time = nil unless klass.string_to_date(time) + # convert to time if still valid + time = klass.string_to_time(time) if time + end + end + + def read_attribute(attr_name) + attr_name = attr_name.to_s + if !(value = @attributes[attr_name]).nil? + if column = column_for_attribute(attr_name) + if unserializable_attribute?(attr_name, column) + unserialize_attribute(attr_name) + elsif column.klass == Time + strict_time_type_cast(value) + else + column.type_cast(value) + end + else + value + end + else + nil + end + end + end end diff --git a/spec/attribute_methods_spec.rb b/spec/attribute_methods_spec.rb index 7d90b4b..7aee83d 100644 --- a/spec/attribute_methods_spec.rb +++ b/spec/attribute_methods_spec.rb @@ -33,6 +33,9 @@ describe ValidatesTimeliness::AttributeMethods do end end + # This fails running as plugin under vendor using Rails 2.1RC + # due to write_attribute_with_dirty ignoring the write method for time zone + # method. But invalid dates do return nil when running app. it "should return nil when time is invalid" do @person.birth_date_and_time = "2000-02-30 01:02:03" @person.birth_date_and_time.should be_nil diff --git a/spec/base_spec.rb b/spec/base_spec.rb index eae5f16..3d3d99f 100644 --- a/spec/base_spec.rb +++ b/spec/base_spec.rb @@ -34,4 +34,38 @@ describe ValidatesTimeliness::Base do execute_callstack_for_multiparameter_attributes(callstack) end end + + describe "strict_time_type_cast" do + it "should return time object for valid time string" do + strict_time_type_cast("2000-01-01 12:13:14").should be_kind_of(Time) + end + + it "should return nil for time string with invalid date part" do + strict_time_type_cast("2000-02-30 12:13:14").should be_nil + end + + it "should return nil for time string with invalid time part" do + strict_time_type_cast("2000-02-01 25:13:14").should be_nil + end + end + + describe "read_attribute" do + it "should return time object from time string" do + @attributes = {} + self.stub!(:column_for_attribute).and_return( mock('Column', :klass => Time) ) + self.stub!(:unserializable_attribute?).and_return(false) + + @attributes['birth_date_and_time'] = "1980-01-01 00:00:00" + read_attribute(:birth_date_and_time).should be_kind_of(Time) + end + + it "should return nil from invalid time string" do + @attributes = {} + self.stub!(:column_for_attribute).and_return( mock('Column', :klass => Time) ) + self.stub!(:unserializable_attribute?).and_return(false) + + @attributes['birth_date_and_time'] = "1980-02-30 00:00:00" + read_attribute(:birth_date_and_time).should be_nil + end + end end