diff --git a/lib/validates_timeliness.rb b/lib/validates_timeliness.rb index 471db46..99e7715 100644 --- a/lib/validates_timeliness.rb +++ b/lib/validates_timeliness.rb @@ -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 diff --git a/lib/validates_timeliness/active_record/attribute_methods.rb b/lib/validates_timeliness/active_record/attribute_methods.rb index 6ee1905..8630196 100644 --- a/lib/validates_timeliness/active_record/attribute_methods.rb +++ b/lib/validates_timeliness/active_record/attribute_methods.rb @@ -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) diff --git a/lib/validates_timeliness/active_record/multiparameter_attributes.rb b/lib/validates_timeliness/active_record/multiparameter_attributes.rb index a755e04..5727dfb 100644 --- a/lib/validates_timeliness/active_record/multiparameter_attributes.rb +++ b/lib/validates_timeliness/active_record/multiparameter_attributes.rb @@ -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 diff --git a/lib/validates_timeliness/parser.rb b/lib/validates_timeliness/parser.rb new file mode 100644 index 0000000..2f50f3e --- /dev/null +++ b/lib/validates_timeliness/parser.rb @@ -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 diff --git a/lib/validates_timeliness/validation_methods.rb b/lib/validates_timeliness/validation_methods.rb index a6f7a76..5d23b09 100644 --- a/lib/validates_timeliness/validation_methods.rb +++ b/lib/validates_timeliness/validation_methods.rb @@ -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 diff --git a/lib/validates_timeliness/validator.rb b/lib/validates_timeliness/validator.rb index 14ef01e..5b5d36a 100644 --- a/lib/validates_timeliness/validator.rb +++ b/lib/validates_timeliness/validator.rb @@ -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 diff --git a/spec/active_record/attribute_methods_spec.rb b/spec/active_record/attribute_methods_spec.rb index 5aba593..2cbad9c 100644 --- a/spec/active_record/attribute_methods_spec.rb +++ b/spec/active_record/attribute_methods_spec.rb @@ -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 diff --git a/spec/parser_spec.rb b/spec/parser_spec.rb new file mode 100644 index 0000000..b57d211 --- /dev/null +++ b/spec/parser_spec.rb @@ -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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4425bea..4d50035 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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:'}) diff --git a/spec/validator_spec.rb b/spec/validator_spec.rb index 963c2f7..a1d9c78 100644 --- a/spec/validator_spec.rb +++ b/spec/validator_spec.rb @@ -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