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.
- checksums.yaml +7 -0
- data/Appraisals +11 -0
- data/CHANGELOG.rdoc +190 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +178 -0
- data/LICENSE +20 -0
- data/README.md +329 -0
- data/Rakefile +30 -0
- data/ae-validates_timeliness.gemspec +25 -0
- data/autotest/discover.rb +1 -0
- data/gemfiles/rails_4_0.gemfile +15 -0
- data/gemfiles/rails_4_0.gemfile.lock +152 -0
- data/gemfiles/rails_4_1.gemfile +15 -0
- data/gemfiles/rails_4_1.gemfile.lock +156 -0
- data/gemfiles/rails_4_2.gemfile +15 -0
- data/gemfiles/rails_4_2.gemfile.lock +178 -0
- data/init.rb +1 -0
- data/lib/ae-validates_timeliness.rb +1 -0
- data/lib/ae-validates_timeliness/version.rb +3 -0
- data/lib/generators/validates_timeliness/install_generator.rb +16 -0
- data/lib/generators/validates_timeliness/templates/en.yml +16 -0
- data/lib/generators/validates_timeliness/templates/validates_timeliness.rb +40 -0
- data/lib/validates_timeliness.rb +70 -0
- data/lib/validates_timeliness/attribute_methods.rb +97 -0
- data/lib/validates_timeliness/conversion.rb +70 -0
- data/lib/validates_timeliness/extensions.rb +14 -0
- data/lib/validates_timeliness/extensions/date_time_select.rb +61 -0
- data/lib/validates_timeliness/extensions/multiparameter_handler.rb +80 -0
- data/lib/validates_timeliness/helper_methods.rb +23 -0
- data/lib/validates_timeliness/orm/active_record.rb +94 -0
- data/lib/validates_timeliness/orm/mongoid.rb +63 -0
- data/lib/validates_timeliness/railtie.rb +15 -0
- data/lib/validates_timeliness/validator.rb +117 -0
- metadata +159 -0
    
        data/init.rb
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            require 'validates_timeliness'
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            require "validates_timeliness"
         | 
| @@ -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
         |