validates_timeliness 2.3.2 → 3.0.0.beta

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