mirror of
https://github.com/ditkrg/validates_timeliness.git
synced 2026-01-26 07:43:00 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14c55e3292 | ||
|
|
75a3b2bd83 | ||
|
|
9b300e084b | ||
|
|
d61dddfbc6 | ||
|
|
4ac4281c8b | ||
|
|
a56cf674b2 | ||
|
|
7f4d7b38d7 | ||
|
|
34c0f25225 | ||
|
|
57c3fdca88 | ||
|
|
805bb5d7fd | ||
|
|
7aac14c874 | ||
|
|
7c0c1afe1c | ||
|
|
5295a494e9 | ||
|
|
572d80d227 |
@@ -1,3 +1,9 @@
|
|||||||
|
= 2.1.0 [2009-06-20]
|
||||||
|
- Added ambiguous year threshold setting in Formats class to customize the threshold for 2 digit years (See README)
|
||||||
|
- Fixed interpolation values in custom error message for Rails 2.2+
|
||||||
|
- Fixed custom I18n local override of en locale
|
||||||
|
- Dramatically simplified ActiveRecord monkey patching and hackery
|
||||||
|
|
||||||
= 2.0.0 [2009-04-12]
|
= 2.0.0 [2009-04-12]
|
||||||
- Error value formats are now specified in the i18n locale file instead of updating plugin hash. See OTHER CUSTOMISATION section in README.
|
- Error value formats are now specified in the i18n locale file instead of updating plugin hash. See OTHER CUSTOMISATION section in README.
|
||||||
- Date/time select helper extension is disabled by default. To enable see DISPLAY INVALID VALUES IN DATE HELPERS section in README to enable.
|
- Date/time select helper extension is disabled by default. To enable see DISPLAY INVALID VALUES IN DATE HELPERS section in README to enable.
|
||||||
|
|||||||
17
README.rdoc
17
README.rdoc
@@ -190,7 +190,7 @@ Here is what each format token means:
|
|||||||
|
|
||||||
Special Cases:
|
Special Cases:
|
||||||
yy = 2 or 4 digit year
|
yy = 2 or 4 digit year
|
||||||
yyyyy = exactly 4 digit year
|
yyyy = exactly 4 digit year
|
||||||
mmm = month long name (e.g. 'Jul' or 'July')
|
mmm = month long name (e.g. 'Jul' or 'July')
|
||||||
ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
|
ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
|
||||||
u = microseconds matches 1 to 3 digits
|
u = microseconds matches 1 to 3 digits
|
||||||
@@ -206,7 +206,7 @@ To see all defined formats look in lib/validates_timeliness/formats.rb.
|
|||||||
|
|
||||||
The perenial problem for non-US developers or applications not primarily for the
|
The perenial problem for non-US developers or applications not primarily for the
|
||||||
US, is the US date format of m/d/yy. This is ambiguous with the European format
|
US, is the US date format of m/d/yy. This is ambiguous with the European format
|
||||||
of d/my/yy. By default the plugin uses the US formats as this is the Ruby default
|
of d/m/yy. By default the plugin uses the US formats as this is the Ruby default
|
||||||
when it does date interpretation, and is in keeping PoLS (principle of least
|
when it does date interpretation, and is in keeping PoLS (principle of least
|
||||||
surprise).
|
surprise).
|
||||||
|
|
||||||
@@ -250,6 +250,19 @@ Now a time of '59:30:23' will be interpreted as 11:30:59 pm. This option saves
|
|||||||
you adding a new one and deleting an old one to get it to work.
|
you adding a new one and deleting an old one to get it to work.
|
||||||
|
|
||||||
|
|
||||||
|
=== AMBIGUOUS YEAR THRESHOLD
|
||||||
|
|
||||||
|
When dealing with 2 digit year values, by default a year is interpreted as being
|
||||||
|
in the last century at or above 30. You can customize this however
|
||||||
|
|
||||||
|
ValidatesTimeliness::Formats.ambiguous_year_threshold = 20
|
||||||
|
|
||||||
|
Now you get:
|
||||||
|
|
||||||
|
year of 19 is considered 2019
|
||||||
|
year of 20 is considered 1920
|
||||||
|
|
||||||
|
|
||||||
=== TEMPORAL RESTRICTION ERRORS:
|
=== TEMPORAL RESTRICTION ERRORS:
|
||||||
|
|
||||||
When using the validation temporal restrictions there are times when the restriction
|
When using the validation temporal restrictions there are times when the restriction
|
||||||
|
|||||||
4
Rakefile
4
Rakefile
@@ -5,7 +5,7 @@ require 'date'
|
|||||||
require 'spec/rake/spectask'
|
require 'spec/rake/spectask'
|
||||||
|
|
||||||
GEM = "validates_timeliness"
|
GEM = "validates_timeliness"
|
||||||
GEM_VERSION = "2.0.0"
|
GEM_VERSION = "2.1.0"
|
||||||
AUTHOR = "Adam Meehan"
|
AUTHOR = "Adam Meehan"
|
||||||
EMAIL = "adam.meehan@gmail.com"
|
EMAIL = "adam.meehan@gmail.com"
|
||||||
HOMEPAGE = "http://github.com/adzap/validates_timeliness"
|
HOMEPAGE = "http://github.com/adzap/validates_timeliness"
|
||||||
@@ -34,7 +34,7 @@ task :default => :spec
|
|||||||
desc "Run specs"
|
desc "Run specs"
|
||||||
Spec::Rake::SpecTask.new do |t|
|
Spec::Rake::SpecTask.new do |t|
|
||||||
t.spec_files = FileList['spec/**/*_spec.rb']
|
t.spec_files = FileList['spec/**/*_spec.rb']
|
||||||
t.spec_opts = %w(-fs --color)
|
t.spec_opts = %w(--color)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ module ValidatesTimeliness
|
|||||||
|
|
||||||
def load_error_messages
|
def load_error_messages
|
||||||
if defined?(I18n)
|
if defined?(I18n)
|
||||||
I18n.load_path += [ LOCALE_PATH ]
|
I18n.load_path.unshift(LOCALE_PATH)
|
||||||
I18n.reload!
|
I18n.reload!
|
||||||
else
|
else
|
||||||
defaults = YAML::load(IO.read(LOCALE_PATH))['en']
|
defaults = YAML::load(IO.read(LOCALE_PATH))['en']
|
||||||
@@ -45,6 +45,7 @@ module ValidatesTimeliness
|
|||||||
def setup_for_rails
|
def setup_for_rails
|
||||||
self.default_timezone = ::ActiveRecord::Base.default_timezone
|
self.default_timezone = ::ActiveRecord::Base.default_timezone
|
||||||
self.use_time_zones = ::ActiveRecord::Base.time_zone_aware_attributes rescue false
|
self.use_time_zones = ::ActiveRecord::Base.time_zone_aware_attributes rescue false
|
||||||
|
self.enable_active_record_datetime_parser!
|
||||||
load_error_messages
|
load_error_messages
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,124 +1,66 @@
|
|||||||
module ValidatesTimeliness
|
module ValidatesTimeliness
|
||||||
|
|
||||||
|
def self.enable_active_record_datetime_parser!
|
||||||
|
::ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::AttributeMethods)
|
||||||
|
end
|
||||||
|
|
||||||
module ActiveRecord
|
module ActiveRecord
|
||||||
|
|
||||||
# Rails 2.1 removed the ability to retrieve the raw value of a time or datetime
|
# Overrides write method for date, time and datetime columns
|
||||||
# attribute. The raw value is necessary to properly validate a string time or
|
# to use plugin parser. Also adds mechanism to store value
|
||||||
# datetime value instead of the internal Rails type casting which is very limited
|
# before type cast.
|
||||||
# and does not allow custom formats. These methods restore that ability while
|
|
||||||
# respecting the automatic timezone handling.
|
|
||||||
#
|
#
|
||||||
# The automatic timezone handling sets the assigned attribute value to the current
|
|
||||||
# zone in Time.zone. To preserve this localised value and capture the raw value
|
|
||||||
# we cache the localised value on write and store the raw value in the attributes
|
|
||||||
# hash for later retrieval and possibly validation. Any value from the database
|
|
||||||
# will not be in the attribute cache on first read so will be considered in default
|
|
||||||
# timezone and converted to local time. It is then stored back in the attributes
|
|
||||||
# hash and cached to avoid the need for any subsequent differentiation.
|
|
||||||
module AttributeMethods
|
module AttributeMethods
|
||||||
|
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
base.extend ClassMethods
|
base.extend ClassMethods
|
||||||
base.class_eval do
|
base.class_eval do
|
||||||
alias_method_chain :read_attribute, :timeliness
|
alias_method_chain :read_attribute_before_type_cast, :timeliness
|
||||||
class << self
|
class << self
|
||||||
alias_method_chain :define_attribute_methods, :timeliness
|
alias_method_chain :define_attribute_methods, :timeliness
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds check for cached date/time attributes which have been type cast already
|
|
||||||
# and value can be used from cache. This prevents the raw date/time value from
|
|
||||||
# being type cast using default Rails type casting when writing values
|
|
||||||
# to the database.
|
|
||||||
def read_attribute_with_timeliness(attr_name)
|
|
||||||
attr_name = attr_name.to_s
|
|
||||||
if !(value = @attributes[attr_name]).nil?
|
|
||||||
column = column_for_attribute(attr_name)
|
|
||||||
if column && [:date, :time, :datetime].include?(column.type) && @attributes_cache.has_key?(attr_name)
|
|
||||||
return @attributes_cache[attr_name]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
read_attribute_without_timeliness(attr_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
# If Rails dirty attributes is enabled then the value is added to
|
|
||||||
# changed attributes if changed. Can't use the default dirty checking
|
|
||||||
# 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)
|
def write_date_time_attribute(attr_name, value, type, time_zone_aware)
|
||||||
new = ValidatesTimeliness::Parser.parse(value, type)
|
@attributes_cache["_#{attr_name}_before_type_cast"] = value
|
||||||
|
|
||||||
if new && type != :date
|
value = ValidatesTimeliness::Parser.parse(value, type)
|
||||||
new = new.to_time
|
|
||||||
new = new.in_time_zone if time_zone_aware
|
if value && type != :date
|
||||||
|
value = value.to_time
|
||||||
|
value = value.in_time_zone if time_zone_aware
|
||||||
end
|
end
|
||||||
|
|
||||||
if defined?(::ActiveRecord::Dirty) && !changed_attributes.include?(attr_name)
|
write_attribute(attr_name.to_sym, value)
|
||||||
old = read_attribute(attr_name)
|
|
||||||
if old != new
|
|
||||||
changed_attributes[attr_name] = (old.duplicable? ? old.clone : old)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@attributes_cache[attr_name] = new
|
|
||||||
@attributes[attr_name] = value
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# If reloading then check if cached, which means its in local time.
|
def read_attribute_before_type_cast_with_timeliness(attr_name)
|
||||||
# If local, convert with parser as local timezone, otherwise use
|
return @attributes_cache["_#{attr_name}_before_type_cast"] if @attributes_cache.has_key?("_#{attr_name}_before_type_cast")
|
||||||
# read_attribute method for quick default type cast of values from
|
read_attribute_before_type_cast_without_timeliness(attr_name)
|
||||||
# database using default timezone.
|
|
||||||
def read_date_time_attribute(attr_name, type, time_zone_aware, reload = false)
|
|
||||||
cached = @attributes_cache[attr_name]
|
|
||||||
return cached if @attributes_cache.has_key?(attr_name) && !reload
|
|
||||||
|
|
||||||
if @attributes_cache.has_key?(attr_name)
|
|
||||||
time = read_attribute_before_type_cast(attr_name)
|
|
||||||
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?
|
|
||||||
end
|
|
||||||
@attributes_cache[attr_name] = time && time_zone_aware ? time.in_time_zone : time
|
|
||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
|
|
||||||
def define_attribute_methods_with_timeliness
|
def define_attribute_methods_with_timeliness
|
||||||
return if generated_methods?
|
return if generated_methods?
|
||||||
|
timeliness_methods = []
|
||||||
|
|
||||||
columns_hash.each do |name, column|
|
columns_hash.each do |name, column|
|
||||||
unless instance_method_already_implemented?(name)
|
|
||||||
if [:date, :time, :datetime].include?(column.type)
|
if [:date, :time, :datetime].include?(column.type)
|
||||||
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
|
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
|
||||||
define_read_method_for_dates_and_times(name, column.type, time_zone_aware)
|
|
||||||
|
class_eval <<-EOV
|
||||||
|
def #{name}=(value)
|
||||||
|
write_date_time_attribute('#{name}', value, #{column.type.inspect}, #{time_zone_aware})
|
||||||
|
end
|
||||||
|
EOV
|
||||||
|
timeliness_methods << name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unless instance_method_already_implemented?("#{name}=")
|
|
||||||
if [:date, :time, :datetime].include?(column.type)
|
|
||||||
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
|
|
||||||
define_write_method_for_dates_and_times(name, column.type, time_zone_aware)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
define_attribute_methods_without_timeliness
|
define_attribute_methods_without_timeliness
|
||||||
end
|
@generated_methods += timeliness_methods
|
||||||
|
|
||||||
def define_write_method_for_dates_and_times(attr_name, type, time_zone_aware)
|
|
||||||
method_body = <<-EOV
|
|
||||||
def #{attr_name}=(value)
|
|
||||||
write_date_time_attribute('#{attr_name}', value, #{type.inspect}, #{time_zone_aware})
|
|
||||||
end
|
|
||||||
EOV
|
|
||||||
evaluate_attribute_method attr_name, method_body, "#{attr_name}="
|
|
||||||
end
|
|
||||||
|
|
||||||
def define_read_method_for_dates_and_times(attr_name, type, time_zone_aware)
|
|
||||||
method_body = <<-EOV
|
|
||||||
def #{attr_name}(reload = false)
|
|
||||||
read_date_time_attribute('#{attr_name}', #{type.inspect}, #{time_zone_aware}, reload)
|
|
||||||
end
|
|
||||||
EOV
|
|
||||||
evaluate_attribute_method attr_name, method_body
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -127,5 +69,3 @@ module ValidatesTimeliness
|
|||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::AttributeMethods)
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ module ValidatesTimeliness
|
|||||||
base.alias_method_chain :execute_callstack_for_multiparameter_attributes, :timeliness
|
base.alias_method_chain :execute_callstack_for_multiparameter_attributes, :timeliness
|
||||||
end
|
end
|
||||||
|
|
||||||
# Overrides AR method to store multiparameter time and dates as string
|
# Assign dates and times as formatted strings to force the use of the plugin parser
|
||||||
# allowing validation later.
|
# and store a before_type_cast value for attribute
|
||||||
def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack)
|
def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack)
|
||||||
errors = []
|
errors = []
|
||||||
callstack.each do |name, values|
|
callstack.each do |name, values|
|
||||||
@@ -46,18 +46,17 @@ module ValidatesTimeliness
|
|||||||
when :time
|
when :time
|
||||||
extract_time_from_multiparameter_attributes(values)
|
extract_time_from_multiparameter_attributes(values)
|
||||||
when :datetime
|
when :datetime
|
||||||
date_values, time_values = values.slice!(0, 3), values
|
extract_date_from_multiparameter_attributes(values) + " " + extract_time_from_multiparameter_attributes(values)
|
||||||
extract_date_from_multiparameter_attributes(date_values) + " " + extract_time_from_multiparameter_attributes(time_values)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_date_from_multiparameter_attributes(values)
|
def extract_date_from_multiparameter_attributes(values)
|
||||||
[values[0], *values.slice(1, 2).map { |s| s.rjust(2, "0") }].join("-")
|
year = ValidatesTimeliness::Formats.unambiguous_year(values[0].rjust(2, "0"))
|
||||||
|
[year, *values.slice(1, 2).map { |s| s.rjust(2, "0") }].join("-")
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_time_from_multiparameter_attributes(values)
|
def extract_time_from_multiparameter_attributes(values)
|
||||||
values = values.size > 3 ? values[3..5] : values
|
values[3..5].map { |s| s.rjust(2, "0") }.join(":")
|
||||||
values.map { |s| s.rjust(2, "0") }.join(":")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,6 +21,17 @@ module ValidatesTimeliness
|
|||||||
:format_tokens,
|
:format_tokens,
|
||||||
:format_proc_args
|
:format_proc_args
|
||||||
|
|
||||||
|
|
||||||
|
# Set the threshold value for a two digit year to be considered last century
|
||||||
|
# Default: 30
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# year = '29' is considered 2029
|
||||||
|
# year = '30' is considered 1930
|
||||||
|
#
|
||||||
|
cattr_accessor :ambiguous_year_threshold
|
||||||
|
self.ambiguous_year_threshold = 30
|
||||||
|
|
||||||
# Format tokens:
|
# Format tokens:
|
||||||
# y = year
|
# y = year
|
||||||
# m = month
|
# m = month
|
||||||
@@ -46,7 +57,7 @@ module ValidatesTimeliness
|
|||||||
#
|
#
|
||||||
# Special Cases:
|
# Special Cases:
|
||||||
# yy = 2 or 4 digit year
|
# yy = 2 or 4 digit year
|
||||||
# yyyyy = exactly 4 digit year
|
# yyyy = exactly 4 digit year
|
||||||
# mmm = month long name (e.g. 'Jul' or 'July')
|
# mmm = month long name (e.g. 'Jul' or 'July')
|
||||||
# ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
|
# ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
|
||||||
# u = microseconds matches 1 to 6 digits
|
# u = microseconds matches 1 to 6 digits
|
||||||
@@ -85,6 +96,7 @@ module ValidatesTimeliness
|
|||||||
@@datetime_formats = [
|
@@datetime_formats = [
|
||||||
'yyyy-mm-dd hh:nn:ss',
|
'yyyy-mm-dd hh:nn:ss',
|
||||||
'yyyy-mm-dd h:nn',
|
'yyyy-mm-dd h:nn',
|
||||||
|
'yyyy-mm-dd h:nn_ampm',
|
||||||
'yyyy-mm-dd hh:nn:ss.u',
|
'yyyy-mm-dd hh:nn:ss.u',
|
||||||
'm/d/yy h:nn:ss',
|
'm/d/yy h:nn:ss',
|
||||||
'm/d/yy h:nn_ampm',
|
'm/d/yy h:nn_ampm',
|
||||||
@@ -226,6 +238,49 @@ module ValidatesTimeliness
|
|||||||
compile_format_expressions
|
compile_format_expressions
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def full_hour(hour, meridian)
|
||||||
|
hour = hour.to_i
|
||||||
|
return hour if meridian.nil?
|
||||||
|
if meridian.delete('.').downcase == 'am'
|
||||||
|
hour == 12 ? 0 : hour
|
||||||
|
else
|
||||||
|
hour == 12 ? hour : hour + 12
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unambiguous_year(year)
|
||||||
|
if year.length <= 2
|
||||||
|
century = Time.now.year.to_s[0..1].to_i
|
||||||
|
century -= 1 if year.to_i >= ambiguous_year_threshold
|
||||||
|
year = "#{century}#{year.rjust(2,'0')}"
|
||||||
|
end
|
||||||
|
year.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def month_index(month)
|
||||||
|
return month.to_i if month.to_i.nonzero?
|
||||||
|
abbr_month_names.index(month.capitalize) || month_names.index(month.capitalize)
|
||||||
|
end
|
||||||
|
|
||||||
|
def month_names
|
||||||
|
defined?(I18n) ? I18n.t('date.month_names') : Date::MONTHNAMES
|
||||||
|
end
|
||||||
|
|
||||||
|
def abbr_month_names
|
||||||
|
defined?(I18n) ? I18n.t('date.abbr_month_names') : Date::ABBR_MONTHNAMES
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Compile formats into validation regexps and format procs
|
# Compile formats into validation regexps and format procs
|
||||||
@@ -259,7 +314,12 @@ module ValidatesTimeliness
|
|||||||
args = order.invert.sort.map {|p| arg_map[p[1]][1] }
|
args = order.invert.sort.map {|p| arg_map[p[1]][1] }
|
||||||
arr = [nil] * 7
|
arr = [nil] * 7
|
||||||
order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? }
|
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.is_a?(Float) ? i : i.to_i } }"
|
proc_string = <<-EOL
|
||||||
|
lambda {|#{args.join(',')}|
|
||||||
|
md ||= nil
|
||||||
|
[#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.is_a?(Float) ? i : i.to_i }
|
||||||
|
}
|
||||||
|
EOL
|
||||||
eval proc_string
|
eval proc_string
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -285,44 +345,6 @@ module ValidatesTimeliness
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def full_hour(hour, meridian)
|
|
||||||
hour = hour.to_i
|
|
||||||
return hour if meridian.nil?
|
|
||||||
if meridian.delete('.').downcase == 'am'
|
|
||||||
hour == 12 ? 0 : hour
|
|
||||||
else
|
|
||||||
hour == 12 ? hour : hour + 12
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def unambiguous_year(year, threshold=30)
|
|
||||||
year = "#{year.to_i < threshold ? '20' : '19'}#{year}" if year.length == 2
|
|
||||||
year.to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
def month_index(month)
|
|
||||||
return month.to_i if month.to_i.nonzero?
|
|
||||||
abbr_month_names.index(month.capitalize) || month_names.index(month.capitalize)
|
|
||||||
end
|
|
||||||
|
|
||||||
def month_names
|
|
||||||
defined?(I18n) ? I18n.t('date.month_names') : Date::MONTHNAMES
|
|
||||||
end
|
|
||||||
|
|
||||||
def abbr_month_names
|
|
||||||
defined?(I18n) ? I18n.t('date.abbr_month_names') : Date::ABBR_MONTHNAMES
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ module ValidatesTimeliness
|
|||||||
restriction = [restriction] unless restriction.is_a?(Array)
|
restriction = [restriction] unless restriction.is_a?(Array)
|
||||||
|
|
||||||
if defined?(I18n)
|
if defined?(I18n)
|
||||||
message = custom_error_messages[option] || I18n.t('activerecord.errors.messages')[option]
|
message = I18n.t('activerecord.errors.messages')[option]
|
||||||
subs = message.scan(/\{\{([^\}]*)\}\}/)
|
subs = message.scan(/\{\{([^\}]*)\}\}/)
|
||||||
interpolations = {}
|
interpolations = {}
|
||||||
subs.each_with_index {|s, i| interpolations[s[0].to_sym] = restriction[i].strftime(format) }
|
subs.each_with_index {|s, i| interpolations[s[0].to_sym] = restriction[i].strftime(format) }
|
||||||
@@ -118,7 +118,7 @@ module ValidatesTimeliness
|
|||||||
def add_error(record, attr_name, message, interpolate=nil)
|
def add_error(record, attr_name, message, interpolate=nil)
|
||||||
if defined?(I18n)
|
if defined?(I18n)
|
||||||
custom = custom_error_messages[message]
|
custom = custom_error_messages[message]
|
||||||
record.errors.add(attr_name, custom || message, interpolate || {})
|
record.errors.add(attr_name, message, { :default => custom }.merge(interpolate || {}))
|
||||||
else
|
else
|
||||||
message = error_messages[message] if message.is_a?(Symbol)
|
message = error_messages[message] if message.is_a?(Symbol)
|
||||||
message = message % interpolate
|
message = message % interpolate
|
||||||
|
|||||||
@@ -20,24 +20,6 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
|||||||
@person.birth_date_and_time = "2000-01-01 12:00"
|
@person.birth_date_and_time = "2000-01-01 12:00"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should call read_date_time_attribute when date attribute is retrieved" do
|
|
||||||
@person.should_receive(:read_date_time_attribute)
|
|
||||||
@person.birth_date = "2000-01-01"
|
|
||||||
@person.birth_date
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should call read_date_time_attribute when time attribute is retrieved" do
|
|
||||||
@person.should_receive(:read_date_time_attribute)
|
|
||||||
@person.birth_time = "12:00"
|
|
||||||
@person.birth_time
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should call read_date_time_attribute when datetime attribute is retrieved" do
|
|
||||||
@person.should_receive(:read_date_time_attribute)
|
|
||||||
@person.birth_date_and_time = "2000-01-01 12:00"
|
|
||||||
@person.birth_date_and_time
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should call parser on write for datetime attribute" do
|
it "should call parser on write for datetime attribute" do
|
||||||
ValidatesTimeliness::Parser.should_receive(:parse).once
|
ValidatesTimeliness::Parser.should_receive(:parse).once
|
||||||
@person.birth_date_and_time = "2000-01-01 02:03:04"
|
@person.birth_date_and_time = "2000-01-01 02:03:04"
|
||||||
@@ -103,7 +85,20 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
|||||||
@person.birth_date_and_time_before_type_cast.should be_nil
|
@person.birth_date_and_time_before_type_cast.should be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
unless RAILS_VER < '2.1'
|
if RAILS_VER < '2.1'
|
||||||
|
|
||||||
|
it "should return time object from database in default timezone" do
|
||||||
|
ActiveRecord::Base.default_timezone = :utc
|
||||||
|
time_string = "2000-01-01 09:00:00"
|
||||||
|
@person = Person.new
|
||||||
|
@person.birth_date_and_time = time_string
|
||||||
|
@person.save
|
||||||
|
@person.reload
|
||||||
|
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z').should == time_string + ' GMT'
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
it "should return stored time string as Time with correct timezone" do
|
it "should return stored time string as Time with correct timezone" do
|
||||||
Time.zone = 'Melbourne'
|
Time.zone = 'Melbourne'
|
||||||
time_string = "2000-06-01 02:03:04"
|
time_string = "2000-06-01 02:03:04"
|
||||||
@@ -121,84 +116,6 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
|||||||
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z %z').should == time_string + ' EST +1000'
|
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z %z').should == time_string + ' EST +1000'
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "dirty attributes" do
|
|
||||||
|
|
||||||
it "should return true for attribute changed? when value updated" do
|
|
||||||
time_string = "2000-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.birth_date_and_time_changed?.should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should show changes when time attribute changed from nil to Time object" do
|
|
||||||
time_string = "2000-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
time = @person.birth_date_and_time
|
|
||||||
@person.changes.should == {"birth_date_and_time" => [nil, time]}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should show changes when time attribute changed from Time object to nil" do
|
|
||||||
time_string = "2020-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save false
|
|
||||||
@person.reload
|
|
||||||
time = @person.birth_date_and_time
|
|
||||||
@person.birth_date_and_time = nil
|
|
||||||
@person.changes.should == {"birth_date_and_time" => [time, nil]}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should show no changes when assigned same value as Time object" do
|
|
||||||
time_string = "2020-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save false
|
|
||||||
@person.reload
|
|
||||||
time = @person.birth_date_and_time
|
|
||||||
@person.birth_date_and_time = time
|
|
||||||
@person.changes.should == {}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should show no changes when assigned same value as time string" do
|
|
||||||
time_string = "2020-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save false
|
|
||||||
@person.reload
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.changes.should == {}
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
else
|
|
||||||
|
|
||||||
it "should return time object from database in default timezone" do
|
|
||||||
ActiveRecord::Base.default_timezone = :utc
|
|
||||||
time_string = "2000-01-01 09:00:00"
|
|
||||||
@person = Person.new
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save
|
|
||||||
@person.reload
|
|
||||||
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z').should == time_string + ' GMT'
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return same time object on repeat reads on existing object" do
|
|
||||||
Time.zone = 'Melbourne' unless RAILS_VER < '2.1'
|
|
||||||
time_string = "2000-01-01 09:00:00"
|
|
||||||
@person = Person.new
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save!
|
|
||||||
@person.reload
|
|
||||||
time = @person.birth_date_and_time
|
|
||||||
@person.birth_date_and_time.should == time
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return same date object on repeat reads on existing object" do
|
|
||||||
date_string = Date.today
|
|
||||||
@person = Person.new
|
|
||||||
@person.birth_date = date_string
|
|
||||||
@person.save!
|
|
||||||
@person.reload
|
|
||||||
date = @person.birth_date
|
|
||||||
@person.birth_date.should == date
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return correct date value after new value assigned" do
|
it "should return correct date value after new value assigned" do
|
||||||
@@ -221,14 +138,4 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
|||||||
@person.birth_date.should == tomorrow
|
@person.birth_date.should == tomorrow
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should skip storing value in attributes hash on read if record frozen" do
|
|
||||||
@person = Person.new
|
|
||||||
@person.birth_date = Date.today
|
|
||||||
@person.save!
|
|
||||||
@person.reload
|
|
||||||
@person.freeze
|
|
||||||
@person.frozen?.should be_true
|
|
||||||
lambda { @person.birth_date }.should_not raise_error
|
|
||||||
@person.birth_date.should == Date.today
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -155,6 +155,28 @@ describe ValidatesTimeliness::Formats do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "parsing date with ambiguous year" do
|
||||||
|
it "should return year in current century if year below threshold" do
|
||||||
|
time_array = formats.parse('01-02-29', :date)
|
||||||
|
time_array.should == [2029,2,1,0,0,0,0]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return year in last century if year at or above threshold" do
|
||||||
|
time_array = formats.parse('01-02-30', :date)
|
||||||
|
time_array.should == [1930,2,1,0,0,0,0]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should allow custom threshold" do
|
||||||
|
default = ValidatesTimeliness::Formats.ambiguous_year_threshold
|
||||||
|
ValidatesTimeliness::Formats.ambiguous_year_threshold = 40
|
||||||
|
time_array = formats.parse('01-02-39', :date)
|
||||||
|
time_array.should == [2039,2,1,0,0,0,0]
|
||||||
|
time_array = formats.parse('01-02-40', :date)
|
||||||
|
time_array.should == [1940,2,1,0,0,0,0]
|
||||||
|
ValidatesTimeliness::Formats.ambiguous_year_threshold = default
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "removing formats" do
|
describe "removing formats" do
|
||||||
it "should remove format from format array" do
|
it "should remove format from format array" do
|
||||||
formats.remove_formats(:time, 'h.nn_ampm')
|
formats.remove_formats(:time, 'h.nn_ampm')
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Ginger.configure do |config|
|
|||||||
rails_versions = ['2.0.2', '2.1.2', '2.2.2', '2.3.2']
|
rails_versions = ['2.0.2', '2.1.2', '2.2.2', '2.3.2']
|
||||||
|
|
||||||
rails_versions.each do |v|
|
rails_versions.each do |v|
|
||||||
g = Ginger::Scenario.new
|
g = Ginger::Scenario.new("Rails #{v}")
|
||||||
g['rails'] = v
|
g['rails'] = v
|
||||||
config.scenarios << g.dup
|
config.scenarios << g.dup
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ $:.unshift(File.dirname(__FILE__) + '/../lib')
|
|||||||
$:.unshift(File.dirname(__FILE__))
|
$:.unshift(File.dirname(__FILE__))
|
||||||
$:.unshift(File.dirname(__FILE__) + '/resources')
|
$:.unshift(File.dirname(__FILE__) + '/resources')
|
||||||
|
|
||||||
ENV['RAILS_ENV'] = 'test'
|
RAILS_ENV = ENV['RAILS_ENV'] = 'test'
|
||||||
|
|
||||||
require 'rubygems'
|
require 'rubygems'
|
||||||
require 'spec'
|
require 'spec/autorun'
|
||||||
|
|
||||||
vendored_rails = File.dirname(__FILE__) + '/../../../../vendor/rails'
|
vendored_rails = File.dirname(__FILE__) + '/../../../../vendor/rails'
|
||||||
|
|
||||||
|
|||||||
@@ -506,12 +506,6 @@ describe ValidatesTimeliness::Validator do
|
|||||||
configure_validator(:type => :date, :before => before)
|
configure_validator(:type => :date, :before => before)
|
||||||
validator.send(:interpolation_values, :before, before.to_date).should == {:restriction => before}
|
validator.send(:interpolation_values, :before, before.to_date).should == {:restriction => before}
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return empty hash if no interpolation keys are in message" do
|
|
||||||
before = '1900-01-01'
|
|
||||||
configure_validator(:type => :date, :before => before, :before_message => 'too late')
|
|
||||||
validator.send(:interpolation_values, :before, before.to_date).should be_empty
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
it "should return array of interpolation values" do
|
it "should return array of interpolation values" do
|
||||||
before = '1900-01-01'
|
before = '1900-01-01'
|
||||||
|
|||||||
@@ -2,26 +2,25 @@
|
|||||||
|
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = %q{validates_timeliness}
|
s.name = %q{validates_timeliness}
|
||||||
s.version = "2.0.0"
|
s.version = "2.1.0"
|
||||||
|
|
||||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||||
s.authors = ["Adam Meehan"]
|
s.authors = ["Adam Meehan"]
|
||||||
s.autorequire = %q{validates_timeliness}
|
s.autorequire = %q{validates_timeliness}
|
||||||
s.date = %q{2009-04-12}
|
s.date = %q{2009-06-20}
|
||||||
s.description = %q{Date and time validation plugin for Rails 2.x which allows custom formats}
|
s.description = %q{Date and time validation plugin for Rails 2.x which allows custom formats}
|
||||||
s.email = %q{adam.meehan@gmail.com}
|
s.email = %q{adam.meehan@gmail.com}
|
||||||
s.extra_rdoc_files = ["README.rdoc", "LICENSE", "TODO", "CHANGELOG"]
|
s.extra_rdoc_files = ["README.rdoc", "LICENSE", "TODO", "CHANGELOG"]
|
||||||
s.files = ["LICENSE", "README.rdoc", "Rakefile", "TODO", "CHANGELOG", "lib/validates_timeliness", "lib/validates_timeliness/core_ext", "lib/validates_timeliness/core_ext/date.rb", "lib/validates_timeliness/core_ext/date_time.rb", "lib/validates_timeliness/core_ext/time.rb", "lib/validates_timeliness/action_view", "lib/validates_timeliness/action_view/instance_tag.rb", "lib/validates_timeliness/locale", "lib/validates_timeliness/locale/en.yml", "lib/validates_timeliness/validation_methods.rb", "lib/validates_timeliness/active_record", "lib/validates_timeliness/active_record/attribute_methods.rb", "lib/validates_timeliness/active_record/multiparameter_attributes.rb", "lib/validates_timeliness/parser.rb", "lib/validates_timeliness/formats.rb", "lib/validates_timeliness/validator.rb", "lib/validates_timeliness/spec", "lib/validates_timeliness/spec/rails", "lib/validates_timeliness/spec/rails/matchers", "lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb", "lib/validates_timeliness.rb", "spec/core_ext", "spec/core_ext/dummy_time_spec.rb", "spec/validator_spec.rb", "spec/action_view", "spec/action_view/instance_tag_spec.rb", "spec/ginger_scenarios.rb", "spec/spec_helper.rb", "spec/formats_spec.rb", "spec/active_record", "spec/active_record/attribute_methods_spec.rb", "spec/active_record/multiparameter_attributes_spec.rb", "spec/time_travel", "spec/time_travel/time_travel.rb", "spec/time_travel/time_extensions.rb", "spec/time_travel/MIT-LICENSE", "spec/parser_spec.rb", "spec/spec", "spec/spec/rails", "spec/spec/rails/matchers", "spec/spec/rails/matchers/validate_timeliness_spec.rb", "spec/resources", "spec/resources/person.rb", "spec/resources/sqlite_patch.rb", "spec/resources/schema.rb", "spec/resources/application.rb"]
|
s.files = ["LICENSE", "README.rdoc", "Rakefile", "TODO", "CHANGELOG", "lib/validates_timeliness", "lib/validates_timeliness/active_record", "lib/validates_timeliness/active_record/multiparameter_attributes.rb", "lib/validates_timeliness/active_record/attribute_methods.rb", "lib/validates_timeliness/parser.rb", "lib/validates_timeliness/core_ext", "lib/validates_timeliness/core_ext/date.rb", "lib/validates_timeliness/core_ext/time.rb", "lib/validates_timeliness/core_ext/date_time.rb", "lib/validates_timeliness/validator.rb", "lib/validates_timeliness/validation_methods.rb", "lib/validates_timeliness/locale", "lib/validates_timeliness/locale/en.yml", "lib/validates_timeliness/spec", "lib/validates_timeliness/spec/rails", "lib/validates_timeliness/spec/rails/matchers", "lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb", "lib/validates_timeliness/action_view", "lib/validates_timeliness/action_view/instance_tag.rb", "lib/validates_timeliness/formats.rb", "lib/validates_timeliness.rb", "spec/active_record", "spec/active_record/multiparameter_attributes_spec.rb", "spec/active_record/attribute_methods_spec.rb", "spec/formats_spec.rb", "spec/parser_spec.rb", "spec/core_ext", "spec/core_ext/dummy_time_spec.rb", "spec/spec_helper.rb", "spec/ginger_scenarios.rb", "spec/time_travel", "spec/time_travel/time_extensions.rb", "spec/time_travel/time_travel.rb", "spec/time_travel/MIT-LICENSE", "spec/spec", "spec/spec/rails", "spec/spec/rails/matchers", "spec/spec/rails/matchers/validate_timeliness_spec.rb", "spec/validator_spec.rb", "spec/action_view", "spec/action_view/instance_tag_spec.rb", "spec/resources", "spec/resources/schema.rb", "spec/resources/application.rb", "spec/resources/person.rb", "spec/resources/sqlite_patch.rb"]
|
||||||
s.has_rdoc = true
|
|
||||||
s.homepage = %q{http://github.com/adzap/validates_timeliness}
|
s.homepage = %q{http://github.com/adzap/validates_timeliness}
|
||||||
s.require_paths = ["lib"]
|
s.require_paths = ["lib"]
|
||||||
s.rubyforge_project = %q{validatestime}
|
s.rubyforge_project = %q{validatestime}
|
||||||
s.rubygems_version = %q{1.3.1}
|
s.rubygems_version = %q{1.3.3}
|
||||||
s.summary = %q{Date and time validation plugin for Rails 2.x which allows custom formats}
|
s.summary = %q{Date and time validation plugin for Rails 2.x which allows custom formats}
|
||||||
|
|
||||||
if s.respond_to? :specification_version then
|
if s.respond_to? :specification_version then
|
||||||
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
||||||
s.specification_version = 2
|
s.specification_version = 3
|
||||||
|
|
||||||
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user