ae-validates_timeliness 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'validates_timeliness'
@@ -0,0 +1 @@
1
+ require "validates_timeliness"
@@ -0,0 +1,3 @@
1
+ module ValidatesTimeliness
2
+ VERSION = '4.0.0'
3
+ end
@@ -0,0 +1,16 @@
1
+ module ValidatesTimeliness
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ desc "Copy ValidatesTimeliness default files"
5
+ source_root File.expand_path('../templates', __FILE__)
6
+
7
+ def copy_initializers
8
+ copy_file 'validates_timeliness.rb', 'config/initializers/validates_timeliness.rb'
9
+ end
10
+
11
+ def copy_locale_file
12
+ copy_file 'en.yml', 'config/locales/validates_timeliness.en.yml'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ invalid_date: "is not a valid date"
5
+ invalid_time: "is not a valid time"
6
+ invalid_datetime: "is not a valid datetime"
7
+ is_at: "must be at %{restriction}"
8
+ before: "must be before %{restriction}"
9
+ on_or_before: "must be on or before %{restriction}"
10
+ after: "must be after %{restriction}"
11
+ on_or_after: "must be on or after %{restriction}"
12
+ validates_timeliness:
13
+ error_value_formats:
14
+ date: '%Y-%m-%d'
15
+ time: '%H:%M:%S'
16
+ datetime: '%Y-%m-%d %H:%M:%S'
@@ -0,0 +1,40 @@
1
+ ValidatesTimeliness.setup do |config|
2
+ # Extend ORM/ODMs for full support (:active_record, :mongoid).
3
+ # config.extend_orms = [ :active_record ]
4
+ #
5
+ # Default timezone
6
+ # config.default_timezone = :utc
7
+ #
8
+ # Set the dummy date part for a time type values.
9
+ # config.dummy_date_for_time_type = [ 2000, 1, 1 ]
10
+ #
11
+ # Ignore errors when restriction options are evaluated
12
+ # config.ignore_restriction_errors = false
13
+ #
14
+ # Re-display invalid values in date/time selects
15
+ # config.enable_date_time_select_extension!
16
+ #
17
+ # Handle multiparameter date/time values strictly
18
+ # config.enable_multiparameter_extension!
19
+ #
20
+ # Shorthand date and time symbols for restrictions
21
+ # config.restriction_shorthand_symbols.update(
22
+ # :now => lambda { Time.current },
23
+ # :today => lambda { Date.current }
24
+ # )
25
+ #
26
+ # Use the plugin date/time parser which is stricter and extendable
27
+ # config.use_plugin_parser = false
28
+ #
29
+ # Add one or more formats making them valid. e.g. add_formats(:date, 'd(st|rd|th) of mmm, yyyy')
30
+ # config.parser.add_formats()
31
+ #
32
+ # Remove one or more formats making them invalid. e.g. remove_formats(:date, 'dd/mm/yyy')
33
+ # config.parser.remove_formats()
34
+ #
35
+ # Change the amiguous year threshold when parsing a 2 digit year
36
+ # config.parser.ambiguous_year_threshold = 30
37
+ #
38
+ # Treat ambiguous dates, such as 01/02/1950, as a Non-US date.
39
+ # config.parser.remove_us_formats
40
+ end
@@ -0,0 +1,70 @@
1
+ require 'date'
2
+ require 'active_support/concern'
3
+ require 'active_support/core_ext/module'
4
+ require 'active_support/core_ext/hash/except'
5
+ require 'active_support/core_ext/string/conversions'
6
+ require 'active_support/core_ext/date/acts_like'
7
+ require 'active_support/core_ext/date/conversions'
8
+ require 'active_support/core_ext/time/acts_like'
9
+ require 'active_support/core_ext/time/conversions'
10
+ require 'active_support/core_ext/date_time/acts_like'
11
+ require 'active_support/core_ext/date_time/conversions'
12
+ require 'timeliness'
13
+
14
+ Timeliness.module_eval do
15
+ class << self
16
+ alias :dummy_date_for_time_type :date_for_time_type
17
+ alias :dummy_date_for_time_type= :date_for_time_type=
18
+ alias :remove_us_formats :use_euro_formats
19
+ end
20
+ end
21
+
22
+ module ValidatesTimeliness
23
+ autoload :VERSION, 'validates_timeliness/version'
24
+
25
+ class << self
26
+ delegate :default_timezone, :default_timezone=, :dummy_date_for_time_type, :dummy_date_for_time_type=, :to => Timeliness
27
+
28
+ attr_accessor :extend_orms, :ignore_restriction_errors, :restriction_shorthand_symbols, :use_plugin_parser
29
+ end
30
+
31
+ # Extend ORM/ODMs for full support (:active_record, :mongoid).
32
+ self.extend_orms = []
33
+
34
+ # Ignore errors when restriction options are evaluated
35
+ self.ignore_restriction_errors = false
36
+
37
+ # Shorthand time and date symbols for restrictions
38
+ self.restriction_shorthand_symbols = {
39
+ :now => lambda { Time.current },
40
+ :today => lambda { Date.current }
41
+ }
42
+
43
+ # Use the plugin date/time parser which is stricter and extensible
44
+ self.use_plugin_parser = false
45
+
46
+ # Default timezone
47
+ self.default_timezone = :utc
48
+
49
+ # Set the dummy date part for a time type values.
50
+ self.dummy_date_for_time_type = [ 2000, 1, 1 ]
51
+
52
+ # Setup method for plugin configuration
53
+ def self.setup
54
+ yield self
55
+ load_orms
56
+ end
57
+
58
+ def self.load_orms
59
+ extend_orms.each {|orm| require "validates_timeliness/orm/#{orm}" }
60
+ end
61
+
62
+ def self.parser; Timeliness end
63
+ end
64
+
65
+ require 'validates_timeliness/conversion'
66
+ require 'validates_timeliness/validator'
67
+ require 'validates_timeliness/helper_methods'
68
+ require 'validates_timeliness/attribute_methods'
69
+ require 'validates_timeliness/extensions'
70
+ require 'validates_timeliness/railtie' if defined?(Rails)
@@ -0,0 +1,97 @@
1
+ module ValidatesTimeliness
2
+ module AttributeMethods
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class << self
7
+ attr_accessor :timeliness_validated_attributes
8
+ end
9
+ self.timeliness_validated_attributes = []
10
+ end
11
+
12
+ module ClassMethods
13
+
14
+ public
15
+ # Override in ORM shim
16
+ def timeliness_attribute_timezone_aware?(attr_name)
17
+ false
18
+ end
19
+
20
+ # Override in ORM shim
21
+ def timeliness_attribute_type(attr_name)
22
+ :datetime
23
+ end
24
+
25
+ def undefine_attribute_methods
26
+ super
27
+ undefine_timeliness_attribute_methods
28
+ end
29
+
30
+ protected
31
+
32
+ def define_timeliness_methods(before_type_cast=false)
33
+ return if timeliness_validated_attributes.blank?
34
+ timeliness_validated_attributes.each do |attr_name|
35
+ define_attribute_timeliness_methods(attr_name, before_type_cast)
36
+ end
37
+ end
38
+
39
+ def define_attribute_timeliness_methods(attr_name, before_type_cast=false)
40
+ define_timeliness_write_method(attr_name)
41
+ define_timeliness_before_type_cast_method(attr_name) if before_type_cast
42
+ end
43
+
44
+ def define_timeliness_write_method(attr_name)
45
+ method_body, line = <<-EOV, __LINE__ + 1
46
+ def #{attr_name}=(value)
47
+ original_value = value
48
+ @timeliness_cache ||= {}
49
+ @timeliness_cache["#{attr_name}"] = original_value
50
+ #{ "if value.is_a?(String)\n#{timeliness_type_cast_code(attr_name, 'value')}\nend" if ValidatesTimeliness.use_plugin_parser }
51
+
52
+ super(value)
53
+ end
54
+ EOV
55
+ generated_timeliness_methods.module_eval(method_body, __FILE__, line)
56
+ end
57
+
58
+ def define_timeliness_before_type_cast_method(attr_name)
59
+ method_body, line = <<-EOV, __LINE__ + 1
60
+ def #{attr_name}_before_type_cast
61
+ _timeliness_raw_value_for('#{attr_name}') || begin
62
+ a = @attributes['#{attr_name}']
63
+ a.respond_to?(:value_before_type_cast) ? a.value_before_type_cast : a
64
+ end
65
+ end
66
+ EOV
67
+ generated_timeliness_methods.module_eval(method_body, __FILE__, line)
68
+ end
69
+
70
+ def timeliness_type_cast_code(attr_name, var_name)
71
+ type = timeliness_attribute_type(attr_name)
72
+ timezone_aware = timeliness_attribute_timezone_aware?(attr_name)
73
+ timezone = :current if timezone_aware
74
+
75
+ "#{var_name} = Timeliness::Parser.parse(#{var_name}, :#{type}, :zone => #{timezone.inspect})"
76
+ end
77
+
78
+ def generated_timeliness_methods
79
+ @generated_timeliness_methods ||= Module.new.tap { |m| include(m) }
80
+ end
81
+
82
+ def undefine_timeliness_attribute_methods
83
+ generated_timeliness_methods.module_eval do
84
+ instance_methods.each { |m| undef_method(m) }
85
+ end
86
+ end
87
+ end
88
+
89
+ def _timeliness_raw_value_for(attr_name)
90
+ @timeliness_cache && @timeliness_cache[attr_name]
91
+ end
92
+
93
+ def _clear_timeliness_cache
94
+ @timeliness_cache = {}
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,70 @@
1
+ module ValidatesTimeliness
2
+ module Conversion
3
+
4
+ def type_cast_value(value, type)
5
+ return nil if value.nil? || !value.respond_to?(:to_time)
6
+
7
+ value = value.in_time_zone if value.acts_like?(:time) && @timezone_aware
8
+ value = case type
9
+ when :time
10
+ dummy_time(value)
11
+ when :date
12
+ value.to_date
13
+ when :datetime
14
+ value.is_a?(Time) ? value : value.to_time
15
+ end
16
+ if options[:ignore_usec] && value.is_a?(Time)
17
+ Timeliness::Parser.make_time(Array(value).reverse[4..9], (:current if @timezone_aware))
18
+ else
19
+ value
20
+ end
21
+ end
22
+
23
+ def dummy_time(value)
24
+ time = if value.acts_like?(:time)
25
+ value = value.in_time_zone if @timezone_aware
26
+ [value.hour, value.min, value.sec]
27
+ else
28
+ [0,0,0]
29
+ end
30
+ values = ValidatesTimeliness.dummy_date_for_time_type + time
31
+ Timeliness::Parser.make_time(values, (:current if @timezone_aware))
32
+ end
33
+
34
+ def evaluate_option_value(value, record)
35
+ case value
36
+ when Time, Date
37
+ value
38
+ when String
39
+ parse(value)
40
+ when Symbol
41
+ if !record.respond_to?(value) && restriction_shorthand?(value)
42
+ ValidatesTimeliness.restriction_shorthand_symbols[value].call
43
+ else
44
+ evaluate_option_value(record.send(value), record)
45
+ end
46
+ when Proc
47
+ result = value.arity > 0 ? value.call(record) : value.call
48
+ evaluate_option_value(result, record)
49
+ else
50
+ value
51
+ end
52
+ end
53
+
54
+ def restriction_shorthand?(symbol)
55
+ ValidatesTimeliness.restriction_shorthand_symbols.keys.include?(symbol)
56
+ end
57
+
58
+ def parse(value)
59
+ return nil if value.nil?
60
+ if ValidatesTimeliness.use_plugin_parser
61
+ Timeliness::Parser.parse(value, @type, :zone => (:current if @timezone_aware), :format => options[:format], :strict => false)
62
+ else
63
+ @timezone_aware ? Time.zone.parse(value, Time.zone.now) : value.to_time(ValidatesTimeliness.default_timezone)
64
+ end
65
+ rescue ArgumentError, TypeError
66
+ nil
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,14 @@
1
+ module ValidatesTimeliness
2
+ module Extensions
3
+ autoload :DateTimeSelect, 'validates_timeliness/extensions/date_time_select'
4
+ autoload :MultiparameterHandler, 'validates_timeliness/extensions/multiparameter_handler'
5
+ end
6
+
7
+ def self.enable_date_time_select_extension!
8
+ ::ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::Extensions::DateTimeSelect)
9
+ end
10
+
11
+ def self.enable_multiparameter_extension!
12
+ ::ActiveRecord::Base.send(:include, ValidatesTimeliness::Extensions::MultiparameterHandler)
13
+ end
14
+ end
@@ -0,0 +1,61 @@
1
+ module ValidatesTimeliness
2
+ module Extensions
3
+ module DateTimeSelect
4
+ extend ActiveSupport::Concern
5
+
6
+ # Intercepts the date and time select helpers to reuse the values from
7
+ # the params rather than the parsed value. This allows invalid date/time
8
+ # values to be redisplayed instead of blanks to aid correction by the user.
9
+ # It's a minor usability improvement which is rarely an issue for the user.
10
+
11
+ included do
12
+ alias_method_chain :datetime_selector, :timeliness
13
+ alias_method_chain :value, :timeliness
14
+ end
15
+
16
+ class TimelinessDateTime
17
+ attr_accessor :year, :month, :day, :hour, :min, :sec
18
+
19
+ def initialize(year, month, day, hour, min, sec)
20
+ @year, @month, @day, @hour, @min, @sec = year, month, day, hour, min, sec
21
+ end
22
+
23
+ # adapted from activesupport/lib/active_support/core_ext/date_time/calculations.rb, line 36 (3.0.7)
24
+ def change(options)
25
+ TimelinessDateTime.new(
26
+ options[:year] || year,
27
+ options[:month] || month,
28
+ options[:day] || day,
29
+ options[:hour] || hour,
30
+ options[:min] || (options[:hour] ? 0 : min),
31
+ options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec)
32
+ )
33
+ end
34
+ end
35
+
36
+ def datetime_selector_with_timeliness(*args)
37
+ @timeliness_date_or_time_tag = true
38
+ datetime_selector_without_timeliness(*args)
39
+ end
40
+
41
+ def value_with_timeliness(object)
42
+ unless @timeliness_date_or_time_tag && @template_object.params[@object_name]
43
+ return value_without_timeliness(object)
44
+ end
45
+
46
+ @template_object.params[@object_name]
47
+
48
+ pairs = @template_object.params[@object_name].select {|k,v| k =~ /^#{@method_name}\(/ }
49
+ return value_without_timeliness(object) if pairs.empty?
50
+
51
+ values = [nil] * 6
52
+ pairs.map do |(param, value)|
53
+ position = param.scan(/\((\d+)\w+\)/).first.first
54
+ values[position.to_i-1] = value.to_i
55
+ end
56
+
57
+ TimelinessDateTime.new(*values)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,80 @@
1
+ module ValidatesTimeliness
2
+ module Extensions
3
+ module MultiparameterHandler
4
+ extend ActiveSupport::Concern
5
+
6
+ # Stricter handling of date and time values from multiparameter
7
+ # assignment from the date/time select view helpers
8
+
9
+ included do
10
+ alias_method_chain :instantiate_time_object, :timeliness
11
+ alias_method :execute_callstack_for_multiparameter_attributes, :execute_callstack_for_multiparameter_attributes_with_timeliness
12
+ alias_method :read_value_from_parameter, :read_value_from_parameter_with_timeliness
13
+ end
14
+
15
+ private
16
+
17
+ def invalid_multiparameter_date_or_time_as_string(values)
18
+ value = [values[0], *values[1..2].map {|s| s.to_s.rjust(2,"0")} ].join("-")
19
+ value += ' ' + values[3..5].map {|s| s.to_s.rjust(2, "0") }.join(":") unless values[3..5].empty?
20
+ value
21
+ end
22
+
23
+ def instantiate_time_object_with_timeliness(name, values)
24
+ validate_multiparameter_date_values(values) {
25
+ instantiate_time_object_without_timeliness(name, values)
26
+ }
27
+ end
28
+
29
+ def instantiate_date_object(name, values)
30
+ validate_multiparameter_date_values(values) {
31
+ Date.new(*values)
32
+ }
33
+ end
34
+
35
+ # Yield if date values are valid
36
+ def validate_multiparameter_date_values(values)
37
+ if values[0..2].all?{ |v| v.present? } && Date.valid_civil?(*values[0..2])
38
+ yield
39
+ else
40
+ invalid_multiparameter_date_or_time_as_string(values)
41
+ end
42
+ end
43
+
44
+ def read_value_from_parameter_with_timeliness(name, values_from_param)
45
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
46
+ values = values_from_param.is_a?(Hash) ? values_from_param.to_a.sort_by(&:first).map(&:last) : values_from_param
47
+
48
+ if values.empty? || values.all?{ |v| v.nil? }
49
+ nil
50
+ elsif klass == Time
51
+ instantiate_time_object(name, values)
52
+ elsif klass == Date
53
+ instantiate_date_object(name, values)
54
+ else
55
+ if respond_to?(:read_other_parameter_value)
56
+ read_date_parameter_value(name, values_from_param)
57
+ else
58
+ klass.new(*values)
59
+ end
60
+ end
61
+ end
62
+
63
+ def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack)
64
+ errors = []
65
+ callstack.each do |name, values_with_empty_parameters|
66
+ begin
67
+ send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
68
+ rescue => ex
69
+ values = values_with_empty_parameters.is_a?(Hash) ? values_with_empty_parameters.values : values_with_empty_parameters
70
+ errors << ActiveRecord::AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
71
+ end
72
+ end
73
+ unless errors.empty?
74
+ raise ActiveRecord::MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
75
+ end
76
+ end
77
+
78
+ end
79
+ end
80
+ end