refactored AR parsing methods into Parser module to reduce AR method pollution and make more consistent

This commit is contained in:
Adam Meehan 2009-03-28 17:25:48 +11:00
parent 88fce1d679
commit 312c1510cb
10 changed files with 133 additions and 58 deletions

View File

@ -1,4 +1,5 @@
require 'validates_timeliness/formats'
require 'validates_timeliness/parser'
require 'validates_timeliness/validator'
require 'validates_timeliness/validation_methods'
require 'validates_timeliness/spec/rails/matchers/validate_timeliness' if ENV['RAILS_ENV'] == 'test'
@ -14,9 +15,11 @@ require 'validates_timeliness/core_ext/date_time'
module ValidatesTimeliness
mattr_accessor :default_timezone
self.default_timezone = :utc
mattr_accessor :use_time_zones
self.use_time_zones = false
LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/validates_timeliness/locale/en.yml')
class << self
@ -46,10 +49,10 @@ module ValidatesTimeliness
end
def setup_for_rails
major, minor = Rails::VERSION::MAJOR, Rails::VERSION::MINOR
self.default_timezone = ::ActiveRecord::Base.default_timezone
self.enable_datetime_select_extension!
self.load_error_messages
self.use_time_zones = ::ActiveRecord::Base.time_zone_aware_attributes rescue false
enable_datetime_select_extension!
load_error_messages
end
end
end

View File

@ -46,7 +46,7 @@ module ValidatesTimeliness
# implementation as it chains the write_attribute method which deletes
# the attribute from the cache.
def write_date_time_attribute(attr_name, value, type, time_zone_aware)
new = self.class.parse_date_time(value, type)
new = ValidatesTimeliness::Parser.parse(value, type)
if new && type != :date
new = new.to_time
@ -73,7 +73,7 @@ module ValidatesTimeliness
if @attributes_cache.has_key?(attr_name)
time = read_attribute_before_type_cast(attr_name)
time = self.class.parse_date_time(time, type)
time = ValidatesTimeliness::Parser.parse(time, type)
else
time = read_attribute(attr_name)
@attributes[attr_name] = time && time_zone_aware ? time.in_time_zone : time
@ -83,8 +83,6 @@ module ValidatesTimeliness
module ClassMethods
# Define attribute reader and writer method for date, time and
# datetime attributes to use plugin parser.
def define_attribute_methods_with_timeliness
return if generated_methods?
columns_hash.each do |name, column|
@ -105,7 +103,6 @@ module ValidatesTimeliness
define_attribute_methods_without_timeliness
end
# Define write method for date, time and datetime columns
def define_write_method_for_dates_and_times(attr_name, type, time_zone_aware)
method_body = <<-EOV
def #{attr_name}=(value)

View File

@ -38,7 +38,7 @@ module ValidatesTimeliness
end
def time_array_to_string(values, type)
values = values.map {|v| v.to_s }
values.collect! {|v| v.to_s }
case type
when :date

View File

@ -0,0 +1,45 @@
module ValidatesTimeliness
module Parser
class << self
def parse(raw_value, type, strict=true)
return nil if raw_value.blank?
return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
time_array = ValidatesTimeliness::Formats.parse(raw_value, type, strict)
raise if time_array.nil?
# Rails dummy time date part is defined as 2000-01-01
time_array[0..2] = 2000, 1, 1 if type == :time
# Date.new enforces days per month, unlike Time
date = Date.new(*time_array[0..2]) unless type == :time
return date if type == :date
# Create time object which checks time part, and return time object
make_time(time_array)
rescue
nil
end
def make_time(time_array)
if Time.respond_to?(:zone) && ValidatesTimeliness.use_time_zones
Time.zone.local(*time_array)
else
begin
time_zone = ValidatesTimeliness.default_timezone
Time.send(time_zone, *time_array)
rescue ArgumentError, TypeError
zone_offset = time_zone == :local ? DateTime.local_offset : 0
time_array.pop # remove microseconds
DateTime.civil(*(time_array << zone_offset))
end
end
end
end
end
end

View File

@ -7,27 +7,6 @@ module ValidatesTimeliness
module ClassMethods
def parse_date_time(raw_value, type, strict=true)
return nil if raw_value.blank?
return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
time_array = ValidatesTimeliness::Formats.parse(raw_value, type, strict)
raise if time_array.nil?
# Rails dummy time date part is defined as 2000-01-01
time_array[0..2] = 2000, 1, 1 if type == :time
# Date.new enforces days per month, unlike Time
date = Date.new(*time_array[0..2]) unless type == :time
return date if type == :date
# Create time object which checks time part, and return time object
make_time(time_array)
rescue
nil
end
def validates_time(*attr_names)
configuration = attr_names.extract_options!
configuration[:type] = :time
@ -59,21 +38,6 @@ module ValidatesTimeliness
end
end
# Time.zone. Rails 2.0 should be default_timezone.
def make_time(time_array)
if Time.respond_to?(:zone) && time_zone_aware_attributes
Time.zone.local(*time_array)
else
begin
Time.send(::ActiveRecord::Base.default_timezone, *time_array)
rescue ArgumentError, TypeError
zone_offset = ::ActiveRecord::Base.default_timezone == :local ? DateTime.local_offset : 0
time_array.pop # remove microseconds
DateTime.civil(*(time_array << zone_offset))
end
end
end
end
end

View File

@ -36,7 +36,7 @@ module ValidatesTimeliness
end
def call(record, attr_name, value)
value = record.class.parse_date_time(value, type, false) if value.is_a?(String)
value = ValidatesTimeliness::Parser.parse(value, type, false) if value.is_a?(String)
raw_value = raw_value(record, attr_name) || value
return if (raw_value.nil? && configuration[:allow_nil]) || (raw_value.blank? && configuration[:allow_blank])
@ -143,7 +143,7 @@ module ValidatesTimeliness
end
date, time = self.class.evaluate_option_value(date, :date, record), self.class.evaluate_option_value(time, :time, record)
return if date.nil? || time.nil?
record.class.send(:make_time, [date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
ValidatesTimeliness::Parser.make_time([date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
end
def validate_options(options)
@ -158,7 +158,7 @@ module ValidatesTimeliness
def evaluate_option_value(value, type, record)
case value
when Time, Date, DateTime
when Time, Date
value
when Symbol
evaluate_option_value(record.send(value), type, record)
@ -169,7 +169,7 @@ module ValidatesTimeliness
when Range
evaluate_option_value([value.first, value.last], type, record)
else
record.class.parse_date_time(value, type, false)
ValidatesTimeliness::Parser.parse(value, type, false)
end
end
@ -192,7 +192,7 @@ module ValidatesTimeliness
nil
end
if ignore_usec && value.is_a?(Time)
::ActiveRecord::Base.send(:make_time, Array(value).reverse[4..9])
ValidatesTimeliness::Parser.make_time(Array(value).reverse[4..9])
else
value
end

View File

@ -39,17 +39,17 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
end
it "should call parser on write for datetime attribute" do
@person.class.should_receive(:parse_date_time).once
ValidatesTimeliness::Parser.should_receive(:parse).once
@person.birth_date_and_time = "2000-01-01 02:03:04"
end
it "should call parser on write for date attribute" do
@person.class.should_receive(:parse_date_time).once
ValidatesTimeliness::Parser.should_receive(:parse).once
@person.birth_date = "2000-01-01"
end
it "should call parser on write for time attribute" do
@person.class.should_receive(:parse_date_time).once
ValidatesTimeliness::Parser.should_receive(:parse).once
@person.birth_time = "12:00"
end

61
spec/parser_spec.rb Normal file
View File

@ -0,0 +1,61 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
describe ValidatesTimeliness::Parser do
attr_accessor :person
describe "parse" do
it "should return time object for valid time string" do
parse("2000-01-01 12:13:14", :datetime).should be_kind_of(Time)
end
it "should return nil for time string with invalid date part" do
parse("2000-02-30 12:13:14", :datetime).should be_nil
end
it "should return nil for time string with invalid time part" do
parse("2000-02-01 25:13:14", :datetime).should be_nil
end
it "should return Time object when passed a Time object" do
parse(Time.now, :datetime).should be_kind_of(Time)
end
if RAILS_VER >= '2.1'
it "should convert time string into current timezone" do
Time.zone = 'Melbourne'
time = parse("2000-01-01 12:13:14", :datetime)
Time.zone.utc_offset.should == 10.hours
end
end
it "should return nil for invalid date string" do
parse("2000-02-30", :date).should be_nil
end
def parse(*args)
ValidatesTimeliness::Parser.parse(*args)
end
end
describe "make_time" do
if RAILS_VER >= '2.1'
it "should create time using current timezone" do
Time.zone = 'Melbourne'
time = ValidatesTimeliness::Parser.make_time([2000,1,1,12,0,0])
time.zone.should == "EST"
end
else
it "should create time using default timezone" do
time = ValidatesTimeliness::Parser.make_time([2000,1,1,12,0,0])
time.zone.should == "UTC"
end
end
end
end

View File

@ -30,6 +30,7 @@ require 'active_record'
require 'active_record/version'
require 'action_controller'
require 'action_view'
require 'action_mailer'
require 'spec/rails'
require 'time_travel/time_travel'
@ -38,13 +39,13 @@ ActiveRecord::Base.default_timezone = :utc
RAILS_VER = Rails::VERSION::STRING
puts "Using #{vendored ? 'vendored' : 'gem'} Rails version #{RAILS_VER} (ActiveRecord version #{ActiveRecord::VERSION::STRING})"
require 'validates_timeliness'
if RAILS_VER >= '2.1'
Time.zone_default = ActiveSupport::TimeZone['UTC']
ActiveRecord::Base.time_zone_aware_attributes = true
end
require 'validates_timeliness'
ActiveRecord::Migration.verbose = false
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})

View File

@ -66,7 +66,7 @@ describe ValidatesTimeliness::Validator do
it "should return array of Time objects when restriction is array of strings" do
time1, time2 = "2000-01-02", "2000-01-01"
evaluate_option_value([time1, time2], :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
evaluate_option_value([time1, time2], :datetime).should == [parse(time2, :datetime), parse(time1, :datetime)]
end
it "should return array of Time objects when restriction is Range of Time objects" do
@ -76,7 +76,7 @@ describe ValidatesTimeliness::Validator do
it "should return array of Time objects when restriction is Range of time strings" do
time1, time2 = "2000-01-02", "2000-01-01"
evaluate_option_value(time1..time2, :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
evaluate_option_value(time1..time2, :datetime).should == [parse(time2, :datetime), parse(time1, :datetime)]
end
def evaluate_option_value(restriction, type)
configure_validator(:type => type)
@ -587,6 +587,10 @@ describe ValidatesTimeliness::Validator do
end
def parse(*args)
ValidatesTimeliness::Parser.parse(*args)
end
def configure_validator(options={})
@validator = ValidatesTimeliness::Validator.new(options)
end