dm-validations 0.10.1 → 0.10.2

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 (102) hide show
  1. data/Rakefile +23 -18
  2. data/VERSION +1 -0
  3. data/dm-validations.gemspec +188 -0
  4. data/lib/dm-validations.rb +25 -75
  5. data/lib/dm-validations/auto_validate.rb +7 -7
  6. data/lib/dm-validations/contextual_validators.rb +4 -18
  7. data/lib/dm-validations/exceptions.rb +3 -1
  8. data/lib/dm-validations/support/context.rb +40 -1
  9. data/lib/dm-validations/validation_errors.rb +2 -13
  10. data/lib/dm-validations/validators/absent_field_validator.rb +2 -2
  11. data/lib/dm-validations/validators/acceptance_validator.rb +14 -16
  12. data/lib/dm-validations/validators/confirmation_validator.rb +17 -15
  13. data/lib/dm-validations/validators/format_validator.rb +18 -11
  14. data/lib/dm-validations/validators/generic_validator.rb +45 -15
  15. data/lib/dm-validations/validators/length_validator.rb +2 -11
  16. data/lib/dm-validations/validators/method_validator.rb +2 -2
  17. data/lib/dm-validations/validators/numeric_validator.rb +8 -6
  18. data/lib/dm-validations/validators/primitive_validator.rb +1 -1
  19. data/lib/dm-validations/validators/uniqueness_validator.rb +13 -9
  20. data/lib/dm-validations/validators/within_validator.rb +4 -3
  21. data/spec/fixtures/basketball_player.rb +0 -3
  22. data/spec/fixtures/bill_of_landing.rb +1 -1
  23. data/spec/fixtures/city.rb +0 -1
  24. data/spec/fixtures/company.rb +1 -3
  25. data/spec/fixtures/corporate_world.rb +0 -6
  26. data/spec/fixtures/country.rb +0 -1
  27. data/spec/fixtures/event.rb +1 -1
  28. data/spec/fixtures/page.rb +2 -2
  29. data/spec/fixtures/reservation.rb +1 -1
  30. data/spec/fixtures/scm_operation.rb +0 -8
  31. data/spec/integration/absent_field_validator/absent_field_validator_spec.rb +6 -2
  32. data/spec/integration/acceptance_validator/acceptance_validator_spec.rb +36 -36
  33. data/spec/integration/automatic_validation/custom_messages_for_inferred_validation_spec.rb +2 -2
  34. data/spec/integration/automatic_validation/disabling_inferred_validation_spec.rb +4 -4
  35. data/spec/integration/automatic_validation/inferred_boolean_properties_validation_spec.rb +2 -2
  36. data/spec/integration/automatic_validation/inferred_float_property_validation_spec.rb +2 -0
  37. data/spec/integration/automatic_validation/inferred_format_validation_spec.rb +3 -1
  38. data/spec/integration/automatic_validation/inferred_integer_properties_validation_spec.rb +2 -0
  39. data/spec/integration/automatic_validation/inferred_length_validation_spec.rb +6 -2
  40. data/spec/integration/automatic_validation/inferred_presence_validation_spec.rb +8 -4
  41. data/spec/integration/automatic_validation/inferred_primitive_validation_spec.rb +3 -1
  42. data/spec/integration/automatic_validation/spec_helper.rb +6 -6
  43. data/spec/integration/block_validator/block_validator_spec.rb +1 -1
  44. data/spec/integration/conditional_validation/if_condition_spec.rb +3 -1
  45. data/spec/integration/confirmation_validator/confirmation_validator_spec.rb +3 -1
  46. data/spec/integration/datamapper_models/association_validation_spec.rb +4 -1
  47. data/spec/integration/datamapper_models/inheritance_spec.rb +6 -2
  48. data/spec/integration/duplicated_validations/duplicated_validations_spec.rb +3 -1
  49. data/spec/integration/format_validator/email_format_validator_spec.rb +5 -1
  50. data/spec/integration/format_validator/format_validator_spec.rb +5 -1
  51. data/spec/integration/format_validator/regexp_validator_spec.rb +5 -1
  52. data/spec/integration/format_validator/url_format_validator_spec.rb +8 -4
  53. data/spec/integration/length_validator/default_value_spec.rb +4 -2
  54. data/spec/integration/length_validator/equality_spec.rb +3 -1
  55. data/spec/integration/length_validator/error_message_spec.rb +3 -1
  56. data/spec/integration/length_validator/maximum_spec.rb +3 -1
  57. data/spec/integration/length_validator/minimum_spec.rb +3 -1
  58. data/spec/integration/length_validator/range_spec.rb +3 -1
  59. data/spec/integration/method_validator/method_validator_spec.rb +3 -1
  60. data/spec/integration/numeric_validator/equality_with_float_type_spec.rb +5 -1
  61. data/spec/integration/numeric_validator/equality_with_integer_type_spec.rb +5 -1
  62. data/spec/integration/numeric_validator/float_type_spec.rb +3 -1
  63. data/spec/integration/numeric_validator/gt_with_float_type_spec.rb +3 -1
  64. data/spec/integration/numeric_validator/gte_with_float_type_spec.rb +3 -1
  65. data/spec/integration/numeric_validator/integer_only_true_spec.rb +11 -9
  66. data/spec/integration/numeric_validator/integer_type_spec.rb +10 -8
  67. data/spec/integration/numeric_validator/lt_with_float_type_spec.rb +3 -1
  68. data/spec/integration/numeric_validator/lte_with_float_type_spec.rb +3 -1
  69. data/spec/integration/primitive_validator/primitive_validator_spec.rb +3 -1
  70. data/spec/integration/pure_ruby_objects/plain_old_ruby_object_validation_spec.rb +6 -6
  71. data/spec/integration/required_field_validator/association_spec.rb +9 -4
  72. data/spec/integration/required_field_validator/boolean_type_value_spec.rb +24 -18
  73. data/spec/integration/required_field_validator/date_type_value_spec.rb +21 -19
  74. data/spec/integration/required_field_validator/datetime_type_value_spec.rb +21 -19
  75. data/spec/integration/required_field_validator/float_type_value_spec.rb +21 -19
  76. data/spec/integration/required_field_validator/integer_type_value_spec.rb +17 -15
  77. data/spec/integration/required_field_validator/shared_examples.rb +5 -3
  78. data/spec/integration/required_field_validator/string_type_value_spec.rb +20 -16
  79. data/spec/integration/required_field_validator/text_type_value_spec.rb +10 -6
  80. data/spec/integration/uniqueness_validator/uniqueness_validator_spec.rb +23 -25
  81. data/spec/integration/within_validator/within_validator_spec.rb +7 -3
  82. data/spec/public/resource_spec.rb +5 -6
  83. data/spec/rcov.opts +6 -0
  84. data/spec/spec.opts +2 -0
  85. data/spec/unit/contextual_validators/emptiness_spec.rb +1 -1
  86. data/spec/unit/contextual_validators/execution_spec.rb +9 -16
  87. data/spec/unit/generic_validator/equality_operator_spec.rb +3 -3
  88. data/spec/unit/validation_errors/adding_spec.rb +1 -1
  89. data/spec/unit/validation_errors/emptiness_spec.rb +1 -1
  90. data/spec/unit/validation_errors/enumerable_spec.rb +1 -1
  91. data/tasks/ci.rake +1 -0
  92. data/tasks/metrics.rake +36 -0
  93. data/tasks/spec.rake +41 -0
  94. data/tasks/yard.rake +9 -0
  95. data/tasks/yardstick.rake +19 -0
  96. metadata +36 -18
  97. data/History.rdoc +0 -45
  98. data/Manifest.txt +0 -139
  99. data/TODO +0 -16
  100. data/lib/dm-validations/version.rb +0 -5
  101. data/tasks/install.rb +0 -13
  102. data/tasks/spec.rb +0 -25
@@ -1,3 +1,5 @@
1
1
  module DataMapper
2
- class ValidationError < StandardError; end
2
+ class ValidationError < StandardError; end
3
+
4
+ class InvalidContextError < StandardError; end
3
5
  end
@@ -24,6 +24,7 @@ module DataMapper
24
24
  #
25
25
  # @api private
26
26
  def validation_context(context = default_validation_context)
27
+ assert_valid_context(context)
27
28
  validation_context_stack << context
28
29
  begin
29
30
  yield
@@ -43,9 +44,47 @@ module DataMapper
43
44
  # Returns the current validation context or nil if none has been pushed
44
45
  # @api private
45
46
  def current_validation_context
46
- validation_context_stack.last
47
+ context = validation_context_stack.last
48
+ valid_context?(context) ? context : :default
47
49
  end
48
50
 
51
+ # Return the contexts for the model
52
+ #
53
+ # @return [Hash]
54
+ # the hash of contexts for the model
55
+ #
56
+ # @api private
57
+ def contexts
58
+ model.validators.contexts
59
+ end
60
+
61
+ # Test if the context is valid for the model
62
+ #
63
+ # @param [Symbol] context
64
+ # the context to test
65
+ #
66
+ # @return [Boolean]
67
+ # true if the context is valid for the model
68
+ #
69
+ # @api private
70
+ def valid_context?(context)
71
+ contexts.empty? || contexts.key?(context)
72
+ end
73
+
74
+ # Assert that the context is valid for this model
75
+ #
76
+ # @param [Symbol] context
77
+ # the context to test
78
+ #
79
+ # @raise [InvalidContextError]
80
+ # raised if the context is not valid for this model
81
+ #
82
+ # @api private
83
+ def assert_valid_context(context)
84
+ unless valid_context?(context)
85
+ raise InvalidContextError, "#{context} is an invalid context, known contexts are #{contexts.keys.inspect}"
86
+ end
87
+ end
49
88
  end
50
89
 
51
90
  include Context
@@ -48,7 +48,7 @@ module DataMapper
48
48
 
49
49
  def initialize(resource)
50
50
  @resource = resource
51
- @errors = Dictionary.new
51
+ @errors = Dictionary.new { |h,k| h[k] = [] }
52
52
  end
53
53
 
54
54
  # Clear existing validation errors.
@@ -84,17 +84,6 @@ module DataMapper
84
84
  end
85
85
  end
86
86
 
87
- # Return validation errors for a particular field name or an empty array
88
- #
89
- # This method is a necessary requirement for active_model compatibility.
90
- #
91
- # @param [Symbol] field_name the name of the field you want an error for
92
- # @return [Array<Array<String>>]
93
- # array of validation errors or empty array, if there are no errors on given field
94
- def [](field_name)
95
- errors[field_name] ||= []
96
- end
97
-
98
87
  # Return validation errors for a particular field_name.
99
88
  #
100
89
  # @param [Symbol] field_name the name of the field you want an error for
@@ -113,7 +102,7 @@ module DataMapper
113
102
  end
114
103
 
115
104
  def empty?
116
- @errors.empty?
105
+ @errors.all? { |property_name, errors| errors.empty? }
117
106
  end
118
107
 
119
108
  def method_missing(meth, *args, &block)
@@ -7,12 +7,12 @@ module DataMapper
7
7
  # @since 0.9
8
8
  class AbsentFieldValidator < GenericValidator
9
9
  def call(target)
10
- return true if target.send(self.field_name).blank?
10
+ return true if target.validation_property_value(field_name).blank?
11
11
 
12
12
  error_message = self.options[:message] || ValidationErrors.default_error_message(:absent, field_name)
13
13
  add_error(target, error_message, field_name)
14
14
 
15
- return false
15
+ false
16
16
  end
17
17
  end # class AbsentFieldValidator
18
18
 
@@ -10,30 +10,28 @@ module DataMapper
10
10
  def initialize(field_name, options = {})
11
11
  super
12
12
 
13
- # ||= true makes value true if it used to be false
14
- @options[:allow_nil] = true unless(options.include?(:allow_nil) && [false, nil, "false", "f"].include?(options[:allow_nil]))
15
- @options[:accept] ||= ["1", 1, "true", true, "t"]
13
+ @options[:allow_nil] = true unless @options.key?(:allow_nil)
14
+
15
+ @options[:accept] ||= [ '1', 1, 'true', true, 't' ]
16
16
  @options[:accept] = Array(@options[:accept])
17
17
  end
18
18
 
19
19
  def call(target)
20
- unless valid?(target)
21
- error_message = @options[:message] || ValidationErrors.default_error_message(:accepted, field_name)
22
- add_error(target, error_message, field_name)
23
- return false
24
- end
20
+ return true if valid?(target)
21
+
22
+ error_message = @options[:message] || ValidationErrors.default_error_message(:accepted, field_name)
23
+ add_error(target, error_message, field_name)
25
24
 
26
- return true
25
+ false
27
26
  end
28
27
 
29
- def valid?(target)
30
- field_value = target.validation_property_value(field_name)
31
- # Allow empty values
32
- return true if @options[:allow_nil] && field_value.blank?
28
+ private
33
29
 
34
- @options[:accept].include?(field_value)
30
+ def valid?(target)
31
+ value = target.validation_property_value(field_name)
32
+ return true if allow_nil?(value)
33
+ @options[:accept].include?(value)
35
34
  end
36
-
37
35
  end # class AcceptanceValidator
38
36
 
39
37
  module ValidatesIsAccepted
@@ -41,7 +39,7 @@ module DataMapper
41
39
  ##
42
40
  # Validates that the attributes's value is in the set of accepted values.
43
41
  #
44
- # @option :allow_nil<Boolean> true if nil is allowed, false if nil is not
42
+ # @option :allow_nil<Boolean> true if nil is allowed, false if not
45
43
  # allowed. Default is true.
46
44
  # @option :accept<Array> a list of accepted values.
47
45
  # Default are ["1", 1, "true", true, "t"]).
@@ -10,33 +10,34 @@ module DataMapper
10
10
 
11
11
  def initialize(field_name, options = {})
12
12
  super
13
+
14
+ set_optional_by_default
15
+
13
16
  @confirm_field_name = (options[:confirm] || "#{field_name}_confirmation").to_sym
14
- @options[:allow_nil] = true unless @options.has_key?(:allow_nil)
15
17
  end
16
18
 
17
19
  def call(target)
18
- unless valid?(target)
19
- error_message = @options[:message] || ValidationErrors.default_error_message(:confirmation, field_name)
20
- add_error(target, error_message, field_name)
21
- return false
22
- end
20
+ return true if valid?(target)
23
21
 
24
- return true
22
+ error_message = @options[:message] || ValidationErrors.default_error_message(:confirmation, field_name)
23
+ add_error(target, error_message, field_name)
24
+
25
+ false
25
26
  end
26
27
 
28
+ private
29
+
27
30
  def valid?(target)
28
- field_value = target.send(field_name)
29
- return true if @options[:allow_nil] && field_value.blank?
30
- return false if !@options[:allow_nil] && field_value.blank?
31
+ value = target.validation_property_value(field_name)
32
+ return true if optional?(value)
31
33
 
32
34
  if target.model.properties.named?(field_name)
33
35
  return true unless target.attribute_dirty?(field_name)
34
36
  end
35
37
 
36
38
  confirm_value = target.instance_variable_get("@#{@confirm_field_name}")
37
- field_value == confirm_value
39
+ value == confirm_value
38
40
  end
39
-
40
41
  end # class ConfirmationValidator
41
42
 
42
43
  module ValidatesIsConfirmed
@@ -47,9 +48,10 @@ module DataMapper
47
48
  # password, for which you use both password and password_confirmation
48
49
  # attributes.
49
50
  #
50
- # @option :allow_nil<Boolean> true/false (default is true)
51
- # @option :confirm<Symbol> the attribute that you want to validate
52
- # against (default is firstattr_confirmation)
51
+ # @option :allow_nil<Boolean> true/false (default is true)
52
+ # @option :allow_blank<Boolean> true/false (default is true)
53
+ # @option :confirm<Symbol> the attribute that you want to validate
54
+ # against (default is firstattr_confirmation)
53
55
  #
54
56
  # @example [Usage]
55
57
  # require 'dm-validations'
@@ -20,31 +20,37 @@ module DataMapper
20
20
  def initialize(field_name, options = {})
21
21
  super
22
22
 
23
- @options[:allow_nil] = true unless @options.include?(:allow_nil)
23
+ set_optional_by_default
24
24
  end
25
25
 
26
26
  def call(target)
27
+ return true if valid?(target)
28
+
29
+ value = target.validation_property_value(field_name)
30
+
31
+ error_message = @options[:message] || ValidationErrors.default_error_message(:invalid, field_name)
32
+ add_error(target, error_message.try_call(humanized_field_name, value), field_name)
33
+
34
+ false
35
+ end
36
+
37
+ private
38
+
39
+ def valid?(target)
27
40
  value = target.validation_property_value(field_name)
28
- return true if @options[:allow_nil] && value.blank?
41
+ return true if optional?(value)
29
42
 
30
43
  validation = @options[:as] || @options[:with]
31
44
 
32
45
  raise "No such predefined format '#{validation}'" if validation.is_a?(Symbol) && !FORMATS.has_key?(validation)
33
46
  validator = validation.is_a?(Symbol) ? FORMATS[validation][0] : validation
34
47
 
35
- valid = case validator
48
+ case validator
36
49
  when Proc then validator.call(value)
37
- when Regexp then (value.is_a?(Fixnum) ? value.to_s : value) =~ validator
50
+ when Regexp then (value.kind_of?(Numeric) ? value.to_s : value) =~ validator
38
51
  else
39
52
  raise UnknownValidationFormat, "Can't determine how to validate #{target.class}##{field_name} with #{validator.inspect}"
40
53
  end
41
-
42
- return true if valid
43
-
44
- error_message = @options[:message] || ValidationErrors.default_error_message(:invalid, field_name)
45
- add_error(target, error_message.try_call(humanized_field_name, value), field_name)
46
-
47
- false
48
54
  end
49
55
  end # class FormatValidator
50
56
 
@@ -58,6 +64,7 @@ module DataMapper
58
64
  # via a Proc or Regexp passed to the the :as or :with options.
59
65
  #
60
66
  # @option :allow_nil<Boolean> true/false (default is true)
67
+ # @option :allow_blank<Boolean> true/false (default is true)
61
68
  # @option :as<Format, Proc, Regexp> the pre-defined format, Proc or Regexp to validate against
62
69
  # @option :with<Format, Proc, Regexp> an alias for :as
63
70
  #
@@ -29,10 +29,10 @@ module DataMapper
29
29
  # that is sub-classing this GenericValidator
30
30
  #
31
31
  def initialize(field_name, options = {})
32
- @if_clause = options.delete(:if)
33
- @unless_clause = options.delete(:unless)
34
-
35
- @field_name, @options = field_name, options
32
+ @field_name = field_name
33
+ @options = options.except(:if, :unless)
34
+ @if_clause = options[:if]
35
+ @unless_clause = options[:unless]
36
36
  @humanized_field_name = Extlib::Inflection.humanize(@field_name)
37
37
  end
38
38
 
@@ -57,7 +57,7 @@ module DataMapper
57
57
  # against
58
58
  # @return <Boolean> true if valid, otherwise false
59
59
  def call(target)
60
- raise NotImplementedError, "DataMapper::Validate::GenericValidator::call must be overriden in a subclass"
60
+ raise NotImplementedError, "#{self.class}#call must be implemented"
61
61
  end
62
62
 
63
63
  # Determines if this validator should be run against the
@@ -68,24 +68,54 @@ module DataMapper
68
68
  # @return <Boolean> true if should be run, otherwise false
69
69
  def execute?(target)
70
70
  if unless_clause = self.unless_clause
71
- if unless_clause.is_a?(Symbol)
72
- return false if target.send(unless_clause)
73
- elsif unless_clause.respond_to?(:call)
74
- return false if unless_clause.call(target)
75
- end
71
+ return !target.__send__(unless_clause) if unless_clause.kind_of?(Symbol)
72
+ return !unless_clause.call(target) if unless_clause.respond_to?(:call)
76
73
  end
77
74
 
78
75
  if if_clause = self.if_clause
79
- if if_clause.is_a?(Symbol)
80
- return target.send(if_clause)
81
- elsif if_clause.respond_to?(:call)
82
- return if_clause.call(target)
83
- end
76
+ return target.__send__(if_clause) if if_clause.kind_of?(Symbol)
77
+ return if_clause.call(target) if if_clause.respond_to?(:call)
84
78
  end
85
79
 
86
80
  true
87
81
  end
88
82
 
83
+ # Set the default value for allow_nil and allow_blank
84
+ #
85
+ # @param [Boolean] default value
86
+ # @return <undefined>
87
+ def set_optional_by_default(default = true)
88
+ [ :allow_nil, :allow_blank ].each do |key|
89
+ @options[key] = true unless options.key?(key)
90
+ end
91
+ end
92
+
93
+ # Test the value to see if it is blank or nil, and if it is allowed
94
+ #
95
+ # @param <Object> value to test
96
+ # @return <Boolean> true if blank/nil is allowed, and the value is blank/nil
97
+ def optional?(value)
98
+ return allow_nil?(value) if value.nil?
99
+ return allow_blank?(value) if value.blank?
100
+ false
101
+ end
102
+
103
+ # Test if the value is nil and is allowed
104
+ #
105
+ # @param <Object> value to test
106
+ # @return <Boolean> true if nil is allowed and value is nil
107
+ def allow_nil?(value)
108
+ @options[:allow_nil] if value.nil?
109
+ end
110
+
111
+ # Test if the value is blank and is allowed
112
+ #
113
+ # @param <Object> value to test
114
+ # @return <Boolean> true if blank is allowed and value is blank
115
+ def allow_blank?(value)
116
+ @options[:allow_blank] if value.blank?
117
+ end
118
+
89
119
  # Returns true if validators are equal
90
120
  #
91
121
  # Note that this intentionally do
@@ -43,7 +43,7 @@ module DataMapper
43
43
  # @api semipublic
44
44
  def call(target)
45
45
  value = target.validation_property_value(field_name)
46
- return true if allow_nil? && value.blank?
46
+ return true if optional?(value)
47
47
 
48
48
  return true unless error_message = error_message_for(value)
49
49
 
@@ -54,16 +54,6 @@ module DataMapper
54
54
 
55
55
  private
56
56
 
57
- # Return if the validation allows nil values
58
- #
59
- # @return [Boolean]
60
- # true if the validation allows nil, false if not
61
- #
62
- # @api private
63
- def allow_nil?
64
- options.fetch(:allow_nil, false)
65
- end
66
-
67
57
  # Return the error messages for the value if it is invalid
68
58
  #
69
59
  # @param [#to_s] value
@@ -172,6 +162,7 @@ module DataMapper
172
162
  # you specify).
173
163
  #
174
164
  # @option :allow_nil<Boolean> true/false (default is true)
165
+ # @option :allow_blank<Boolean> true/false (default is true)
175
166
  # @option :minimum ensures that the attribute's length is greater than
176
167
  # or equal to the supplied value
177
168
  # @option :min alias for :minimum
@@ -9,11 +9,11 @@ module DataMapper
9
9
 
10
10
  def initialize(field_name, options={})
11
11
  super
12
- @options[:method] = @field_name unless @options.has_key?(:method)
12
+ @options[:method] = @field_name unless @options.key?(:method)
13
13
  end
14
14
 
15
15
  def call(target)
16
- result, message = target.send(@options[:method])
16
+ result, message = target.__send__(@options[:method])
17
17
  add_error(target, message, field_name) unless result
18
18
  result
19
19
  end
@@ -8,8 +8,8 @@ module DataMapper
8
8
  class NumericValidator < GenericValidator
9
9
 
10
10
  def call(target)
11
- value = target.send(field_name)
12
- return true if allow_nil? && value.blank?
11
+ value = target.validation_property_value(field_name)
12
+ return true if optional?(value)
13
13
 
14
14
  errors = []
15
15
 
@@ -31,10 +31,6 @@ module DataMapper
31
31
 
32
32
  private
33
33
 
34
- def allow_nil?
35
- options.fetch(:allow_nil, false)
36
- end
37
-
38
34
  def integer_only?
39
35
  options.fetch(:integer_only, false)
40
36
  end
@@ -131,6 +127,12 @@ module DataMapper
131
127
  #
132
128
  # Options are:
133
129
  #
130
+ # :allow_nil => true | false
131
+ # true if number can be nil, false if not
132
+ #
133
+ # :allow_blank => true | false
134
+ # true if number can be blank, false if not
135
+ #
134
136
  # :message => "Error message for %s"
135
137
  # Custom error message, also can be a callable object that takes
136
138
  # an object (for pure Ruby objects) or object and property (for DM resources)