ae-validates_timeliness 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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