stannum 0.3.0 → 0.4.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -1
  3. data/README.md +129 -1263
  4. data/config/locales/en.rb +4 -0
  5. data/lib/stannum/association.rb +293 -0
  6. data/lib/stannum/associations/many.rb +250 -0
  7. data/lib/stannum/associations/one.rb +106 -0
  8. data/lib/stannum/associations.rb +11 -0
  9. data/lib/stannum/attribute.rb +86 -8
  10. data/lib/stannum/constraints/base.rb +3 -5
  11. data/lib/stannum/constraints/enum.rb +1 -1
  12. data/lib/stannum/constraints/equality.rb +1 -1
  13. data/lib/stannum/constraints/format.rb +72 -0
  14. data/lib/stannum/constraints/hashes/extra_keys.rb +7 -12
  15. data/lib/stannum/constraints/identity.rb +1 -1
  16. data/lib/stannum/constraints/properties/base.rb +1 -1
  17. data/lib/stannum/constraints/properties/do_not_match_property.rb +11 -11
  18. data/lib/stannum/constraints/properties/match_property.rb +11 -11
  19. data/lib/stannum/constraints/properties/matching.rb +7 -7
  20. data/lib/stannum/constraints/signature.rb +2 -2
  21. data/lib/stannum/constraints/tuples/extra_items.rb +6 -6
  22. data/lib/stannum/constraints/type.rb +3 -3
  23. data/lib/stannum/constraints/types/array_type.rb +2 -2
  24. data/lib/stannum/constraints/types/hash_type.rb +4 -4
  25. data/lib/stannum/constraints/union.rb +1 -1
  26. data/lib/stannum/constraints/uuid.rb +30 -0
  27. data/lib/stannum/constraints.rb +2 -0
  28. data/lib/stannum/contract.rb +7 -7
  29. data/lib/stannum/contracts/array_contract.rb +2 -7
  30. data/lib/stannum/contracts/base.rb +15 -15
  31. data/lib/stannum/contracts/builder.rb +2 -2
  32. data/lib/stannum/contracts/hash_contract.rb +3 -9
  33. data/lib/stannum/contracts/indifferent_hash_contract.rb +2 -2
  34. data/lib/stannum/contracts/map_contract.rb +6 -10
  35. data/lib/stannum/contracts/parameters/arguments_contract.rb +1 -1
  36. data/lib/stannum/contracts/parameters/keywords_contract.rb +1 -1
  37. data/lib/stannum/contracts/parameters/signature_contract.rb +1 -1
  38. data/lib/stannum/contracts/parameters_contract.rb +4 -4
  39. data/lib/stannum/contracts/tuple_contract.rb +5 -5
  40. data/lib/stannum/entities/associations.rb +451 -0
  41. data/lib/stannum/entities/attributes.rb +116 -18
  42. data/lib/stannum/entities/constraints.rb +3 -2
  43. data/lib/stannum/entities/primary_key.rb +148 -0
  44. data/lib/stannum/entities/properties.rb +30 -8
  45. data/lib/stannum/entities.rb +5 -2
  46. data/lib/stannum/entity.rb +4 -0
  47. data/lib/stannum/errors.rb +9 -13
  48. data/lib/stannum/messages/default_strategy.rb +2 -2
  49. data/lib/stannum/parameter_validation.rb +10 -10
  50. data/lib/stannum/rspec/match_errors_matcher.rb +1 -1
  51. data/lib/stannum/rspec/validate_parameter.rb +2 -2
  52. data/lib/stannum/rspec/validate_parameter_matcher.rb +15 -13
  53. data/lib/stannum/schema.rb +62 -62
  54. data/lib/stannum/support/optional.rb +1 -1
  55. data/lib/stannum/version.rb +4 -4
  56. data/lib/stannum.rb +3 -0
  57. metadata +14 -79
@@ -4,16 +4,71 @@ require 'stannum'
4
4
  require 'stannum/support/optional'
5
5
 
6
6
  module Stannum
7
- # Data object representing an attribute on a struct.
7
+ # Data object representing an attribute on an entity.
8
8
  class Attribute
9
9
  include Stannum::Support::Optional
10
10
 
11
+ # Builder class for defining attribute methods on an entity.
12
+ class Builder
13
+ # @param schema [Stannum::Schema] the attributes schema on which to define
14
+ # methods.
15
+ def initialize(schema)
16
+ @schema = schema
17
+ end
18
+
19
+ # @return [Stannum::Schema] the attributes schema on which to define
20
+ # methods.
21
+ attr_reader :schema
22
+
23
+ # Defines the reader and writer methods for the attribute.
24
+ #
25
+ # @param attribute [Stannum::Attribute]
26
+ def call(attribute)
27
+ define_reader(attribute)
28
+ define_writer(attribute)
29
+ end
30
+
31
+ private
32
+
33
+ def define_reader(attribute)
34
+ schema.define_method(attribute.reader_name) do
35
+ read_attribute(attribute.name, safe: false)
36
+ end
37
+ end
38
+
39
+ def define_writer(attribute) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
40
+ assoc_name = attribute.association_name
41
+
42
+ schema.define_method(attribute.writer_name) do |value|
43
+ previous_value = read_attribute(attribute.name, safe: false)
44
+
45
+ return if previous_value == value
46
+
47
+ if attribute.foreign_key? && !previous_value.nil?
48
+ self
49
+ .class
50
+ .associations[assoc_name]
51
+ .remove_value(self, previous_value)
52
+ end
53
+
54
+ value = attribute.default_value_for(self) if value.nil?
55
+
56
+ write_attribute(attribute.name, value, safe: false)
57
+ end
58
+ end
59
+ end
60
+
11
61
  # @param name [String, Symbol] The name of the attribute. Converted to a
12
62
  # String.
13
63
  # @param options [Hash, nil] Options for the attribute. Converted to a Hash
14
64
  # with Symbol keys. Defaults to an empty Hash.
15
65
  # @param type [Class, Module, String] The type of the attribute. Can be a
16
66
  # Class, a Module, or the name of a class or module.
67
+ #
68
+ # @option options [Object] :default The default value for the attribute.
69
+ # Defaults to nil.
70
+ # @option options [Boolean] :primary_key true if the attribute represents
71
+ # the primary key for the entity; otherwise false. Defaults to false.
17
72
  def initialize(name:, options:, type:)
18
73
  validate_name(name)
19
74
  validate_options(options)
@@ -35,6 +90,12 @@ module Stannum
35
90
  # @return [String] the name of the attribute type Class or Module.
36
91
  attr_reader :type
37
92
 
93
+ # @return [String] the name of the association if the attribute is a foreign
94
+ # key; otherwise false.
95
+ def association_name
96
+ @options[:association_name]
97
+ end
98
+
38
99
  # @return [Object] the default value for the attribute, if any.
39
100
  def default
40
101
  @options[:default]
@@ -46,6 +107,29 @@ module Stannum
46
107
  !@options[:default].nil?
47
108
  end
48
109
 
110
+ # @param context [Object] the context object used to determinet the default
111
+ # value.
112
+ #
113
+ # @return [Object] the value of the default attribute for the given context
114
+ # object, if any.
115
+ def default_value_for(context)
116
+ return default unless default.is_a?(Proc)
117
+
118
+ default.arity.zero? ? default.call : default.call(context)
119
+ end
120
+
121
+ # @return [Boolean] true if the attribute represents the foreign key for an
122
+ # association; otherwise false.
123
+ def foreign_key?
124
+ !!@options[:foreign_key]
125
+ end
126
+
127
+ # @return [Boolean] true if the attribute represents the primary key for the
128
+ # entity; otherwise false.
129
+ def primary_key?
130
+ !!@options[:primary_key]
131
+ end
132
+
49
133
  # @return [Symbol] the name of the reader method for the attribute.
50
134
  def reader_name
51
135
  @reader_name ||= name.intern
@@ -82,13 +166,7 @@ module Stannum
82
166
  end
83
167
 
84
168
  def validate_name(name)
85
- raise ArgumentError, "name can't be blank" if name.nil?
86
-
87
- unless name.is_a?(String) || name.is_a?(Symbol)
88
- raise ArgumentError, 'name must be a String or Symbol'
89
- end
90
-
91
- raise ArgumentError, "name can't be blank" if name.empty?
169
+ tools.assertions.validate_name(name, as: 'name')
92
170
  end
93
171
 
94
172
  def validate_options(options)
@@ -55,9 +55,7 @@ module Stannum::Constraints
55
55
  #
56
56
  # @return [Stannum::Constraints::Base] the cloned constraint.
57
57
  def clone(freeze: nil)
58
- freeze = true if freeze.nil? && RUBY_VERSION <= '3.0.0'
59
-
60
- super(freeze: freeze).copy_properties(self)
58
+ super.copy_properties(self)
61
59
  end
62
60
 
63
61
  # Checks that the given object does not match the constraint.
@@ -80,7 +78,7 @@ module Stannum::Constraints
80
78
  # or behavior, otherwise true.
81
79
  #
82
80
  # @see #matches?
83
- def does_not_match?(actual)
81
+ def does_not_match?(actual) # rubocop:disable Naming/PredicatePrefix
84
82
  !matches?(actual)
85
83
  end
86
84
 
@@ -118,7 +116,7 @@ module Stannum::Constraints
118
116
  # @see #matches?
119
117
  # @see #negated_errors_for
120
118
  def errors_for(actual, errors: nil) # rubocop:disable Lint/UnusedMethodArgument
121
- (errors || Stannum::Errors.new).add(type, message: message)
119
+ (errors || Stannum::Errors.new).add(type, message:)
122
120
  end
123
121
 
124
122
  # Checks the given object against the constraint and returns errors, if any.
@@ -26,7 +26,7 @@ module Stannum::Constraints
26
26
  def initialize(first, *rest, **options)
27
27
  expected_values = rest.unshift(first)
28
28
 
29
- super(expected_values: expected_values, **options)
29
+ super(expected_values:, **options)
30
30
 
31
31
  @matching_values = Set.new(expected_values)
32
32
  end
@@ -27,7 +27,7 @@ module Stannum::Constraints
27
27
  def initialize(expected_value, **options)
28
28
  @expected_value = expected_value
29
29
 
30
- super(expected_value: expected_value, **options)
30
+ super(expected_value:, **options)
31
31
  end
32
32
 
33
33
  # @return [Object] the expected object.
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stannum/constraints'
4
+
5
+ module Stannum::Constraints
6
+ # A Format constraint asserts the value is a string matching the given format.
7
+ #
8
+ # @example Using a Format constraint with a String format.
9
+ # format = 'Greetings'
10
+ # constraint = Stannum::Constraints::Format.new(format)
11
+ #
12
+ # constraint.matches?(nil) #=> false
13
+ # constraint.matches?('Hello, world') #=> false
14
+ # constraint.matches?('Greetings, programs!') #=> true
15
+ #
16
+ # @example Using a Format constraint with a Regex format.
17
+ # format = /\AGreetings/
18
+ # constraint = Stannum::Constraints::Format.new(format)
19
+ #
20
+ # constraint.matches?(nil) #=> false
21
+ # constraint.matches?('Hello, world') #=> false
22
+ # constraint.matches?('Greetings, programs!') #=> true
23
+ class Format < Stannum::Constraints::Base
24
+ # The :type of the error generated for a matching object.
25
+ NEGATED_TYPE = 'stannum.constraints.matches_format'
26
+
27
+ # The :type of the error generated for a non-matching object.
28
+ TYPE = 'stannum.constraints.does_not_match_format'
29
+
30
+ # @param expected_format [Regex, String] The expected object.
31
+ # @param options [Hash<Symbol, Object>] Configuration options for the
32
+ # constraint. Defaults to an empty Hash.
33
+ def initialize(expected_format, **options)
34
+ @expected_format = expected_format
35
+
36
+ super(expected_format:, **options)
37
+ end
38
+
39
+ # @return [Regex, String] the expected format.
40
+ attr_reader :expected_format
41
+
42
+ # (see Stannum::Constraints::Base#errors_for)
43
+ def errors_for(actual, errors: nil)
44
+ return super if type_constraint.matches?(actual)
45
+
46
+ type_constraint.errors_for(actual, errors:)
47
+ end
48
+
49
+ # Checks that the object is a string with the expected format.
50
+ #
51
+ # @return [true, false] true if the object is a string with the expected
52
+ # format, otherwise false.
53
+ #
54
+ # @see Stannum::Constraint#matches?
55
+ def matches?(actual)
56
+ return false unless type_constraint.matches?(actual)
57
+
58
+ if expected_format.is_a?(String)
59
+ actual.include?(expected_format)
60
+ else
61
+ actual.match?(expected_format)
62
+ end
63
+ end
64
+ alias match? matches?
65
+
66
+ private
67
+
68
+ def type_constraint
69
+ @type_constraint ||= Stannum::Constraints::Type.new(String)
70
+ end
71
+ end
72
+ end
@@ -38,19 +38,14 @@ module Stannum::Constraints::Hashes
38
38
  def initialize(expected_keys, **options)
39
39
  validate_expected_keys(expected_keys)
40
40
 
41
- expected_keys =
42
- if expected_keys.is_a?(Array)
43
- Set.new(expected_keys)
44
- else
45
- expected_keys
46
- end
47
-
48
- super(expected_keys: expected_keys, **options)
41
+ expected_keys = Set.new(expected_keys) if expected_keys.is_a?(Array)
42
+
43
+ super(expected_keys:, **options)
49
44
  end
50
45
 
51
46
  # @return [true, false] true if the object responds to #[] and #keys and the
52
47
  # object has at least one key that is not in expected_keys.
53
- def does_not_match?(actual)
48
+ def does_not_match?(actual) # rubocop:disable Naming/PredicatePrefix
54
49
  return false unless hash?(actual)
55
50
 
56
51
  !(Set.new(actual.keys) <= expected_keys) # rubocop:disable Style/InverseMethods
@@ -61,13 +56,13 @@ module Stannum::Constraints::Hashes
61
56
  errors ||= Stannum::Errors.new
62
57
 
63
58
  unless actual.respond_to?(:keys)
64
- return add_invalid_hash_error(actual: actual, errors: errors)
59
+ return add_invalid_hash_error(actual:, errors:)
65
60
  end
66
61
 
67
62
  each_extra_key(actual) do |key, value|
68
63
  key = Stannum::Support::Coercion.error_key(key)
69
64
 
70
- errors[key].add(type, value: value)
65
+ errors[key].add(type, value:)
71
66
  end
72
67
 
73
68
  errors
@@ -96,7 +91,7 @@ module Stannum::Constraints::Hashes
96
91
  def add_invalid_hash_error(actual:, errors:)
97
92
  Stannum::Constraints::Signature
98
93
  .new(:keys)
99
- .errors_for(actual, errors: errors)
94
+ .errors_for(actual, errors:)
100
95
  end
101
96
 
102
97
  def each_extra_key(actual)
@@ -26,7 +26,7 @@ module Stannum::Constraints
26
26
  def initialize(expected_value, **options)
27
27
  @expected_value = expected_value
28
28
 
29
- super(expected_value: expected_value, **options)
29
+ super(expected_value:, **options)
30
30
  end
31
31
 
32
32
  # @return [Object] the expected object.
@@ -37,7 +37,7 @@ module Stannum::Constraints::Properties
37
37
  super(
38
38
  allow_empty: !!options[:allow_empty],
39
39
  allow_nil: !!options[:allow_nil],
40
- property_names: property_names,
40
+ property_names:,
41
41
  **options
42
42
  )
43
43
  end
@@ -41,7 +41,7 @@ module Stannum::Constraints::Properties
41
41
 
42
42
  # @return [true, false] true if the property values match the reference
43
43
  # property value; otherwise false.
44
- def does_not_match?(actual)
44
+ def does_not_match?(actual) # rubocop:disable Naming/PredicatePrefix
45
45
  return false unless can_match_properties?(actual)
46
46
 
47
47
  expected = expected_value(actual)
@@ -49,8 +49,8 @@ module Stannum::Constraints::Properties
49
49
  return false if skip_property?(expected)
50
50
 
51
51
  each_non_matching_property(
52
- actual: actual,
53
- expected: expected,
52
+ actual:,
53
+ expected:,
54
54
  include_all: true
55
55
  )
56
56
  .none?
@@ -63,12 +63,12 @@ module Stannum::Constraints::Properties
63
63
  return invalid_object_errors(errors) unless can_match_properties?(actual)
64
64
 
65
65
  expected = expected_value(actual)
66
- matching = each_matching_property(actual: actual, expected: expected)
66
+ matching = each_matching_property(actual:, expected:)
67
67
 
68
- return generic_errors(errors) if matching.count.zero?
68
+ return generic_errors(errors) if matching.none?
69
69
 
70
- matching.each do |property_name, _|
71
- errors[property_name].add(type, message: message)
70
+ matching.each do |property_name, _| # rubocop:disable Style/HashEachMethods
71
+ errors[property_name].add(type, message:)
72
72
  end
73
73
 
74
74
  errors
@@ -83,7 +83,7 @@ module Stannum::Constraints::Properties
83
83
 
84
84
  return true if skip_property?(expected)
85
85
 
86
- each_matching_property(actual: actual, expected: expected).none?
86
+ each_matching_property(actual:, expected:).none?
87
87
  end
88
88
  alias match? matches?
89
89
 
@@ -95,12 +95,12 @@ module Stannum::Constraints::Properties
95
95
 
96
96
  expected = expected_value(actual)
97
97
  matching = each_non_matching_property(
98
- actual: actual,
99
- expected: expected,
98
+ actual:,
99
+ expected:,
100
100
  include_all: true
101
101
  )
102
102
 
103
- return generic_errors(errors) if matching.count.zero?
103
+ return generic_errors(errors) if matching.none?
104
104
 
105
105
  matching.each do |property_name, value|
106
106
  errors[property_name].add(
@@ -41,7 +41,7 @@ module Stannum::Constraints::Properties
41
41
 
42
42
  # @return [true, false] false if any of the property values match the
43
43
  # reference property value; otherwise true.
44
- def does_not_match?(actual)
44
+ def does_not_match?(actual) # rubocop:disable Naming/PredicatePrefix
45
45
  return false unless can_match_properties?(actual)
46
46
 
47
47
  expected = expected_value(actual)
@@ -49,8 +49,8 @@ module Stannum::Constraints::Properties
49
49
  return false if skip_property?(expected)
50
50
 
51
51
  each_matching_property(
52
- actual: actual,
53
- expected: expected,
52
+ actual:,
53
+ expected:,
54
54
  include_all: true
55
55
  )
56
56
  .none?
@@ -63,14 +63,14 @@ module Stannum::Constraints::Properties
63
63
  return invalid_object_errors(errors) unless can_match_properties?(actual)
64
64
 
65
65
  expected = expected_value(actual)
66
- matching = each_non_matching_property(actual: actual, expected: expected)
66
+ matching = each_non_matching_property(actual:, expected:)
67
67
 
68
- return generic_errors(errors) if matching.count.zero?
68
+ return generic_errors(errors) if matching.none?
69
69
 
70
70
  matching.each do |property_name, value|
71
71
  errors[property_name].add(
72
72
  type,
73
- message: message,
73
+ message:,
74
74
  expected: filter_parameters? ? '[FILTERED]' : expected_value(actual),
75
75
  actual: filter_parameters? ? '[FILTERED]' : value
76
76
  )
@@ -88,7 +88,7 @@ module Stannum::Constraints::Properties
88
88
 
89
89
  return true if skip_property?(expected)
90
90
 
91
- each_non_matching_property(actual: actual, expected: expected).none?
91
+ each_non_matching_property(actual:, expected:).none?
92
92
  end
93
93
  alias match? matches?
94
94
 
@@ -100,14 +100,14 @@ module Stannum::Constraints::Properties
100
100
 
101
101
  expected = expected_value(actual)
102
102
  matching = each_matching_property(
103
- actual: actual,
104
- expected: expected,
103
+ actual:,
104
+ expected:,
105
105
  include_all: true
106
106
  )
107
107
 
108
- return generic_errors(errors) if matching.count.zero?
108
+ return generic_errors(errors) if matching.none?
109
109
 
110
- matching.each do |property_name, _|
110
+ matching.each do |property_name, _| # rubocop:disable Style/HashEachMethods
111
111
  errors[property_name].add(negated_type, message: negated_message)
112
112
  end
113
113
 
@@ -22,7 +22,7 @@ module Stannum::Constraints::Properties
22
22
 
23
23
  validate_reference_name
24
24
 
25
- super(*property_names, reference_name: reference_name, **options)
25
+ super(*property_names, reference_name:, **options)
26
26
  end
27
27
 
28
28
  # @return [String, Symbol] the name of the reference property to compare to.
@@ -39,9 +39,9 @@ module Stannum::Constraints::Properties
39
39
  unless block_given?
40
40
  return to_enum(
41
41
  __method__,
42
- actual: actual,
43
- expected: expected,
44
- include_all: include_all
42
+ actual:,
43
+ expected:,
44
+ include_all:
45
45
  )
46
46
  end
47
47
 
@@ -65,9 +65,9 @@ module Stannum::Constraints::Properties
65
65
  unless block_given?
66
66
  return to_enum(
67
67
  __method__,
68
- actual: actual,
69
- expected: expected,
70
- include_all: include_all
68
+ actual:,
69
+ expected:,
70
+ include_all:
71
71
  )
72
72
  end
73
73
 
@@ -27,7 +27,7 @@ module Stannum::Constraints
27
27
 
28
28
  @expected_methods = expected_methods
29
29
 
30
- super(expected_methods: expected_methods, **options)
30
+ super(expected_methods:, **options)
31
31
  end
32
32
 
33
33
  # @return [Array<String, Symbol>] the methods the object is expected to
@@ -36,7 +36,7 @@ module Stannum::Constraints
36
36
 
37
37
  # @return [true, false] true if the object does not respond to any of the
38
38
  # expected methods; otherwise false.
39
- def does_not_match?(actual)
39
+ def does_not_match?(actual) # rubocop:disable Naming/PredicatePrefix
40
40
  each_missing_method(actual).to_a == expected_methods
41
41
  end
42
42
 
@@ -24,12 +24,12 @@ module Stannum::Constraints::Tuples
24
24
  # @param options [Hash<Symbol, Object>] Configuration options for the
25
25
  # constraint. Defaults to an empty Hash.
26
26
  def initialize(expected_count, **options)
27
- super(expected_count: expected_count, **options)
27
+ super(expected_count:, **options)
28
28
  end
29
29
 
30
30
  # @return [true, false] true if the object responds to #size and the object
31
31
  # size is greater than the number of expected items; otherwise false.
32
- def does_not_match?(actual)
32
+ def does_not_match?(actual) # rubocop:disable Naming/PredicatePrefix
33
33
  return false unless actual.respond_to?(:size)
34
34
 
35
35
  actual.size > expected_count
@@ -40,7 +40,7 @@ module Stannum::Constraints::Tuples
40
40
  errors ||= Stannum::Errors.new
41
41
 
42
42
  unless actual.respond_to?(:size)
43
- return add_invalid_tuple_error(actual: actual, errors: errors)
43
+ return add_invalid_tuple_error(actual:, errors:)
44
44
  end
45
45
 
46
46
  each_extra_item(actual) do |item, index|
@@ -72,13 +72,13 @@ module Stannum::Constraints::Tuples
72
72
  def add_invalid_tuple_error(actual:, errors:)
73
73
  Stannum::Constraints::Signature
74
74
  .new(:size)
75
- .errors_for(actual, errors: errors)
75
+ .errors_for(actual, errors:)
76
76
  end
77
77
 
78
- def each_extra_item(actual, &block)
78
+ def each_extra_item(actual, &)
79
79
  return if matches?(actual)
80
80
 
81
- actual[expected_count..].each.with_index(expected_count, &block)
81
+ actual[expected_count..].each.with_index(expected_count, &)
82
82
  end
83
83
  end
84
84
  end
@@ -47,10 +47,10 @@ module Stannum::Constraints
47
47
  expected_type = resolve_expected_type(expected_type)
48
48
 
49
49
  super(
50
- expected_type: expected_type,
50
+ expected_type:,
51
51
  **resolve_required_option(
52
- optional: optional,
53
- required: required,
52
+ optional:,
53
+ required:,
54
54
  **options
55
55
  )
56
56
  )
@@ -60,7 +60,7 @@ module Stannum::Constraints::Types
60
60
  # otherwise false.
61
61
  #
62
62
  # @see Stannum::Constraints::Types::ArrayType#matches?
63
- def does_not_match?(actual)
63
+ def does_not_match?(actual) # rubocop:disable Naming/PredicatePrefix
64
64
  !matches_type?(actual)
65
65
  end
66
66
 
@@ -126,7 +126,7 @@ module Stannum::Constraints::Types
126
126
  end
127
127
 
128
128
  def error_properties
129
- super().merge(allow_empty: allow_empty?)
129
+ super.merge(allow_empty: allow_empty?)
130
130
  end
131
131
 
132
132
  def item_type_matches?(actual)
@@ -73,7 +73,7 @@ module Stannum::Constraints::Types
73
73
  # false.
74
74
  #
75
75
  # @see Stannum::Constraints::Types::HashType#matches?
76
- def does_not_match?(actual)
76
+ def does_not_match?(actual) # rubocop:disable Naming/PredicatePrefix
77
77
  !matches_type?(actual)
78
78
  end
79
79
 
@@ -85,9 +85,9 @@ module Stannum::Constraints::Types
85
85
 
86
86
  return add_presence_error(errors) unless presence_matches?(actual)
87
87
 
88
- update_key_errors_for(actual: actual, errors: errors)
88
+ update_key_errors_for(actual:, errors:)
89
89
 
90
- update_value_errors_for(actual: actual, errors: errors)
90
+ update_value_errors_for(actual:, errors:)
91
91
 
92
92
  errors
93
93
  end
@@ -155,7 +155,7 @@ module Stannum::Constraints::Types
155
155
  end
156
156
 
157
157
  def error_properties
158
- super().merge(allow_empty: allow_empty?)
158
+ super.merge(allow_empty: allow_empty?)
159
159
  end
160
160
 
161
161
  def key_type_matches?(actual)
@@ -31,7 +31,7 @@ module Stannum::Constraints
31
31
  def initialize(first, *rest, **options)
32
32
  expected_constraints = rest.unshift(first)
33
33
 
34
- super(expected_constraints: expected_constraints, **options)
34
+ super(expected_constraints:, **options)
35
35
 
36
36
  @expected_constraints = expected_constraints
37
37
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stannum/constraints'
4
+
5
+ module Stannum::Constraints
6
+ # A UUID constraint asserts the value is a string in UUID format.
7
+ #
8
+ # @example Using a UUID constraint with a String format.
9
+ # constraint = Stannum::Constraints::Uuid.new
10
+ #
11
+ # constraint.matches?(nil) #=> false
12
+ # constraint.matches?('Hello, world') #=> false
13
+ # constraint.matches?('01234567-89ab-cdef-0123-456789abcdef') #=> true
14
+ class Uuid < Stannum::Constraints::Format
15
+ # The :type of the error generated for a matching object.
16
+ NEGATED_TYPE = 'stannum.constraints.is_a_uuid'
17
+
18
+ # The :type of the error generated for a non-matching object.
19
+ TYPE = 'stannum.constraints.is_not_a_uuid'
20
+
21
+ # Regular expression describing a valid upper- or lower-case UUID.
22
+ UUID_FORMAT = /\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/
23
+
24
+ # @param options [Hash<Symbol, Object>] Configuration options for the
25
+ # constraint. Defaults to an empty Hash.
26
+ def initialize(**options)
27
+ super(UUID_FORMAT, **options)
28
+ end
29
+ end
30
+ end
@@ -12,6 +12,7 @@ module Stannum
12
12
  autoload :Delegator, 'stannum/constraints/delegator'
13
13
  autoload :Enum, 'stannum/constraints/enum'
14
14
  autoload :Equality, 'stannum/constraints/equality'
15
+ autoload :Format, 'stannum/constraints/format'
15
16
  autoload :Hashes, 'stannum/constraints/hashes'
16
17
  autoload :Identity, 'stannum/constraints/identity'
17
18
  autoload :Nothing, 'stannum/constraints/nothing'
@@ -24,5 +25,6 @@ module Stannum
24
25
  autoload :Type, 'stannum/constraints/type'
25
26
  autoload :Types, 'stannum/constraints/types'
26
27
  autoload :Union, 'stannum/constraints/union'
28
+ autoload :Uuid, 'stannum/constraints/uuid'
27
29
  end
28
30
  end