added strict_time_type_cast method to handle casting

refactored to be simler and rely on read_atribute method conversion
rather than use new reader method definer got time (Rails 2.1)
This commit is contained in:
Adam Meehan 2008-05-21 16:55:10 +10:00
parent d3b126729b
commit a9b033e539
4 changed files with 79 additions and 34 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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