sp-validates_timeliness 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +183 -0
  3. data/LICENSE +20 -0
  4. data/README.md +323 -0
  5. data/Rakefile +30 -0
  6. data/init.rb +1 -0
  7. data/lib/generators/validates_timeliness/install_generator.rb +16 -0
  8. data/lib/generators/validates_timeliness/templates/en.yml +16 -0
  9. data/lib/generators/validates_timeliness/templates/validates_timeliness.rb +40 -0
  10. data/lib/jc-validates_timeliness.rb +1 -0
  11. data/lib/validates_timeliness.rb +70 -0
  12. data/lib/validates_timeliness/attribute_methods.rb +95 -0
  13. data/lib/validates_timeliness/conversion.rb +70 -0
  14. data/lib/validates_timeliness/extensions.rb +14 -0
  15. data/lib/validates_timeliness/extensions/date_time_select.rb +61 -0
  16. data/lib/validates_timeliness/extensions/multiparameter_handler.rb +80 -0
  17. data/lib/validates_timeliness/helper_methods.rb +23 -0
  18. data/lib/validates_timeliness/orm/active_record.rb +53 -0
  19. data/lib/validates_timeliness/orm/mongoid.rb +63 -0
  20. data/lib/validates_timeliness/railtie.rb +15 -0
  21. data/lib/validates_timeliness/validator.rb +117 -0
  22. data/lib/validates_timeliness/version.rb +3 -0
  23. data/spec/spec_helper.rb +109 -0
  24. data/spec/support/config_helper.rb +36 -0
  25. data/spec/support/model_helpers.rb +27 -0
  26. data/spec/support/tag_matcher.rb +35 -0
  27. data/spec/support/test_model.rb +59 -0
  28. data/spec/validates_timeliness/attribute_methods_spec.rb +86 -0
  29. data/spec/validates_timeliness/conversion_spec.rb +234 -0
  30. data/spec/validates_timeliness/extensions/date_time_select_spec.rb +163 -0
  31. data/spec/validates_timeliness/extensions/multiparameter_handler_spec.rb +44 -0
  32. data/spec/validates_timeliness/helper_methods_spec.rb +30 -0
  33. data/spec/validates_timeliness/orm/active_record_spec.rb +244 -0
  34. data/spec/validates_timeliness/orm/mongoid_spec.rb +189 -0
  35. data/spec/validates_timeliness/validator/after_spec.rb +57 -0
  36. data/spec/validates_timeliness/validator/before_spec.rb +57 -0
  37. data/spec/validates_timeliness/validator/is_at_spec.rb +61 -0
  38. data/spec/validates_timeliness/validator/on_or_after_spec.rb +57 -0
  39. data/spec/validates_timeliness/validator/on_or_before_spec.rb +57 -0
  40. data/spec/validates_timeliness/validator_spec.rb +246 -0
  41. data/spec/validates_timeliness_spec.rb +43 -0
  42. data/validates_timeliness.gemspec +25 -0
  43. metadata +157 -0
@@ -0,0 +1,23 @@
1
+ module ActiveModel
2
+ module Validations
3
+
4
+ module HelperMethods
5
+ def validates_date(*attr_names)
6
+ timeliness_validation_for attr_names, :date
7
+ end
8
+
9
+ def validates_time(*attr_names)
10
+ timeliness_validation_for attr_names, :time
11
+ end
12
+
13
+ def validates_datetime(*attr_names)
14
+ timeliness_validation_for attr_names, :datetime
15
+ end
16
+
17
+ def timeliness_validation_for(attr_names, type)
18
+ validates_with TimelinessValidator, _merge_attributes(attr_names).merge(:type => type)
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,53 @@
1
+ module ValidatesTimeliness
2
+ module ORM
3
+ module ActiveRecord
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ public
8
+
9
+ def timeliness_attribute_timezone_aware?(attr_name)
10
+ create_time_zone_conversion_attribute?(attr_name, timeliness_column_for_attribute(attr_name))
11
+ end
12
+
13
+ def timeliness_attribute_type(attr_name)
14
+ timeliness_column_for_attribute(attr_name).type
15
+ end
16
+
17
+ def timeliness_column_for_attribute(attr_name)
18
+ columns_hash.fetch(attr_name.to_s) do |attr_name|
19
+ validation_type = _validators[attr_name.to_sym].find {|v| v.kind == :timeliness }.type
20
+ ::ActiveRecord::ConnectionAdapters::Column.new(attr_name, nil, validation_type.to_s)
21
+ end
22
+ end
23
+
24
+ def define_attribute_methods
25
+ super.tap do |attribute_methods_generated|
26
+ define_timeliness_methods true
27
+ end
28
+ end
29
+
30
+ protected
31
+
32
+ def timeliness_type_cast_code(attr_name, var_name)
33
+ type = timeliness_attribute_type(attr_name)
34
+
35
+ method_body = super
36
+ method_body << "\n#{var_name} = #{var_name}.to_date if #{var_name}" if type == :date
37
+ method_body
38
+ end
39
+ end
40
+
41
+ def reload(*args)
42
+ _clear_timeliness_cache
43
+ super
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+
50
+ class ActiveRecord::Base
51
+ include ValidatesTimeliness::AttributeMethods
52
+ include ValidatesTimeliness::ORM::ActiveRecord
53
+ end
@@ -0,0 +1,63 @@
1
+ module ValidatesTimeliness
2
+ module ORM
3
+ module Mongoid
4
+ extend ActiveSupport::Concern
5
+ # You need define the fields before you define the validations.
6
+ # It is best to use the plugin parser to avoid errors on a bad
7
+ # field value in Mongoid. Parser will return nil rather than error.
8
+
9
+ module ClassMethods
10
+ public
11
+
12
+ # Mongoid has no bulk attribute method definition hook. It defines
13
+ # them with each field definition. So we likewise define them after
14
+ # each validation is defined.
15
+ #
16
+ def timeliness_validation_for(attr_names, type)
17
+ super
18
+ attr_names.each { |attr_name| define_timeliness_write_method(attr_name) }
19
+ end
20
+
21
+ def timeliness_attribute_type(attr_name)
22
+ {
23
+ Date => :date,
24
+ Time => :time,
25
+ DateTime => :datetime
26
+ }[fields[attr_name.to_s].type] || :datetime
27
+ end
28
+
29
+ protected
30
+
31
+ def timeliness_type_cast_code(attr_name, var_name)
32
+ type = timeliness_attribute_type(attr_name)
33
+
34
+ "#{var_name} = Timeliness::Parser.parse(value, :#{type})"
35
+ end
36
+
37
+ end
38
+
39
+ module Reload
40
+ def reload(*args)
41
+ _clear_timeliness_cache
42
+ super
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ module Mongoid::Document
50
+ include ValidatesTimeliness::AttributeMethods
51
+ include ValidatesTimeliness::ORM::Mongoid
52
+
53
+ # Pre-2.3 reload
54
+ if (instance_methods & ['reload', :reload]).present?
55
+ def reload_with_timeliness
56
+ _clear_timeliness_cache
57
+ reload_without_timeliness
58
+ end
59
+ alias_method_chain :reload, :timeliness
60
+ else
61
+ include ValidatesTimeliness::ORM::Mongoid::Reload
62
+ end
63
+ end
@@ -0,0 +1,15 @@
1
+ module ValidatesTimeliness
2
+ class Railtie < Rails::Railtie
3
+ initializer "validates_timeliness.initialize_active_record", :after => 'active_record.initialize_timezone' do
4
+ ActiveSupport.on_load(:active_record) do
5
+ ValidatesTimeliness.default_timezone = ActiveRecord::Base.default_timezone
6
+ ValidatesTimeliness.extend_orms << :active_record
7
+ ValidatesTimeliness.load_orms
8
+ end
9
+ end
10
+
11
+ initializer "validates_timeliness.initialize_restriction_errors" do
12
+ ValidatesTimeliness.ignore_restriction_errors = !Rails.env.test?
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,117 @@
1
+ require 'active_model'
2
+ require 'active_model/validator'
3
+
4
+ module ValidatesTimeliness
5
+ class Validator < ActiveModel::EachValidator
6
+ include Conversion
7
+
8
+ attr_reader :type
9
+
10
+ RESTRICTIONS = {
11
+ :is_at => :==,
12
+ :before => :<,
13
+ :after => :>,
14
+ :on_or_before => :<=,
15
+ :on_or_after => :>=,
16
+ }.freeze
17
+
18
+ DEFAULT_ERROR_VALUE_FORMATS = {
19
+ :date => '%Y-%m-%d',
20
+ :time => '%H:%M:%S',
21
+ :datetime => '%Y-%m-%d %H:%M:%S'
22
+ }.freeze
23
+
24
+ RESTRICTION_ERROR_MESSAGE = "Error occurred validating %s for %s restriction:\n%s"
25
+
26
+ # Prior to version 4.1, Rails will call `#setup`, if defined. This method is deprecated in Rails 4.1 and removed
27
+ # altogether in 4.2.
28
+ SETUP_DEPRECATED = ActiveModel.respond_to?(:version) && ActiveModel.version >= Gem::Version.new('4.1')
29
+
30
+ def self.kind
31
+ :timeliness
32
+ end
33
+
34
+ def initialize(options)
35
+ @type = options.delete(:type) || :datetime
36
+ @allow_nil, @allow_blank = options.delete(:allow_nil), options.delete(:allow_blank)
37
+
38
+ if range = options.delete(:between)
39
+ raise ArgumentError, ":between must be a Range or an Array" unless range.is_a?(Range) || range.is_a?(Array)
40
+ options[:on_or_after] = range.first
41
+ if range.is_a?(Range) && range.exclude_end?
42
+ options[:before] = range.last
43
+ else
44
+ options[:on_or_before] = range.last
45
+ end
46
+ end
47
+
48
+ @restrictions_to_check = RESTRICTIONS.keys & options.keys
49
+ super
50
+ setup_timeliness_validated_attributes(options[:class]) if options[:class]
51
+ end
52
+
53
+ def setup_timeliness_validated_attributes(model)
54
+ if model.respond_to?(:timeliness_validated_attributes)
55
+ model.timeliness_validated_attributes ||= []
56
+ model.timeliness_validated_attributes |= @attributes
57
+ end
58
+ end
59
+
60
+ # Provide backwards compatibility for Rails < 4.1, which expects `#setup` to be defined.
61
+ alias_method :setup, :setup_timeliness_validated_attributes unless SETUP_DEPRECATED
62
+
63
+ def validate_each(record, attr_name, value)
64
+ raw_value = attribute_raw_value(record, attr_name) || value
65
+ return if (@allow_nil && raw_value.nil?) || (@allow_blank && raw_value.blank?)
66
+
67
+ @timezone_aware = timezone_aware?(record, attr_name)
68
+ value = parse(raw_value) if value.is_a?(String) || options[:format]
69
+ value = type_cast_value(value, @type)
70
+
71
+ add_error(record, attr_name, :"invalid_#{@type}") and return if value.blank?
72
+
73
+ validate_restrictions(record, attr_name, value)
74
+ end
75
+
76
+ def validate_restrictions(record, attr_name, value)
77
+ @restrictions_to_check.each do |restriction|
78
+ begin
79
+ restriction_value = type_cast_value(evaluate_option_value(options[restriction], record), @type)
80
+ unless value.send(RESTRICTIONS[restriction], restriction_value)
81
+ add_error(record, attr_name, restriction, restriction_value) and break
82
+ end
83
+ rescue => e
84
+ unless ValidatesTimeliness.ignore_restriction_errors
85
+ message = RESTRICTION_ERROR_MESSAGE % [ attr_name, restriction.inspect, e.message ]
86
+ add_error(record, attr_name, message) and break
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ def add_error(record, attr_name, message, value=nil)
93
+ value = format_error_value(value) if value
94
+ message_options = { :message => options[:"#{message}_message"], :restriction => value }
95
+ record.errors.add(attr_name, message, message_options)
96
+ end
97
+
98
+ def format_error_value(value)
99
+ format = I18n.t(@type, :default => DEFAULT_ERROR_VALUE_FORMATS[@type], :scope => 'validates_timeliness.error_value_formats')
100
+ value.strftime(format)
101
+ end
102
+
103
+ def attribute_raw_value(record, attr_name)
104
+ record.respond_to?(:_timeliness_raw_value_for) &&
105
+ record._timeliness_raw_value_for(attr_name.to_s)
106
+ end
107
+
108
+ def timezone_aware?(record, attr_name)
109
+ record.class.respond_to?(:timeliness_attribute_timezone_aware?) &&
110
+ record.class.timeliness_attribute_timezone_aware?(attr_name)
111
+ end
112
+
113
+ end
114
+ end
115
+
116
+ # Compatibility with ActiveModel validates method which matches option keys to their validator class
117
+ ActiveModel::Validations::TimelinessValidator = ValidatesTimeliness::Validator
@@ -0,0 +1,3 @@
1
+ module ValidatesTimeliness
2
+ VERSION = '3.1.2'
3
+ end
@@ -0,0 +1,109 @@
1
+ require 'rspec'
2
+ require 'rspec/collection_matchers'
3
+
4
+ # Coveralls
5
+ require "coveralls"
6
+
7
+ Coveralls.wear! do
8
+ add_filter 'spec'
9
+ end
10
+
11
+ require 'active_model'
12
+ require 'active_model/validations'
13
+ require 'active_record'
14
+ require 'action_view'
15
+ require 'timecop'
16
+
17
+ require 'validates_timeliness'
18
+
19
+ require 'support/test_model'
20
+ require 'support/model_helpers'
21
+ require 'support/config_helper'
22
+ require 'support/tag_matcher'
23
+
24
+ ValidatesTimeliness.setup do |c|
25
+ c.extend_orms = [ :active_record ]
26
+ c.enable_date_time_select_extension!
27
+ c.enable_multiparameter_extension!
28
+ c.default_timezone = :utc
29
+ end
30
+
31
+ Time.zone = 'Australia/Melbourne'
32
+
33
+ LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/../lib/generators/validates_timeliness/templates/en.yml')
34
+ I18n.load_path.unshift(LOCALE_PATH)
35
+
36
+ # Extend TestModel as you would another ORM/ODM module
37
+ module TestModelShim
38
+ extend ActiveSupport::Concern
39
+ include ValidatesTimeliness::AttributeMethods
40
+
41
+ module ClassMethods
42
+ # Hook method for attribute method generation
43
+ def define_attribute_methods(attr_names)
44
+ super
45
+ define_timeliness_methods
46
+ end
47
+
48
+ # Hook into native time zone handling check, if any
49
+ def timeliness_attribute_timezone_aware?(attr_name)
50
+ false
51
+ end
52
+ end
53
+ end
54
+
55
+ class Person
56
+ include TestModel
57
+ attribute :birth_date, :date
58
+ attribute :birth_time, :time
59
+ attribute :birth_datetime, :datetime
60
+
61
+ define_attribute_methods model_attributes.keys
62
+ end
63
+
64
+ class PersonWithShim < Person
65
+ include TestModelShim
66
+ end
67
+
68
+ I18n.enforce_available_locales = false
69
+
70
+ ActiveRecord::Base.default_timezone = :utc
71
+ ActiveRecord::Base.time_zone_aware_attributes = true
72
+ ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
73
+ ActiveRecord::Migration.verbose = false
74
+ ActiveRecord::Schema.define(:version => 1) do
75
+ create_table :employees, :force => true do |t|
76
+ t.string :first_name
77
+ t.string :last_name
78
+ t.date :birth_date
79
+ t.time :birth_time
80
+ t.datetime :birth_datetime
81
+ end
82
+ end
83
+
84
+ class Employee < ActiveRecord::Base
85
+ attr_accessor :redefined_birth_date_called
86
+ validates_date :birth_date, :allow_nil => true
87
+ validates_date :birth_time, :allow_nil => true
88
+ validates_date :birth_datetime, :allow_nil => true
89
+
90
+ def birth_date=(value)
91
+ self.redefined_birth_date_called = true
92
+ super
93
+ end
94
+ end
95
+
96
+ RSpec.configure do |c|
97
+ c.mock_with :rspec
98
+ c.include(ModelHelpers)
99
+ c.include(ConfigHelper)
100
+ c.include(TagMatcher)
101
+ c.before do
102
+ reset_validation_setup_for(Person)
103
+ reset_validation_setup_for(PersonWithShim)
104
+ end
105
+
106
+ c.filter_run_excluding :active_record => lambda {|version|
107
+ !(::ActiveRecord::VERSION::STRING.to_s =~ /^#{version.to_s}/)
108
+ }
109
+ end
@@ -0,0 +1,36 @@
1
+ module ConfigHelper
2
+ extend ActiveSupport::Concern
3
+
4
+ # Justin French tip
5
+ def with_config(preference_name, temporary_value)
6
+ old_value = ValidatesTimeliness.send(preference_name)
7
+ ValidatesTimeliness.send(:"#{preference_name}=", temporary_value)
8
+ yield
9
+ ensure
10
+ ValidatesTimeliness.send(:"#{preference_name}=", old_value)
11
+ end
12
+
13
+ def reset_validation_setup_for(model_class)
14
+ model_class.reset_callbacks(:validate)
15
+ model_class._validators.clear
16
+ model_class.timeliness_validated_attributes = [] if model_class.respond_to?(:timeliness_validated_attributes)
17
+ model_class.undefine_attribute_methods
18
+ # This is a hack to avoid a disabled super method error message after an undef
19
+ model_class.instance_variable_set(:@generated_attribute_methods, nil)
20
+ model_class.instance_variable_set(:@generated_timeliness_methods, nil)
21
+ end
22
+
23
+ module ClassMethods
24
+ def with_config(preference_name, temporary_value)
25
+ original_config_value = ValidatesTimeliness.send(preference_name)
26
+
27
+ before(:all) do
28
+ ValidatesTimeliness.send(:"#{preference_name}=", temporary_value)
29
+ end
30
+
31
+ after(:all) do
32
+ ValidatesTimeliness.send(:"#{preference_name}=", original_config_value)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ module ModelHelpers
2
+
3
+ # Some test helpers from Rails source
4
+ def invalid!(attr_name, values, error = nil)
5
+ with_each_person_value(attr_name, values) do |record, value|
6
+ expect(record).to be_invalid
7
+ expect(record.errors[attr_name].size).to be >= 1
8
+ expect(record.errors[attr_name].first).to eq(error) if error
9
+ end
10
+ end
11
+
12
+ def valid!(attr_name, values)
13
+ with_each_person_value(attr_name, values) do |record, value|
14
+ expect(record).to be_valid
15
+ end
16
+ end
17
+
18
+ def with_each_person_value(attr_name, values)
19
+ record = Person.new
20
+ values = [values] unless values.is_a?(Array)
21
+ values.each do |value|
22
+ record.send("#{attr_name}=", value)
23
+ yield record, value
24
+ end
25
+ end
26
+
27
+ end