validates_timeliness 2.3.2 → 3.0.0.beta

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.
Files changed (59) hide show
  1. data/CHANGELOG +12 -4
  2. data/LICENSE +1 -1
  3. data/README.rdoc +138 -280
  4. data/Rakefile +30 -16
  5. data/lib/generators/validates_timeliness/install_generator.rb +17 -0
  6. data/lib/generators/validates_timeliness/templates/en.yml +16 -0
  7. data/lib/generators/validates_timeliness/templates/validates_timeliness.rb +22 -0
  8. data/lib/validates_timeliness.rb +46 -52
  9. data/lib/validates_timeliness/attribute_methods.rb +51 -0
  10. data/lib/validates_timeliness/conversion.rb +69 -0
  11. data/lib/validates_timeliness/extensions.rb +14 -0
  12. data/lib/validates_timeliness/extensions/date_time_select.rb +45 -0
  13. data/lib/validates_timeliness/extensions/multiparameter_handler.rb +31 -0
  14. data/lib/validates_timeliness/helper_methods.rb +41 -0
  15. data/lib/validates_timeliness/orms/active_record.rb +14 -0
  16. data/lib/validates_timeliness/parser.rb +389 -17
  17. data/lib/validates_timeliness/validator.rb +37 -200
  18. data/lib/validates_timeliness/version.rb +1 -1
  19. data/spec/model_helpers.rb +27 -0
  20. data/spec/spec_helper.rb +74 -43
  21. data/spec/test_model.rb +56 -0
  22. data/spec/validates_timeliness/attribute_methods_spec.rb +36 -0
  23. data/spec/validates_timeliness/conversion_spec.rb +204 -0
  24. data/spec/validates_timeliness/extensions/date_time_select_spec.rb +178 -0
  25. data/spec/validates_timeliness/extensions/multiparameter_handler_spec.rb +21 -0
  26. data/spec/validates_timeliness/helper_methods_spec.rb +36 -0
  27. data/spec/{formats_spec.rb → validates_timeliness/parser_spec.rb} +105 -71
  28. data/spec/validates_timeliness/validator/after_spec.rb +59 -0
  29. data/spec/validates_timeliness/validator/before_spec.rb +59 -0
  30. data/spec/validates_timeliness/validator/is_at_spec.rb +63 -0
  31. data/spec/validates_timeliness/validator/on_or_after_spec.rb +59 -0
  32. data/spec/validates_timeliness/validator/on_or_before_spec.rb +59 -0
  33. data/spec/validates_timeliness/validator_spec.rb +172 -0
  34. data/validates_timeliness.gemspec +30 -0
  35. metadata +42 -40
  36. data/TODO +0 -8
  37. data/lib/validates_timeliness/action_view/instance_tag.rb +0 -52
  38. data/lib/validates_timeliness/active_record/attribute_methods.rb +0 -77
  39. data/lib/validates_timeliness/active_record/multiparameter_attributes.rb +0 -69
  40. data/lib/validates_timeliness/formats.rb +0 -368
  41. data/lib/validates_timeliness/locale/en.new.yml +0 -18
  42. data/lib/validates_timeliness/locale/en.old.yml +0 -18
  43. data/lib/validates_timeliness/matcher.rb +0 -1
  44. data/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb +0 -162
  45. data/lib/validates_timeliness/validation_methods.rb +0 -46
  46. data/spec/action_view/instance_tag_spec.rb +0 -194
  47. data/spec/active_record/attribute_methods_spec.rb +0 -157
  48. data/spec/active_record/multiparameter_attributes_spec.rb +0 -118
  49. data/spec/ginger_scenarios.rb +0 -19
  50. data/spec/parser_spec.rb +0 -65
  51. data/spec/resources/application.rb +0 -2
  52. data/spec/resources/person.rb +0 -3
  53. data/spec/resources/schema.rb +0 -10
  54. data/spec/resources/sqlite_patch.rb +0 -19
  55. data/spec/spec/rails/matchers/validate_timeliness_spec.rb +0 -245
  56. data/spec/time_travel/MIT-LICENSE +0 -20
  57. data/spec/time_travel/time_extensions.rb +0 -33
  58. data/spec/time_travel/time_travel.rb +0 -12
  59. data/spec/validator_spec.rb +0 -723
data/Rakefile CHANGED
@@ -1,52 +1,66 @@
1
1
  require 'rubygems'
2
+ require 'rake/rdoctask'
2
3
  require 'rake/gempackagetask'
3
4
  require 'rubygems/specification'
4
- require 'date'
5
- require 'spec/rake/spectask'
5
+ require 'rspec/core/rake_task'
6
6
  require 'lib/validates_timeliness/version'
7
7
 
8
- GEM = "validates_timeliness"
8
+ GEM_NAME = "validates_timeliness"
9
9
  GEM_VERSION = ValidatesTimeliness::VERSION
10
10
 
11
11
  spec = Gem::Specification.new do |s|
12
- s.name = GEM
12
+ s.name = GEM_NAME
13
13
  s.version = GEM_VERSION
14
14
  s.platform = Gem::Platform::RUBY
15
- s.rubyforge_project = "validatestime"
15
+ s.rubyforge_project = "validates_timeliness"
16
16
  s.has_rdoc = true
17
- s.extra_rdoc_files = ["README.rdoc", "LICENSE", "TODO", "CHANGELOG"]
18
- s.summary = "Date and time validation plugin for Rails 2.x which allows custom formats"
17
+ s.extra_rdoc_files = ["README.rdoc", "LICENSE", "CHANGELOG"]
18
+ s.summary = %q{Date and time validation plugin for Rails which allows custom formats}
19
19
  s.description = s.summary
20
20
  s.author = "Adam Meehan"
21
21
  s.email = "adam.meehan@gmail.com"
22
22
  s.homepage = "http://github.com/adzap/validates_timeliness"
23
23
 
24
24
  s.require_path = 'lib'
25
- s.autorequire = GEM
26
- s.files = %w(LICENSE README.rdoc Rakefile TODO CHANGELOG) + Dir.glob("{lib,spec}/**/*")
25
+ s.autorequire = GEM_NAME
26
+ s.files = %w(validates_timeliness.gemspec LICENSE CHANGELOG README.rdoc Rakefile) + Dir.glob("{lib,spec}/**/*")
27
27
  end
28
28
 
29
+ desc 'Default: run specs.'
29
30
  task :default => :spec
30
31
 
31
32
  desc "Run specs"
32
- Spec::Rake::SpecTask.new do |t|
33
- t.spec_files = FileList['spec/**/*_spec.rb']
34
- t.spec_opts = %w(--color)
33
+ RSpec::Core::RakeTask.new do |t|
34
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
35
35
  end
36
36
 
37
+ desc "Generate code coverage"
38
+ RSpec::Core::RakeTask.new(:coverage) do |t|
39
+ t.rcov = true
40
+ t.rcov_opts = ['--exclude', 'spec']
41
+ end
42
+
43
+ desc 'Generate documentation for plugin.'
44
+ Rake::RDocTask.new(:rdoc) do |rdoc|
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = 'ValidatesTimeliness'
47
+ rdoc.options << '--line-numbers' << '--inline-source'
48
+ rdoc.rdoc_files.include('README')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
37
51
 
38
52
  Rake::GemPackageTask.new(spec) do |pkg|
39
53
  pkg.gem_spec = spec
40
54
  end
41
55
 
42
- desc "install the gem locally"
56
+ desc "Install the gem locally"
43
57
  task :install => [:package] do
44
- sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
58
+ sh %{gem install pkg/#{GEM_NAME}-#{GEM_VERSION}}
45
59
  end
46
60
 
47
- desc "create a gemspec file"
61
+ desc "Create a gemspec file"
48
62
  task :make_spec do
49
- File.open("#{GEM}.gemspec", "w") do |file|
63
+ File.open("#{GEM_NAME}.gemspec", "w") do |file|
50
64
  file.puts spec.to_ruby
51
65
  end
52
66
  end
@@ -0,0 +1,17 @@
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
+ class_option :template_engine
7
+
8
+ def copy_initializers
9
+ copy_file 'validates_timeliness.rb', 'config/initializers/validates_timeliness.rb'
10
+ end
11
+
12
+ def copy_locale_file
13
+ copy_file 'en.yml', 'config/locales/validates_timeliness.en.yml'
14
+ end
15
+ end
16
+ end
17
+ 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,22 @@
1
+ ValidatesTimeliness.setup do |config|
2
+ # Add plugin to supported ORMs (only :active_record for now)
3
+ # config.extend_orms = [ :active_record ]
4
+ #
5
+ # Set the dummy date part for a time type values.
6
+ # config.dummy_date_for_time_type = [ 2000, 1, 1 ]
7
+ #
8
+ # Ignore errors when restriction options are evaluated
9
+ # config.ignore_restriction_errors = false
10
+ #
11
+ # Re-display invalid values in date/time selects
12
+ # config.enable_date_time_select_extension!
13
+ #
14
+ # Handle multiparameter date/time values strictly
15
+ # config.enable_multiparameter_extension!
16
+ #
17
+ # Shorthand date and time symbols for restrictions
18
+ # config.restriction_shorthand_symbols.update(
19
+ # :now => lambda { Time.now },
20
+ # :today => lambda { Date.today }
21
+ # )
22
+ end
@@ -1,59 +1,53 @@
1
- require 'validates_timeliness/formats'
2
- require 'validates_timeliness/parser'
3
- require 'validates_timeliness/validator'
4
- require 'validates_timeliness/validation_methods'
5
- require 'validates_timeliness/active_record/attribute_methods'
6
- require 'validates_timeliness/active_record/multiparameter_attributes'
7
- require 'validates_timeliness/action_view/instance_tag'
8
- begin
9
- i18n_path = $:.grep(/active_support\/vendor\/i18n-/)
10
- if i18n_path.empty?
11
- require 'i18n/version'
12
- else
13
- require i18n_path[0] + '/version'
14
- end
15
- rescue LoadError
16
- end if defined?(I18n)
1
+ require 'date'
2
+ require 'active_support/core_ext/hash/except'
3
+ require 'active_support/core_ext/string/conversions'
4
+ require 'active_support/core_ext/date/acts_like'
5
+ require 'active_support/core_ext/date/conversions'
6
+ require 'active_support/core_ext/time/acts_like'
7
+ require 'active_support/core_ext/time/conversions'
8
+ require 'active_support/core_ext/date_time/acts_like'
9
+ require 'active_support/core_ext/date_time/conversions'
17
10
 
18
11
  module ValidatesTimeliness
12
+ autoload :VERSION, 'validates_timeliness/version'
13
+
14
+ # Add plugin to supported ORMs (only :active_record for now)
15
+ mattr_accessor :extend_orms
16
+ @@extend_orms = [ defined?(ActiveRecord) && :active_record ].compact
19
17
 
18
+ # User the plugin date/time parser which is stricter and extendable
19
+ mattr_accessor :use_plugin_parser
20
+ @@use_plugin_parser = false
21
+
22
+ # Default timezone
20
23
  mattr_accessor :default_timezone
21
- self.default_timezone = :utc
22
-
23
- mattr_accessor :use_time_zones
24
- self.use_time_zones = false
25
-
26
- I18N_LATEST = defined?(I18n::VERSION) && I18n::VERSION >= '0.4.0'
27
- locale_file = I18N_LATEST ? 'en.new.yml' : 'en.old.yml'
28
- LOCALE_PATH = File.expand_path(File.join(File.dirname(__FILE__),'validates_timeliness','locale',locale_file))
29
-
30
- class << self
31
-
32
- def enable_datetime_select_extension!
33
- enable_datetime_select_invalid_value_extension!
34
- enable_multiparameter_attributes_extension!
35
- end
36
-
37
- def load_error_messages
38
- defaults = YAML::load(IO.read(LOCALE_PATH))['en']
39
- ValidatesTimeliness::Validator.error_value_formats = defaults['validates_timeliness']['error_value_formats'].symbolize_keys
40
-
41
- if defined?(I18n)
42
- I18n.load_path.unshift(LOCALE_PATH)
43
- I18n.reload!
44
- else
45
- errors = defaults['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h }
46
- ::ActiveRecord::Errors.default_error_messages.update(errors)
47
- end
48
- end
49
-
50
- def setup_for_rails
51
- self.default_timezone = ::ActiveRecord::Base.default_timezone
52
- self.use_time_zones = ::ActiveRecord::Base.time_zone_aware_attributes rescue false
53
- self.enable_active_record_datetime_parser!
54
- load_error_messages
55
- end
24
+ @@default_timezone = defined?(ActiveRecord) ? ActiveRecord::Base.default_timezone : :utc
25
+
26
+ # Set the dummy date part for a time type values.
27
+ mattr_accessor :dummy_date_for_time_type
28
+ @@dummy_date_for_time_type = [ 2000, 1, 1 ]
29
+
30
+ # Ignore errors when restriction options are evaluated
31
+ mattr_accessor :ignore_restriction_errors
32
+ @@ignore_restriction_errors = defined?(Rails) ? !Rails.env.test? : false
33
+
34
+ # Shorthand time and date symbols for restrictions
35
+ mattr_accessor :restriction_shorthand_symbols
36
+ @@restriction_shorthand_symbols = {
37
+ :now => lambda { Time.now },
38
+ :today => lambda { Date.today }
39
+ }
40
+
41
+ # Setup method for plugin configuration
42
+ def self.setup
43
+ yield self
44
+ extend_orms.each {|orm| require "validates_timeliness/orms/#{orm}" }
56
45
  end
57
46
  end
58
47
 
59
- ValidatesTimeliness.setup_for_rails
48
+ require 'validates_timeliness/parser'
49
+ require 'validates_timeliness/conversion'
50
+ require 'validates_timeliness/validator'
51
+ require 'validates_timeliness/helper_methods'
52
+ require 'validates_timeliness/attribute_methods'
53
+ require 'validates_timeliness/extensions'
@@ -0,0 +1,51 @@
1
+ module ValidatesTimeliness
2
+ module AttributeMethods
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def define_timeliness_methods(before_type_cast=false)
8
+ timeliness_validated_attributes.each do |attr_name, type|
9
+ define_timeliness_write_method(attr_name, type, timeliness_attribute_timezone_aware?(attr_name))
10
+ define_timeliness_before_type_cast_method(attr_name) if before_type_cast
11
+ end
12
+ end
13
+
14
+ protected
15
+
16
+ def define_timeliness_write_method(attr_name, type, timezone_aware)
17
+ method_body, line = <<-EOV, __LINE__ + 1
18
+ def #{attr_name}=(value)
19
+ @attributes_cache ||= {}
20
+ @attributes_cache["_#{attr_name}_before_type_cast"] = value
21
+ super
22
+ end
23
+ EOV
24
+ class_eval(method_body, __FILE__, line)
25
+ end
26
+
27
+ def define_timeliness_before_type_cast_method(attr_name)
28
+ method_body, line = <<-EOV, __LINE__ + 1
29
+ def #{attr_name}_before_type_cast
30
+ _timeliness_raw_value_for('#{attr_name}')
31
+ end
32
+ EOV
33
+ class_eval(method_body, __FILE__, line)
34
+ end
35
+
36
+ def timeliness_attribute_timezone_aware?(attr_name)
37
+ false
38
+ end
39
+
40
+ end
41
+
42
+ module InstanceMethods
43
+
44
+ def _timeliness_raw_value_for(attr_name)
45
+ @attributes_cache && @attributes_cache["_#{attr_name}_before_type_cast"]
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,69 @@
1
+ module ValidatesTimeliness
2
+ module Conversion
3
+
4
+ def type_cast_value(value, type)
5
+ return nil if value.nil?
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.to_time
15
+ end
16
+ if options[:ignore_usec] && value.is_a?(Time)
17
+ ValidatesTimeliness::Parser.make_time(Array(value).reverse[4..9], @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
+ ValidatesTimeliness::Parser.make_time(values, @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
+ if ValidatesTimeliness.use_plugin_parser
60
+ ValidatesTimeliness::Parser.parse(value, @type, :timezone_aware => @timezone_aware, :format => options[:format], :strict => false)
61
+ else
62
+ @timezone_aware ? Time.zone.parse(value) : value.to_time(ValidatesTimeliness.default_timezone)
63
+ end
64
+ rescue ArgumentError, TypeError
65
+ nil
66
+ end
67
+
68
+ end
69
+ 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,45 @@
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 the
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
+ # Its 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
+ module InstanceMethods
17
+
18
+ TimelinessDateTime = Struct.new(:year, :month, :day, :hour, :min, :sec)
19
+
20
+ def datetime_selector_with_timeliness(*args)
21
+ @timeliness_date_or_time_tag = true
22
+ datetime_selector_without_timeliness(*args)
23
+ end
24
+
25
+ def value_with_timeliness(object)
26
+ unless @timeliness_date_or_time_tag && @template_object.params[@object_name]
27
+ return value_without_timeliness(object)
28
+ end
29
+
30
+ pairs = @template_object.params[@object_name].select {|k,v| k =~ /^#{@method_name}\(/ }
31
+ return value_without_timeliness(object) if pairs.empty?
32
+
33
+ values = [nil] * 6
34
+ pairs.map do |(param, value)|
35
+ position = param.scan(/\(([0-9]*).*\)/).first.first
36
+ values[position.to_i-1] = value
37
+ end
38
+
39
+ TimelinessDateTime.new(*values)
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,31 @@
1
+ module ValidatesTimeliness
2
+ module Extensions
3
+ module MultiparameterHandler
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ alias_method_chain :instantiate_time_object, :timeliness
8
+ end
9
+
10
+ private
11
+
12
+ # Stricter handling of date and time values from multiparameter
13
+ # assignment from the date/time select view helpers
14
+ #
15
+ def instantiate_time_object_with_timeliness(name, values)
16
+ unless Date.valid_civil?(*values[0..2])
17
+ value = [values[0], *values[1..2].map {|s| s.to_s.rjust(2,"0")} ].join("-")
18
+ value += ' ' + values[3..5].map {|s| s.to_s.rjust(2, "0") }.join(":") unless values[3..5].empty?
19
+ return value
20
+ end
21
+
22
+ if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
23
+ Time.zone.local(*values)
24
+ else
25
+ Time.time_with_datetime_fallback(self.class.default_timezone, *values)
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end