mirror of
https://github.com/ditkrg/validates_timeliness.git
synced 2026-01-23 06:16:44 +00:00
namespaced ActiveRecord and ActionView specifc modules and specs with a mind to making the plugin framework agnostic in the future
This commit is contained in:
parent
21d26ee2b1
commit
412ff22dd9
@ -1,18 +1,19 @@
|
|||||||
require 'validates_timeliness/attribute_methods'
|
|
||||||
require 'validates_timeliness/validations'
|
require 'validates_timeliness/validations'
|
||||||
require 'validates_timeliness/formats'
|
require 'validates_timeliness/formats'
|
||||||
require 'validates_timeliness/multiparameter_attributes'
|
|
||||||
require 'validates_timeliness/instance_tag'
|
|
||||||
require 'validates_timeliness/validate_timeliness_matcher' if ENV['RAILS_ENV'] == 'test'
|
require 'validates_timeliness/validate_timeliness_matcher' if ENV['RAILS_ENV'] == 'test'
|
||||||
|
|
||||||
|
require 'validates_timeliness/active_record/attribute_methods'
|
||||||
|
require 'validates_timeliness/active_record/multiparameter_attributes'
|
||||||
|
require 'validates_timeliness/action_view/instance_tag'
|
||||||
|
|
||||||
require 'validates_timeliness/core_ext/time'
|
require 'validates_timeliness/core_ext/time'
|
||||||
require 'validates_timeliness/core_ext/date'
|
require 'validates_timeliness/core_ext/date'
|
||||||
require 'validates_timeliness/core_ext/date_time'
|
require 'validates_timeliness/core_ext/date_time'
|
||||||
|
|
||||||
ActiveRecord::Base.send(:include, ValidatesTimeliness::AttributeMethods)
|
|
||||||
ActiveRecord::Base.send(:include, ValidatesTimeliness::Validations)
|
ActiveRecord::Base.send(:include, ValidatesTimeliness::Validations)
|
||||||
ActiveRecord::Base.send(:include, ValidatesTimeliness::MultiparameterAttributes)
|
ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::AttributeMethods)
|
||||||
ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::InstanceTag)
|
ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::MultiparameterAttributes)
|
||||||
|
ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::ActionView::InstanceTag)
|
||||||
|
|
||||||
Time.send(:include, ValidatesTimeliness::CoreExtensions::Time)
|
Time.send(:include, ValidatesTimeliness::CoreExtensions::Time)
|
||||||
Date.send(:include, ValidatesTimeliness::CoreExtensions::Date)
|
Date.send(:include, ValidatesTimeliness::CoreExtensions::Date)
|
||||||
|
|||||||
43
lib/validates_timeliness/action_view/instance_tag.rb
Normal file
43
lib/validates_timeliness/action_view/instance_tag.rb
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module ActionView
|
||||||
|
|
||||||
|
# Intercepts the date and time select helpers to allow the
|
||||||
|
# attribute value before type cast to be used as in the select helpers.
|
||||||
|
# This means that an invalid date or time will be redisplayed rather than the
|
||||||
|
# type cast value which would be nil if invalid.
|
||||||
|
module InstanceTag
|
||||||
|
|
||||||
|
def self.included(base)
|
||||||
|
selector_method = Rails::VERSION::STRING < '2.2' ? :date_or_time_select : :datetime_selector
|
||||||
|
base.class_eval do
|
||||||
|
alias_method :datetime_selector_without_timeliness, selector_method
|
||||||
|
alias_method selector_method, :datetime_selector_with_timeliness
|
||||||
|
end
|
||||||
|
base.alias_method_chain :value, :timeliness
|
||||||
|
end
|
||||||
|
|
||||||
|
TimelinessDateTime = Struct.new(:year, :month, :day, :hour, :min, :sec)
|
||||||
|
|
||||||
|
def datetime_selector_with_timeliness(*args)
|
||||||
|
@timeliness_date_or_time_tag = true
|
||||||
|
datetime_selector_without_timeliness(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def value_with_timeliness(object)
|
||||||
|
return value_without_timeliness(object) unless @timeliness_date_or_time_tag
|
||||||
|
|
||||||
|
raw_value = value_before_type_cast(object)
|
||||||
|
|
||||||
|
if raw_value.nil? || raw_value.acts_like?(:time) || raw_value.is_a?(Date)
|
||||||
|
return value_without_timeliness(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
time_array = ParseDate.parsedate(raw_value)
|
||||||
|
|
||||||
|
TimelinessDateTime.new(*time_array[0..5])
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
153
lib/validates_timeliness/active_record/attribute_methods.rb
Normal file
153
lib/validates_timeliness/active_record/attribute_methods.rb
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module ActiveRecord
|
||||||
|
|
||||||
|
# The crux of the plugin is being able to store raw user entered values
|
||||||
|
# while not interfering with the Rails 2.1 automatic timezone handling. This
|
||||||
|
# requires us to distinguish a user entered value from a value read from the
|
||||||
|
# database. Both maybe in string form, but only the database value should be
|
||||||
|
# interpreted as being in the default timezone which is normally UTC. The user
|
||||||
|
# entered value should be interpreted as being in the current zone as indicated
|
||||||
|
# by Time.zone.
|
||||||
|
#
|
||||||
|
# To do this we must cache the user entered values 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.
|
||||||
|
#
|
||||||
|
# The wholesale replacement of the Rails time type casting is not done to
|
||||||
|
# preserve the quickest conversion for timestamp columns and also any value
|
||||||
|
# which is never changed during the life of the record object.
|
||||||
|
module AttributeMethods
|
||||||
|
|
||||||
|
def self.included(base)
|
||||||
|
base.extend ClassMethods
|
||||||
|
|
||||||
|
if Rails::VERSION::STRING < '2.1'
|
||||||
|
base.class_eval do
|
||||||
|
class << self
|
||||||
|
def create_time_zone_conversion_attribute?(name, column)
|
||||||
|
false
|
||||||
|
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(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 [:date, :time, :datetime].include?(column.type) && @attributes_cache.has_key?(attr_name)
|
||||||
|
@attributes_cache[attr_name]
|
||||||
|
else
|
||||||
|
column.type_cast(value)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Writes attribute value by storing raw value in attributes hash,
|
||||||
|
# then convert it with parser and cache it.
|
||||||
|
#
|
||||||
|
# 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)
|
||||||
|
attr_name = attr_name.to_s
|
||||||
|
column = column_for_attribute(attr_name)
|
||||||
|
old = read_attribute(attr_name) if defined?(::ActiveRecord::Dirty)
|
||||||
|
new = self.class.parse_date_time(value, column.type)
|
||||||
|
|
||||||
|
if self.class.send(:create_time_zone_conversion_attribute?, attr_name, column)
|
||||||
|
new = new.in_time_zone rescue nil
|
||||||
|
end
|
||||||
|
@attributes_cache[attr_name] = new
|
||||||
|
|
||||||
|
if defined?(::ActiveRecord::Dirty) && !changed_attributes.include?(attr_name) && old != new
|
||||||
|
changed_attributes[attr_name] = (old.duplicable? ? old.clone : old)
|
||||||
|
end
|
||||||
|
@attributes[attr_name] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
# Override AR method to define attribute reader and writer method for
|
||||||
|
# date, time and datetime attributes to use plugin parser.
|
||||||
|
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 create_time_zone_conversion_attribute?(name, column)
|
||||||
|
define_read_method_for_time_zone_conversion(name.to_sym)
|
||||||
|
else
|
||||||
|
define_read_method(name.to_sym, name, column)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unless instance_method_already_implemented?("#{name}=")
|
||||||
|
if [:date, :time, :datetime].include?(column.type)
|
||||||
|
define_write_method_for_dates_and_times(name.to_sym)
|
||||||
|
else
|
||||||
|
define_write_method(name.to_sym)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unless instance_method_already_implemented?("#{name}?")
|
||||||
|
define_question_method(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Define write method for date, time and datetime columns
|
||||||
|
def define_write_method_for_dates_and_times(attr_name)
|
||||||
|
method_body = <<-EOV
|
||||||
|
def #{attr_name}=(value)
|
||||||
|
write_date_time_attribute('#{attr_name}', value)
|
||||||
|
end
|
||||||
|
EOV
|
||||||
|
evaluate_attribute_method attr_name, method_body
|
||||||
|
end
|
||||||
|
|
||||||
|
# Define time attribute reader. If reloading then check if cached,
|
||||||
|
# which means its in local time. If local, convert with parser as local
|
||||||
|
# timezone, otherwise use read_attribute method for quick default type
|
||||||
|
# cast of values from database using default timezone.
|
||||||
|
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 @attributes_cache.has_key?('#{attr_name}') && !reload
|
||||||
|
if @attributes_cache.has_key?('#{attr_name}')
|
||||||
|
time = read_attribute_before_type_cast('#{attr_name}')
|
||||||
|
time = self.class.parse_date_time(date, :datetime)
|
||||||
|
else
|
||||||
|
time = read_attribute('#{attr_name}')
|
||||||
|
@attributes['#{attr_name}'] = time.in_time_zone rescue nil
|
||||||
|
end
|
||||||
|
@attributes_cache['#{attr_name}'] = time.in_time_zone rescue nil
|
||||||
|
end
|
||||||
|
EOV
|
||||||
|
evaluate_attribute_method attr_name, method_body
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module ActiveRecord
|
||||||
|
|
||||||
|
module MultiparameterAttributes
|
||||||
|
|
||||||
|
def self.included(base)
|
||||||
|
base.alias_method_chain :execute_callstack_for_multiparameter_attributes, :timeliness
|
||||||
|
end
|
||||||
|
|
||||||
|
# Overrides AR method to store multiparameter time and dates as string
|
||||||
|
# allowing validation later.
|
||||||
|
def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack)
|
||||||
|
errors = []
|
||||||
|
callstack.each do |name, values|
|
||||||
|
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
|
||||||
|
if values.empty?
|
||||||
|
send(name + "=", nil)
|
||||||
|
else
|
||||||
|
column = column_for_attribute(name)
|
||||||
|
begin
|
||||||
|
value = if [:date, :time, :datetime].include?(column.type)
|
||||||
|
time_array_to_string(values, column.type)
|
||||||
|
else
|
||||||
|
klass.new(*values)
|
||||||
|
end
|
||||||
|
send(name + "=", value)
|
||||||
|
rescue => ex
|
||||||
|
errors << ::ActiveRecord::AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
unless errors.empty?
|
||||||
|
raise ::ActiveRecord::MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def time_array_to_string(values, type)
|
||||||
|
values = values.map(&:to_s)
|
||||||
|
|
||||||
|
case type
|
||||||
|
when :date
|
||||||
|
extract_date_from_multiparameter_attributes(values)
|
||||||
|
when :time
|
||||||
|
extract_time_from_multiparameter_attributes(values)
|
||||||
|
when :datetime
|
||||||
|
date_values, time_values = values.slice!(0, 3), values
|
||||||
|
extract_date_from_multiparameter_attributes(date_values) + " " + extract_time_from_multiparameter_attributes(time_values)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def extract_date_from_multiparameter_attributes(values)
|
||||||
|
[values[0], *values.slice(1, 2).map { |s| s.rjust(2, "0") }].join("-")
|
||||||
|
end
|
||||||
|
|
||||||
|
def extract_time_from_multiparameter_attributes(values)
|
||||||
|
values.last(3).map { |s| s.rjust(2, "0") }.join(":")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,150 +0,0 @@
|
|||||||
module ValidatesTimeliness
|
|
||||||
|
|
||||||
# The crux of the plugin is being able to store raw user entered values
|
|
||||||
# while not interfering with the Rails 2.1 automatic timezone handling. This
|
|
||||||
# requires us to distinguish a user entered value from a value read from the
|
|
||||||
# database. Both maybe in string form, but only the database value should be
|
|
||||||
# interpreted as being in the default timezone which is normally UTC. The user
|
|
||||||
# entered value should be interpreted as being in the current zone as indicated
|
|
||||||
# by Time.zone.
|
|
||||||
#
|
|
||||||
# To do this we must cache the user entered values 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.
|
|
||||||
#
|
|
||||||
# The wholesale replacement of the Rails time type casting is not done to
|
|
||||||
# preserve the quickest conversion for timestamp columns and also any value
|
|
||||||
# which is never changed during the life of the record object.
|
|
||||||
module AttributeMethods
|
|
||||||
|
|
||||||
def self.included(base)
|
|
||||||
base.extend ClassMethods
|
|
||||||
|
|
||||||
if Rails::VERSION::STRING < '2.1'
|
|
||||||
base.class_eval do
|
|
||||||
class << self
|
|
||||||
def create_time_zone_conversion_attribute?(name, column)
|
|
||||||
false
|
|
||||||
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(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 [:date, :time, :datetime].include?(column.type) && @attributes_cache.has_key?(attr_name)
|
|
||||||
@attributes_cache[attr_name]
|
|
||||||
else
|
|
||||||
column.type_cast(value)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
value
|
|
||||||
end
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Writes attribute value by storing raw value in attributes hash,
|
|
||||||
# then convert it with parser and cache it.
|
|
||||||
#
|
|
||||||
# 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)
|
|
||||||
attr_name = attr_name.to_s
|
|
||||||
column = column_for_attribute(attr_name)
|
|
||||||
old = read_attribute(attr_name) if defined?(ActiveRecord::Dirty)
|
|
||||||
new = self.class.parse_date_time(value, column.type)
|
|
||||||
|
|
||||||
if self.class.send(:create_time_zone_conversion_attribute?, attr_name, column)
|
|
||||||
new = new.in_time_zone rescue nil
|
|
||||||
end
|
|
||||||
@attributes_cache[attr_name] = new
|
|
||||||
|
|
||||||
if defined?(ActiveRecord::Dirty) && !changed_attributes.include?(attr_name) && old != new
|
|
||||||
changed_attributes[attr_name] = (old.duplicable? ? old.clone : old)
|
|
||||||
end
|
|
||||||
@attributes[attr_name] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
|
|
||||||
# Override AR method to define attribute reader and writer method for
|
|
||||||
# date, time and datetime attributes to use plugin parser.
|
|
||||||
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 create_time_zone_conversion_attribute?(name, column)
|
|
||||||
define_read_method_for_time_zone_conversion(name.to_sym)
|
|
||||||
else
|
|
||||||
define_read_method(name.to_sym, name, column)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
unless instance_method_already_implemented?("#{name}=")
|
|
||||||
if [:date, :time, :datetime].include?(column.type)
|
|
||||||
define_write_method_for_dates_and_times(name.to_sym)
|
|
||||||
else
|
|
||||||
define_write_method(name.to_sym)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
unless instance_method_already_implemented?("#{name}?")
|
|
||||||
define_question_method(name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Define write method for date, time and datetime columns
|
|
||||||
def define_write_method_for_dates_and_times(attr_name)
|
|
||||||
method_body = <<-EOV
|
|
||||||
def #{attr_name}=(value)
|
|
||||||
write_date_time_attribute('#{attr_name}', value)
|
|
||||||
end
|
|
||||||
EOV
|
|
||||||
evaluate_attribute_method attr_name, method_body
|
|
||||||
end
|
|
||||||
|
|
||||||
# Define time attribute reader. If reloading then check if cached,
|
|
||||||
# which means its in local time. If local, convert with parser as local
|
|
||||||
# timezone, otherwise use read_attribute method for quick default type
|
|
||||||
# cast of values from database using default timezone.
|
|
||||||
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 @attributes_cache.has_key?('#{attr_name}') && !reload
|
|
||||||
if @attributes_cache.has_key?('#{attr_name}')
|
|
||||||
time = read_attribute_before_type_cast('#{attr_name}')
|
|
||||||
time = self.class.parse_date_time(date, :datetime)
|
|
||||||
else
|
|
||||||
time = read_attribute('#{attr_name}')
|
|
||||||
@attributes['#{attr_name}'] = time.in_time_zone rescue nil
|
|
||||||
end
|
|
||||||
@attributes_cache['#{attr_name}'] = time.in_time_zone rescue nil
|
|
||||||
end
|
|
||||||
EOV
|
|
||||||
evaluate_attribute_method attr_name, method_body
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
module ValidatesTimeliness
|
|
||||||
|
|
||||||
# Intercepts the date and time select helpers to allow the
|
|
||||||
# attribute value before type cast to be used as in the select helpers.
|
|
||||||
# This means that an invalid date or time will be redisplayed rather than the
|
|
||||||
# type cast value which would be nil if invalid.
|
|
||||||
module InstanceTag
|
|
||||||
|
|
||||||
def self.included(base)
|
|
||||||
selector_method = Rails::VERSION::STRING < '2.2' ? :date_or_time_select : :datetime_selector
|
|
||||||
base.class_eval do
|
|
||||||
alias_method :datetime_selector_without_timeliness, selector_method
|
|
||||||
alias_method selector_method, :datetime_selector_with_timeliness
|
|
||||||
end
|
|
||||||
base.alias_method_chain :value, :timeliness
|
|
||||||
end
|
|
||||||
|
|
||||||
TimelinessDateTime = Struct.new(:year, :month, :day, :hour, :min, :sec)
|
|
||||||
|
|
||||||
def datetime_selector_with_timeliness(*args)
|
|
||||||
@timeliness_date_or_time_tag = true
|
|
||||||
datetime_selector_without_timeliness(*args)
|
|
||||||
end
|
|
||||||
|
|
||||||
def value_with_timeliness(object)
|
|
||||||
return value_without_timeliness(object) unless @timeliness_date_or_time_tag
|
|
||||||
|
|
||||||
raw_value = value_before_type_cast(object)
|
|
||||||
|
|
||||||
if raw_value.nil? || raw_value.acts_like?(:time) || raw_value.is_a?(Date)
|
|
||||||
return value_without_timeliness(object)
|
|
||||||
end
|
|
||||||
|
|
||||||
time_array = ParseDate.parsedate(raw_value)
|
|
||||||
|
|
||||||
TimelinessDateTime.new(*time_array[0..5])
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
module ValidatesTimeliness
|
|
||||||
module MultiparameterAttributes
|
|
||||||
|
|
||||||
def self.included(base)
|
|
||||||
base.alias_method_chain :execute_callstack_for_multiparameter_attributes, :timeliness
|
|
||||||
end
|
|
||||||
|
|
||||||
# Overrides AR method to store multiparameter time and dates as string
|
|
||||||
# allowing validation later.
|
|
||||||
def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack)
|
|
||||||
errors = []
|
|
||||||
callstack.each do |name, values|
|
|
||||||
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
|
|
||||||
if values.empty?
|
|
||||||
send(name + "=", nil)
|
|
||||||
else
|
|
||||||
column = column_for_attribute(name)
|
|
||||||
begin
|
|
||||||
value = if [:date, :time, :datetime].include?(column.type)
|
|
||||||
time_array_to_string(values, column.type)
|
|
||||||
else
|
|
||||||
klass.new(*values)
|
|
||||||
end
|
|
||||||
send(name + "=", value)
|
|
||||||
rescue => ex
|
|
||||||
errors << ActiveRecord::AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
unless errors.empty?
|
|
||||||
raise ActiveRecord::MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def time_array_to_string(values, type)
|
|
||||||
values = values.map(&:to_s)
|
|
||||||
|
|
||||||
case type
|
|
||||||
when :date
|
|
||||||
extract_date_from_multiparameter_attributes(values)
|
|
||||||
when :time
|
|
||||||
extract_time_from_multiparameter_attributes(values)
|
|
||||||
when :datetime
|
|
||||||
date_values, time_values = values.slice!(0, 3), values
|
|
||||||
extract_date_from_multiparameter_attributes(date_values) + " " + extract_time_from_multiparameter_attributes(time_values)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_date_from_multiparameter_attributes(values)
|
|
||||||
[values[0], *values.slice(1, 2).map { |s| s.rjust(2, "0") }].join("-")
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_time_from_multiparameter_attributes(values)
|
|
||||||
values.last(3).map { |s| s.rjust(2, "0") }.join(":")
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -12,14 +12,14 @@ module ValidatesTimeliness
|
|||||||
base.class_inheritable_accessor :ignore_datetime_restriction_errors
|
base.class_inheritable_accessor :ignore_datetime_restriction_errors
|
||||||
base.ignore_datetime_restriction_errors = false
|
base.ignore_datetime_restriction_errors = false
|
||||||
|
|
||||||
ActiveRecord::Errors.class_inheritable_accessor :date_time_error_value_formats
|
::ActiveRecord::Errors.class_inheritable_accessor :date_time_error_value_formats
|
||||||
ActiveRecord::Errors.date_time_error_value_formats = {
|
::ActiveRecord::Errors.date_time_error_value_formats = {
|
||||||
:time => '%H:%M:%S',
|
:time => '%H:%M:%S',
|
||||||
:date => '%Y-%m-%d',
|
:date => '%Y-%m-%d',
|
||||||
:datetime => '%Y-%m-%d %H:%M:%S'
|
:datetime => '%Y-%m-%d %H:%M:%S'
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveRecord::Errors.default_error_messages.update(
|
::ActiveRecord::Errors.default_error_messages.update(
|
||||||
:invalid_date => "is not a valid date",
|
:invalid_date => "is not a valid date",
|
||||||
:invalid_time => "is not a valid time",
|
:invalid_time => "is not a valid time",
|
||||||
:invalid_datetime => "is not a valid datetime",
|
:invalid_datetime => "is not a valid datetime",
|
||||||
@ -140,7 +140,7 @@ module ValidatesTimeliness
|
|||||||
|
|
||||||
type_cast_method = restriction_type_cast_method(configuration[:type])
|
type_cast_method = restriction_type_cast_method(configuration[:type])
|
||||||
|
|
||||||
display = ActiveRecord::Errors.date_time_error_value_formats[configuration[:type]]
|
display = ::ActiveRecord::Errors.date_time_error_value_formats[configuration[:type]]
|
||||||
|
|
||||||
value = value.send(type_cast_method)
|
value = value.send(type_cast_method)
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ module ValidatesTimeliness
|
|||||||
|
|
||||||
# Map error message keys to *_message to merge with validation options
|
# Map error message keys to *_message to merge with validation options
|
||||||
def timeliness_default_error_messages
|
def timeliness_default_error_messages
|
||||||
defaults = ActiveRecord::Errors.default_error_messages.slice(
|
defaults = ::ActiveRecord::Errors.default_error_messages.slice(
|
||||||
:blank, :invalid_date, :invalid_time, :invalid_datetime, :before, :on_or_before, :after, :on_or_after)
|
:blank, :invalid_date, :invalid_time, :invalid_datetime, :before, :on_or_before, :after, :on_or_after)
|
||||||
returning({}) do |messages|
|
returning({}) do |messages|
|
||||||
defaults.each {|k, v| messages["#{k}_message".to_sym] = v }
|
defaults.each {|k, v| messages["#{k}_message".to_sym] = v }
|
||||||
@ -175,9 +175,9 @@ module ValidatesTimeliness
|
|||||||
Time.zone.local(*time_array)
|
Time.zone.local(*time_array)
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
Time.send(ActiveRecord::Base.default_timezone, *time_array)
|
Time.send(::ActiveRecord::Base.default_timezone, *time_array)
|
||||||
rescue ArgumentError, TypeError
|
rescue ArgumentError, TypeError
|
||||||
zone_offset = ActiveRecord::Base.default_timezone == :local ? DateTime.local_offset : 0
|
zone_offset = ::ActiveRecord::Base.default_timezone == :local ? DateTime.local_offset : 0
|
||||||
time_array.pop # remove microseconds
|
time_array.pop # remove microseconds
|
||||||
DateTime.civil(*(time_array << zone_offset))
|
DateTime.civil(*(time_array << zone_offset))
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||||
|
|
||||||
describe ValidatesTimeliness::InstanceTag, :type => :helper do
|
describe ValidatesTimeliness::ActionView::InstanceTag, :type => :helper do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@person = Person.new
|
@person = Person.new
|
||||||
@ -1,7 +1,7 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||||
|
|
||||||
describe ValidatesTimeliness::AttributeMethods do
|
describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
||||||
include ValidatesTimeliness::AttributeMethods
|
include ValidatesTimeliness::ActiveRecord::AttributeMethods
|
||||||
include ValidatesTimeliness::Validations
|
include ValidatesTimeliness::Validations
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@ -1,6 +1,6 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||||
|
|
||||||
describe ValidatesTimeliness::MultiparameterAttributes do
|
describe ValidatesTimeliness::ActiveRecord::MultiparameterAttributes do
|
||||||
def obj
|
def obj
|
||||||
@obj ||= Person.new
|
@obj ||= Person.new
|
||||||
end
|
end
|
||||||
@ -1,4 +1,4 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
||||||
|
|
||||||
describe ValidatesTimeliness::CoreExtensions::Date do
|
describe ValidatesTimeliness::CoreExtensions::Date do
|
||||||
before do
|
before do
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
||||||
|
|
||||||
describe ValidatesTimeliness::Formats do
|
describe ValidatesTimeliness::Formats do
|
||||||
attr_reader :formats
|
attr_reader :formats
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
||||||
|
|
||||||
describe "ValidateTimeliness matcher" do
|
describe "ValidateTimeliness matcher" do
|
||||||
attr_accessor :no_validation, :with_validation
|
attr_accessor :no_validation, :with_validation
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
||||||
|
|
||||||
describe ValidatesTimeliness::Validations do
|
describe ValidatesTimeliness::Validations do
|
||||||
attr_accessor :person
|
attr_accessor :person
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user