diff --git a/README.rdoc b/README.rdoc index 2848350..62a3255 100644 --- a/README.rdoc +++ b/README.rdoc @@ -266,6 +266,20 @@ corner cases a little harder to test. In general if you are using procs or model methods and you only care when they return a value, then they should return nil in all other situations. Restrictions are skipped if they are nil. + +=== DISPLAY INVALID VALUES IN DATE HELPERS: + +The plugin has some extensions to ActionView and ActiveRecord by allowing invalid +date and time values to be redisplayed to the user as feedback, instead of +a blank field which happens by default in Rails. Though the date helpers make this a +pretty rare occurence, given the select dropdowns for each date/time component, but +it may be something of interest. + +To activate it, put this in an initializer: + + ValidatesTimeliness.enable_datetime_select_extension! + + === OTHER CUSTOMISATION: The error messages for each temporal restrictions can also be globally overridden by @@ -302,12 +316,22 @@ will be inserted. And for something a little more specific you can override the format of the interpolation values inserted in the error messages for temporal restrictions like so +For Rails 2.0/2.1: + ValidatesTimeliness::Validator.error_value_formats.update( :time => '%H:%M:%S', :date => '%Y-%m-%d', :datetime => '%Y-%m-%d %H:%M:%S' ) +Rails 2.2+ using the I18n system to define new defaults: + + validates_timeliness: + error_value_formats: + date: '%Y-%m-%d' + time: '%H:%M:%S' + datetime: '%Y-%m-%d %H:%M:%S' + Those are Ruby strftime formats not the plugin formats. diff --git a/lib/validates_timeliness.rb b/lib/validates_timeliness.rb index 471db46..fd6ef54 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 @@ -31,25 +34,18 @@ module ValidatesTimeliness I18n.load_path += [ LOCALE_PATH ] I18n.reload! else - messages = YAML::load(IO.read(LOCALE_PATH)) - errors = messages['en']['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h } + defaults = YAML::load(IO.read(LOCALE_PATH))['en'] + errors = defaults['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h } ::ActiveRecord::Errors.default_error_messages.update(errors) + + ValidatesTimeliness::Validator.error_value_formats = defaults['validates_timeliness']['error_value_formats'].symbolize_keys end end - def default_error_messages - if Rails::VERSION::STRING < '2.2' - ::ActiveRecord::Errors.default_error_messages - else - I18n.translate('activerecord.errors.messages') - end - 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 + load_error_messages end end end diff --git a/lib/validates_timeliness/action_view/instance_tag.rb b/lib/validates_timeliness/action_view/instance_tag.rb index 98529f8..48f4c5b 100644 --- a/lib/validates_timeliness/action_view/instance_tag.rb +++ b/lib/validates_timeliness/action_view/instance_tag.rb @@ -37,7 +37,7 @@ module ValidatesTimeliness return value_without_timeliness(object) end - time_array = ParseDate.parsedate(raw_value) + time_array = ValidatesTimeliness::Formats.parse(raw_value, :datetime) TimelinessDateTime.new(*time_array[0..5]) end diff --git a/lib/validates_timeliness/active_record/attribute_methods.rb b/lib/validates_timeliness/active_record/attribute_methods.rb index 1f770ac..a115066 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) unless frozen? @@ -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/formats.rb b/lib/validates_timeliness/formats.rb index 1e3f3ba..7082752 100644 --- a/lib/validates_timeliness/formats.rb +++ b/lib/validates_timeliness/formats.rb @@ -124,13 +124,13 @@ module ValidatesTimeliness { 's' => [ /s{1}/, '(\d{1,2})', :sec ] }, { 'u' => [ /u{1,}/, '(\d{1,6})', :usec ] }, { 'ampm' => [ /ampm/, '((?:[aApP])\.?[mM]\.?)', :meridian ] }, - { 'zo' => [ /zo/, '(?:[+-]\d{2}:?\d{2})'] }, + { 'zo' => [ /zo/, '([+-]\d{2}:?\d{2})', :offset ] }, { 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] }, { '_' => [ /_/, '\s?' ] } ] - # Arguments whichs will be passed to the format proc if matched in the - # time string. The key must should the key from the format tokens. The array + # Arguments which will be passed to the format proc if matched in the + # time string. The key must be the key from the format tokens. The array # consists of the arry position of the arg, the arg name, and the code to # place in the time array slot. The position can be nil which means the arg # won't be placed in the array. @@ -146,6 +146,7 @@ module ValidatesTimeliness :min => [4, 'n', 'n'], :sec => [5, 's', 's'], :usec => [6, 'u', 'microseconds(u)'], + :offset => [7, 'z', 'offset_in_seconds(z)'], :meridian => [nil, 'md', nil] } @@ -161,12 +162,13 @@ module ValidatesTimeliness # pre or post match strings to exist if strict is false. Otherwise wrap # regexp in start and end anchors. # Returns 7 part time array. - def parse(string, type, strict=true) + def parse(string, type, options={}) return string unless string.is_a?(String) + options.reverse_merge!(:strict => true) matches = nil exp, processor = expression_set(type, string).find do |regexp, proc| - full = /\A#{regexp}\Z/ if strict + full = /\A#{regexp}\Z/ if options[:strict] full ||= case type when :date then /\A#{regexp}/ when :time then /#{regexp}\Z/ @@ -174,7 +176,8 @@ module ValidatesTimeliness end matches = full.match(string.strip) end - processor.call(*matches[1..7]) if matches + last = options[:include_offset] ? 8 : 7 + processor.call(*matches[1..last]) if matches end # Delete formats of specified type. Error raised if format not found. @@ -206,8 +209,7 @@ module ValidatesTimeliness end compile_format_expressions end - - + # Removes formats where the 1 or 2 digit month comes first, to eliminate # formats which are ambiguous with the European style of day then month. # The mmm token is ignored as its not ambigous. @@ -246,17 +248,12 @@ module ValidatesTimeliness # argument in the position indicated by the first element of the proc arg # array. # - # Examples: - # - # 'yyyy-mm-dd hh:nn' => lambda {|y,m,d,h,n| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } } - # 'dd/mm/yyyy h:nn_ampm' => lambda {|d,m,y,h,n,md| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } } - # def format_proc(order) arg_map = format_proc_args args = order.invert.sort.map {|p| arg_map[p[1]][1] } arr = [nil] * 7 order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? } - proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.to_i } }" + proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.is_a?(Float) ? i : i.to_i } }" eval proc_string end @@ -313,6 +310,13 @@ module ValidatesTimeliness def microseconds(usec) (".#{usec}".to_f * 1_000_000).to_i end + + def offset_in_seconds(offset) + sign = offset =~ /^-/ ? -1 : 1 + parts = offset.scan(/\d\d/).map {|p| p.to_f } + parts[1] = parts[1].to_f / 60 + (parts[0] + parts[1]) * sign * 3600 + end end end end diff --git a/lib/validates_timeliness/locale/en.yml b/lib/validates_timeliness/locale/en.yml index f8731c7..3b249a6 100644 --- a/lib/validates_timeliness/locale/en.yml +++ b/lib/validates_timeliness/locale/en.yml @@ -11,3 +11,8 @@ en: after: "must be after {{restriction}}" on_or_after: "must be on or after {{restriction}}" between: "must be between {{earliest}} and {{latest}}" + validates_timeliness: + error_value_formats: + date: '%Y-%m-%d' + time: '%H:%M:%S' + datetime: '%Y-%m-%d %H:%M:%S' diff --git a/lib/validates_timeliness/parser.rb b/lib/validates_timeliness/parser.rb new file mode 100644 index 0000000..7b7845c --- /dev/null +++ b/lib/validates_timeliness/parser.rb @@ -0,0 +1,46 @@ +module ValidatesTimeliness + module Parser + + class << self + + def parse(raw_value, type, options={}) + return nil if raw_value.blank? + return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date) + + options.reverse_merge!(:strict => true) + + time_array = ValidatesTimeliness::Formats.parse(raw_value, type, options) + 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 + + make_time(time_array[0..7]) + 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/spec/rails/matchers/validate_timeliness.rb b/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb index f215554..6587415 100644 --- a/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb +++ b/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb @@ -116,7 +116,7 @@ module Spec end def error_message_for(option) - msg = @validator.send(:error_messages)[option] + msg = @validator.error_messages[option] restriction = @validator.class.send(:evaluate_option_value, @validator.configuration[option], @type, @record) if restriction @@ -135,7 +135,7 @@ module Spec def format_value(value) return value if value.is_a?(String) - value.strftime(ValidatesTimeliness::Validator.error_value_formats[@type]) + value.strftime(@validator.class.error_value_formats[@type]) 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..06978c7 100644 --- a/lib/validates_timeliness/validator.rb +++ b/lib/validates_timeliness/validator.rb @@ -2,14 +2,7 @@ module ValidatesTimeliness class Validator cattr_accessor :ignore_restriction_errors - cattr_accessor :error_value_formats - self.ignore_restriction_errors = false - self.error_value_formats = { - :time => '%H:%M:%S', - :date => '%Y-%m-%d', - :datetime => '%Y-%m-%d %H:%M:%S' - } RESTRICTION_METHODS = { :equal_to => :==, @@ -36,7 +29,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, :strict => 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]) @@ -47,7 +40,11 @@ module ValidatesTimeliness validate_restrictions(record, attr_name, value) end - + + def error_messages + @error_messages ||= self.class.default_error_messages.merge(custom_error_messages) + end + private def raw_value(record, attr_name) @@ -87,7 +84,7 @@ module ValidatesTimeliness restriction = [restriction] unless restriction.is_a?(Array) if defined?(I18n) - message = custom_error_messages[option] || I18n.translate('activerecord.errors.messages')[option] + message = custom_error_messages[option] || I18n.t('activerecord.errors.messages')[option] subs = message.scan(/\{\{([^\}]*)\}\}/) interpolations = {} subs.each_with_index {|s, i| interpolations[s[0].to_sym] = restriction[i].strftime(format) } @@ -120,10 +117,6 @@ module ValidatesTimeliness end end - def error_messages - @error_messages ||= ValidatesTimeliness.default_error_messages.merge(custom_error_messages) - end - def custom_error_messages @custom_error_messages ||= configuration.inject({}) {|msgs, (k, v)| if md = /(.*)_message$/.match(k.to_s) @@ -132,7 +125,7 @@ module ValidatesTimeliness msgs } end - + def combine_date_and_time(value, record) if type == :date date = value @@ -143,7 +136,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) @@ -156,9 +149,29 @@ module ValidatesTimeliness # class methods class << self + def default_error_messages + if defined?(I18n) + I18n.t('activerecord.errors.messages') + else + ::ActiveRecord::Errors.default_error_messages + end + end + + def error_value_formats + if defined?(I18n) + I18n.t('validates_timeliness.error_value_formats') + else + @@error_value_formats + end + end + + def error_value_formats=(formats) + @@error_value_formats = formats + end + 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 +182,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, :strict => false) end end @@ -192,7 +205,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 1a0077f..18e267a 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/formats_spec.rb b/spec/formats_spec.rb index a166a76..2c233c9 100644 --- a/spec/formats_spec.rb +++ b/spec/formats_spec.rb @@ -6,46 +6,6 @@ describe ValidatesTimeliness::Formats do before do @formats = ValidatesTimeliness::Formats end - - describe "expression generator" do - it "should generate regexp for time" do - generate_regexp_str('hh:nn:ss').should == '/(\d{2}):(\d{2}):(\d{2})/' - end - - it "should generate regexp for time with meridian" do - generate_regexp_str('hh:nn:ss ampm').should == '/(\d{2}):(\d{2}):(\d{2}) ((?:[aApP])\.?[mM]\.?)/' - end - - it "should generate regexp for time with meridian and optional space between" do - generate_regexp_str('hh:nn:ss_ampm').should == '/(\d{2}):(\d{2}):(\d{2})\s?((?:[aApP])\.?[mM]\.?)/' - end - - it "should generate regexp for time with single or double digits" do - generate_regexp_str('h:n:s').should == '/(\d{1,2}):(\d{1,2}):(\d{1,2})/' - end - - it "should generate regexp for date" do - generate_regexp_str('yyyy-mm-dd').should == '/(\d{4})-(\d{2})-(\d{2})/' - end - - it "should generate regexp for date with slashes" do - generate_regexp_str('dd/mm/yyyy').should == '/(\d{2})\/(\d{2})\/(\d{4})/' - end - - it "should generate regexp for date with dots" do - generate_regexp_str('dd.mm.yyyy').should == '/(\d{2})\.(\d{2})\.(\d{4})/' - end - - it "should generate regexp for Ruby time string" do - expected = '/(\w{3,9}) (\w{3,9}) (\d{2}):(\d{2}):(\d{2}) (?:[+-]\d{2}:?\d{2}) (\d{4})/' - generate_regexp_str('ddd mmm hh:nn:ss zo yyyy').should == expected - end - - it "should generate regexp for iso8601 datetime" do - expected = '/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:Z|(?:[+-]\d{2}:?\d{2}))/' - generate_regexp_str('yyyy-mm-ddThh:nn:ss(?:Z|zo)').should == expected - end - end describe "format proc generator" do it "should generate proc which outputs date array with values in correct order" do @@ -71,6 +31,10 @@ describe ValidatesTimeliness::Formats do it "should generate proc which outputs time array with microseconds" do generate_proc('hh:nn:ss.u').call('01', '02', '03', '99').should == [0,0,0,1,2,3,990000] end + + it "should generate proc which outputs datetime array with zone offset" do + generate_proc('yyyy-mm-dd hh:nn:ss.u zo').call('2001', '02', '03', '04', '05', '06', '99', '+10:00').should == [2001,2,3,4,5,6,990000,36000] + end end describe "validation regexps" do @@ -139,38 +103,43 @@ describe ValidatesTimeliness::Formats do describe "extracting values" do it "should return time array from date string" do - time_array = formats.parse('12:13:14', :time, true) + time_array = formats.parse('12:13:14', :time, :strict => true) time_array.should == [0,0,0,12,13,14,0] end it "should return date array from time string" do - time_array = formats.parse('2000-02-01', :date, true) + time_array = formats.parse('2000-02-01', :date, :strict => true) time_array.should == [2000,2,1,0,0,0,0] end it "should return datetime array from string value" do - time_array = formats.parse('2000-02-01 12:13:14', :datetime, true) + time_array = formats.parse('2000-02-01 12:13:14', :datetime, :strict => true) time_array.should == [2000,2,1,12,13,14,0] end it "should parse date string when type is datetime" do - time_array = formats.parse('2000-02-01', :datetime, false) + time_array = formats.parse('2000-02-01', :datetime, :strict => false) time_array.should == [2000,2,1,0,0,0,0] end it "should ignore time when extracting date and strict is false" do - time_array = formats.parse('2000-02-01 12:12', :date, false) + time_array = formats.parse('2000-02-01 12:13', :date, :strict => false) time_array.should == [2000,2,1,0,0,0,0] end it "should ignore time when extracting date from format with trailing year and strict is false" do - time_array = formats.parse('01-02-2000 12:12', :date, false) + time_array = formats.parse('01-02-2000 12:13', :date, :strict => false) time_array.should == [2000,2,1,0,0,0,0] end it "should ignore date when extracting time and strict is false" do - time_array = formats.parse('2000-02-01 12:12', :time, false) - time_array.should == [0,0,0,12,12,0,0] + time_array = formats.parse('2000-02-01 12:13', :time, :strict => false) + time_array.should == [0,0,0,12,13,0,0] + end + + it "should return zone offset when :include_offset options is true" do + time_array = formats.parse('2000-02-01T12:13:14-10:30', :datetime, :include_offset => true) + time_array.should == [2000,2,1,12,13,14,0,-37800] end end diff --git a/spec/validation_methods_spec.rb b/spec/parser_spec.rb similarity index 58% rename from spec/validation_methods_spec.rb rename to spec/parser_spec.rb index ab0f2cd..b57d211 100644 --- a/spec/validation_methods_spec.rb +++ b/spec/parser_spec.rb @@ -1,39 +1,39 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper') -describe ValidatesTimeliness::ValidationMethods do +describe ValidatesTimeliness::Parser do attr_accessor :person - describe "parse_date_time" do + describe "parse" do it "should return time object for valid time string" do - parse_method("2000-01-01 12:13:14", :datetime).should be_kind_of(Time) + 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_method("2000-02-30 12:13:14", :datetime).should be_nil + 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_method("2000-02-01 25:13:14", :datetime).should be_nil + parse("2000-02-01 25:13:14", :datetime).should be_nil end it "should return Time object when passed a Time object" do - parse_method(Time.now, :datetime).should be_kind_of(Time) + 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_method("2000-01-01 12:13:14", :datetime) + 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_method("2000-02-30", :date).should be_nil + parse("2000-02-30", :date).should be_nil end - def parse_method(*args) - ActiveRecord::Base.parse_date_time(*args) + def parse(*args) + ValidatesTimeliness::Parser.parse(*args) end end @@ -43,14 +43,14 @@ describe ValidatesTimeliness::ValidationMethods do it "should create time using current timezone" do Time.zone = 'Melbourne' - time = ActiveRecord::Base.send(:make_time, [2000,1,1,12,0,0]) + 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 = ActiveRecord::Base.send(:make_time, [2000,1,1,12,0,0]) + time = ValidatesTimeliness::Parser.make_time([2000,1,1,12,0,0]) time.zone.should == "UTC" end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4425bea..55f542f 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,15 @@ 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' + +ValidatesTimeliness.enable_datetime_select_extension! + 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..5eff1d9 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) @@ -554,12 +554,18 @@ describe ValidatesTimeliness::Validator do describe "custom formats" do before :all do - @@formats = ValidatesTimeliness::Validator.error_value_formats - ValidatesTimeliness::Validator.error_value_formats = { + custom = { :time => '%H:%M %p', :date => '%d-%m-%Y', :datetime => '%d-%m-%Y %H:%M %p' } + + if defined?(I18n) + I18n.backend.store_translations 'en', :validates_timeliness => { :error_value_formats => custom } + else + @@formats = ValidatesTimeliness::Validator.error_value_formats + ValidatesTimeliness::Validator.error_value_formats = custom + end end it "should format datetime value of restriction" do @@ -581,12 +587,20 @@ describe ValidatesTimeliness::Validator do end after :all do - ValidatesTimeliness::Validator.error_value_formats = @@formats + if defined?(I18n) + I18n.reload! + else + ValidatesTimeliness::Validator.error_value_formats = @@formats + end end end end + def parse(*args) + ValidatesTimeliness::Parser.parse(*args) + end + def configure_validator(options={}) @validator = ValidatesTimeliness::Validator.new(options) end