stannum 0.1.0 → 0.3.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +85 -21
  4. data/config/locales/en.rb +17 -3
  5. data/lib/stannum/constraints/base.rb +11 -4
  6. data/lib/stannum/constraints/hashes/extra_keys.rb +10 -2
  7. data/lib/stannum/constraints/hashes/indifferent_extra_keys.rb +47 -0
  8. data/lib/stannum/constraints/hashes.rb +6 -2
  9. data/lib/stannum/constraints/parameters/extra_arguments.rb +23 -0
  10. data/lib/stannum/constraints/parameters/extra_keywords.rb +29 -0
  11. data/lib/stannum/constraints/parameters.rb +11 -0
  12. data/lib/stannum/constraints/properties/base.rb +124 -0
  13. data/lib/stannum/constraints/properties/do_not_match_property.rb +117 -0
  14. data/lib/stannum/constraints/properties/match_property.rb +117 -0
  15. data/lib/stannum/constraints/properties/matching.rb +112 -0
  16. data/lib/stannum/constraints/properties.rb +17 -0
  17. data/lib/stannum/constraints/tuples/extra_items.rb +1 -1
  18. data/lib/stannum/constraints/type.rb +1 -1
  19. data/lib/stannum/constraints/types/hash_type.rb +6 -2
  20. data/lib/stannum/constraints.rb +2 -0
  21. data/lib/stannum/contracts/builder.rb +13 -2
  22. data/lib/stannum/contracts/hash_contract.rb +14 -0
  23. data/lib/stannum/contracts/indifferent_hash_contract.rb +13 -0
  24. data/lib/stannum/contracts/parameters/arguments_contract.rb +2 -7
  25. data/lib/stannum/contracts/parameters/keywords_contract.rb +2 -7
  26. data/lib/stannum/contracts/tuple_contract.rb +1 -1
  27. data/lib/stannum/entities/attributes.rb +218 -0
  28. data/lib/stannum/entities/constraints.rb +177 -0
  29. data/lib/stannum/entities/properties.rb +186 -0
  30. data/lib/stannum/entities.rb +13 -0
  31. data/lib/stannum/entity.rb +83 -0
  32. data/lib/stannum/errors.rb +3 -3
  33. data/lib/stannum/messages/default_loader.rb +95 -0
  34. data/lib/stannum/messages/default_strategy.rb +31 -50
  35. data/lib/stannum/messages.rb +1 -0
  36. data/lib/stannum/rspec/match_errors_matcher.rb +6 -6
  37. data/lib/stannum/rspec/validate_parameter_matcher.rb +10 -9
  38. data/lib/stannum/schema.rb +78 -37
  39. data/lib/stannum/struct.rb +12 -346
  40. data/lib/stannum/support/coercion.rb +19 -0
  41. data/lib/stannum/version.rb +1 -1
  42. data/lib/stannum.rb +3 -0
  43. metadata +29 -19
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stannum/entities'
4
+ require 'stannum/schema'
5
+
6
+ module Stannum::Entities
7
+ # Methods for defining and accessing entity attributes.
8
+ module Attributes
9
+ # Class methods to extend the class when including Attributes.
10
+ module ClassMethods
11
+ # Defines an attribute on the entity.
12
+ #
13
+ # When an attribute is defined, each of the following steps is executed:
14
+ #
15
+ # - Adds the attribute to ::Attributes and the .attributes class method.
16
+ # - Adds the attribute to #attributes and the associated methods, such as
17
+ # #assign_attributes, #[] and #[]=.
18
+ # - Defines reader and writer methods.
19
+ #
20
+ # @param attr_name [String, Symbol] The name of the attribute. Must be a
21
+ # non-empty String or Symbol.
22
+ # @param attr_type [Class, String] The type of the attribute. Must be a
23
+ # Class or Module, or the name of a class or module.
24
+ # @param options [Hash] Additional options for the attribute.
25
+ #
26
+ # @option options [Object] :default The default value for the attribute.
27
+ # Defaults to nil.
28
+ #
29
+ # @return [Symbol] The attribute name as a symbol.
30
+ def attribute(attr_name, attr_type, **options)
31
+ attributes.define_attribute(
32
+ name: attr_name,
33
+ type: attr_type,
34
+ options: options
35
+ )
36
+
37
+ attr_name.intern
38
+ end
39
+
40
+ # @return [Stannum::Schema] The attributes Schema object for the Entity.
41
+ def attributes
42
+ self::Attributes
43
+ end
44
+
45
+ private
46
+
47
+ def included(other)
48
+ super
49
+
50
+ other.include(Stannum::Entities::Attributes)
51
+
52
+ Stannum::Entities::Attributes.apply(other) if other.is_a?(Class)
53
+ end
54
+
55
+ def inherited(other)
56
+ super
57
+
58
+ Stannum::Entities::Attributes.apply(other)
59
+ end
60
+ end
61
+
62
+ class << self
63
+ # Generates Attributes schema for the class.
64
+ #
65
+ # Creates a new Stannum::Schema and sets it as the class's :Attributes
66
+ # constant. If the superclass is an entity class (and already defines its
67
+ # own Attributes, includes the superclass Attributes in the class
68
+ # Attributes). Finally, includes the class Attributes in the class.
69
+ #
70
+ # @param other [Class] the class to which attributes are added.
71
+ def apply(other)
72
+ return unless other.is_a?(Class)
73
+
74
+ return if entity_class?(other)
75
+
76
+ other.const_set(:Attributes, Stannum::Schema.new)
77
+
78
+ if entity_class?(other.superclass)
79
+ other::Attributes.include(other.superclass::Attributes)
80
+ end
81
+
82
+ other.include(other::Attributes)
83
+ end
84
+
85
+ private
86
+
87
+ def entity_class?(other)
88
+ other.const_defined?(:Attributes, false)
89
+ end
90
+
91
+ def included(other)
92
+ super
93
+
94
+ other.extend(self::ClassMethods)
95
+
96
+ apply(other) if other.is_a?(Class)
97
+ end
98
+ end
99
+
100
+ # @param properties [Hash] the properties used to initialize the entity.
101
+ def initialize(**properties)
102
+ @attributes = {}
103
+
104
+ super
105
+ end
106
+
107
+ # Updates the struct's attributes with the given values.
108
+ #
109
+ # This method is used to update some (but not all) of the attributes of the
110
+ # struct. For each key in the hash, it calls the corresponding writer method
111
+ # with the value for that attribute. If the value is nil, this will set the
112
+ # attribute value to the default for that attribute.
113
+ #
114
+ # Any attributes that are not in the given hash are unchanged, as are any
115
+ # properties that are not attributes.
116
+ #
117
+ # If the attributes hash includes any keys that do not correspond to an
118
+ # attribute, the struct will raise an error.
119
+ #
120
+ # @param attributes [Hash] The initial attributes for the struct.
121
+ #
122
+ # @raise ArgumentError if the key is not a valid attribute.
123
+ #
124
+ # @see #attributes=
125
+ def assign_attributes(attributes)
126
+ unless attributes.is_a?(Hash)
127
+ raise ArgumentError, 'attributes must be a Hash'
128
+ end
129
+
130
+ set_attributes(attributes, force: false)
131
+ end
132
+
133
+ # Collects the entity attributes.
134
+ #
135
+ # @param attributes [Hash<String, Object>] the entity attributes.
136
+ def attributes
137
+ @attributes.dup
138
+ end
139
+
140
+ # Replaces the entity's attributes with the given values.
141
+ #
142
+ # This method is used to update all of the attributes of the entity. For
143
+ # each attribute, the writer method is called with the value from the hash,
144
+ # or nil if the corresponding key is not present in the hash. Any nil or
145
+ # missing values set the attribute value to that attribute's default value,
146
+ # if any. Non-attribute properties are unchanged.
147
+ #
148
+ # If the attributes hash includes any keys that do not correspond to a valid
149
+ # attribute, the entity will raise an error.
150
+ #
151
+ # @param attributes [Hash] the attributes to assign to the entity.
152
+ #
153
+ # @raise ArgumentError if any key is not a valid attribute.
154
+ #
155
+ # @see #assign_attributes
156
+ def attributes=(attributes)
157
+ unless attributes.is_a?(Hash)
158
+ raise ArgumentError, 'attributes must be a Hash'
159
+ end
160
+
161
+ set_attributes(attributes, force: true)
162
+ end
163
+
164
+ # (see Stannum::Entities::Properties#properties)
165
+ def properties
166
+ super.merge(attributes)
167
+ end
168
+
169
+ private
170
+
171
+ def get_property(key)
172
+ return @attributes[key.to_s] if attributes.key?(key.to_s)
173
+
174
+ super
175
+ end
176
+
177
+ def inspectable_properties
178
+ super().merge(attributes)
179
+ end
180
+
181
+ def set_attributes(attributes, force:)
182
+ attributes, non_matching =
183
+ bisect_properties(attributes, self.class.attributes)
184
+
185
+ unless non_matching.empty?
186
+ handle_invalid_properties(non_matching, as: 'attribute')
187
+ end
188
+
189
+ write_attributes(attributes, force: force)
190
+ end
191
+
192
+ def set_properties(properties, force:)
193
+ attributes, non_matching =
194
+ bisect_properties(properties, self.class.attributes)
195
+
196
+ super(non_matching, force: force)
197
+
198
+ write_attributes(attributes, force: force)
199
+ end
200
+
201
+ def set_property(key, value)
202
+ return super unless attributes.key?(key.to_s)
203
+
204
+ send(self.class.attributes[key.to_s].writer_name, value)
205
+ end
206
+
207
+ def write_attributes(attributes, force:)
208
+ self.class.attributes.each do |attr_name, attribute|
209
+ next unless attributes.key?(attr_name) || force
210
+
211
+ send(
212
+ attribute.writer_name,
213
+ attributes[attr_name].nil? ? attribute.default : attributes[attr_name]
214
+ )
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stannum/entities'
4
+
5
+ module Stannum::Entities
6
+ # Methods for defining and accessing entity constraints.
7
+ module Constraints
8
+ # Class methods to extend the class when including Attributes.
9
+ module AttributesMethods
10
+ # Defines an attribute on the entity.
11
+ #
12
+ # Delegates to the superclass method, and then adds a type constraint to
13
+ # ::Contract.
14
+ #
15
+ # @see Stannum::Entities::Attributes::ClassMethods#attribute.
16
+ def attribute(attr_name, attr_type, **options) # rubocop:disable Metrics/MethodLength
17
+ returned = super
18
+
19
+ attribute = attributes[attr_name.to_s]
20
+ constraint = Stannum::Constraints::Type.new(
21
+ attribute.type,
22
+ required: attribute.required?
23
+ )
24
+
25
+ self::Contract.add_constraint(
26
+ constraint,
27
+ property: attribute.reader_name
28
+ )
29
+
30
+ returned
31
+ end
32
+ end
33
+
34
+ # Class methods to extend the class when including Constraints.
35
+ module ClassMethods
36
+ # Defines a constraint on the entity or one of its properties.
37
+ #
38
+ # @overload constraint()
39
+ # Defines a constraint on the entity.
40
+ #
41
+ # A new Stannum::Constraint instance will be generated, passing the
42
+ # block from .constraint to the new constraint. This constraint will be
43
+ # added to the contract.
44
+ #
45
+ # @yieldparam entity [Stannum::Entities::Constraints] The entity at the
46
+ # time the constraint is evaluated.
47
+ #
48
+ # @overload constraint(constraint)
49
+ # Defines a constraint on the entity.
50
+ #
51
+ # The given constraint is added to the contract. When the contract is
52
+ # evaluated, this constraint will be matched against the entity.
53
+ #
54
+ # @param constraint [Stannum::Constraints::Base] The constraint to add.
55
+ #
56
+ # @overload constraint(attr_name)
57
+ # Defines a constraint on the given attribute or property.
58
+ #
59
+ # A new Stannum::Constraint instance will be generated, passing the
60
+ # block from .constraint to the new constraint. This constraint will be
61
+ # added to the contract.
62
+ #
63
+ # @param attr_name [String, Symbol] The name of the attribute or
64
+ # property to constrain.
65
+ #
66
+ # @yieldparam value [Object] The value of the attribute or property of
67
+ # the entity at the time the constraint is evaluated.
68
+ #
69
+ # @overload constraint(attr_name, constraint)
70
+ # Defines a constraint on the given attribute or property.
71
+ #
72
+ # The given constraint is added to the contract. When the contract is
73
+ # evaluated, this constraint will be matched against the value of the
74
+ # attribute or property.
75
+ #
76
+ # @param attr_name [String, Symbol] The name of the attribute or
77
+ # property to constrain.
78
+ # @param constraint [Stannum::Constraints::Base] The constraint to add.
79
+ def constraint(attr_name = nil, constraint = nil, &block)
80
+ attr_name, constraint = resolve_constraint(attr_name, constraint)
81
+
82
+ if block_given?
83
+ constraint = Stannum::Constraint.new(&block)
84
+ else
85
+ validate_constraint(constraint)
86
+ end
87
+
88
+ contract.add_constraint(constraint, property: attr_name)
89
+ end
90
+
91
+ # @return [Stannum::Contract] The Contract object for the entity.
92
+ def contract
93
+ self::Contract
94
+ end
95
+
96
+ private
97
+
98
+ def included(other)
99
+ super
100
+
101
+ other.include(Stannum::Entities::Constraints)
102
+
103
+ Stannum::Entities::Constraints.apply(other) if other.is_a?(Class)
104
+ end
105
+
106
+ def inherited(other)
107
+ super
108
+
109
+ Stannum::Entities::Constraints.apply(other)
110
+ end
111
+
112
+ def resolve_constraint(attr_name, constraint)
113
+ return [nil, attr_name] if attr_name.is_a?(Stannum::Constraints::Base)
114
+
115
+ unless attr_name.nil?
116
+ tools.assertions.validate_name(attr_name, as: 'attribute')
117
+ end
118
+
119
+ [attr_name.nil? ? attr_name : attr_name.intern, constraint]
120
+ end
121
+
122
+ def tools
123
+ SleepingKingStudios::Tools::Toolbelt.instance
124
+ end
125
+
126
+ def validate_constraint(constraint)
127
+ raise ArgumentError, "constraint can't be blank" if constraint.nil?
128
+
129
+ return if constraint.is_a?(Stannum::Constraints::Base)
130
+
131
+ raise ArgumentError, 'constraint must be a Stannum::Constraints::Base'
132
+ end
133
+ end
134
+
135
+ class << self
136
+ # Generates a Contract for the class.
137
+ #
138
+ # Creates a new Stannum::Contract and sets it as the class's :Contract
139
+ # constant. If the superclass is an entity class (and already defines its
140
+ # own Contract, concatenates the superclass Contract into the class
141
+ # Contract).
142
+ #
143
+ # @param other [Class] the class to which attributes are added.
144
+ def apply(other)
145
+ return unless other.is_a?(Class)
146
+
147
+ return if entity_class?(other)
148
+
149
+ contract = Stannum::Contract.new
150
+
151
+ other.const_set(:Contract, contract)
152
+
153
+ return unless entity_class?(other.superclass)
154
+
155
+ contract.concat(other.superclass::Contract)
156
+ end
157
+
158
+ private
159
+
160
+ def entity_class?(other)
161
+ other.const_defined?(:Contract, false)
162
+ end
163
+
164
+ def included(other)
165
+ super
166
+
167
+ other.extend(self::ClassMethods)
168
+
169
+ if other < Stannum::Entities::Attributes
170
+ other.extend(self::AttributesMethods)
171
+ end
172
+
173
+ apply(other) if other.is_a?(Class)
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stannum/entities'
4
+
5
+ module Stannum::Entities
6
+ # Abstract module for handling heterogenous entity properties.
7
+ #
8
+ # This module provides a base for accessing and mutating entity properties
9
+ # such as attributes and associations.
10
+ module Properties
11
+ # @param properties [Hash] the properties used to initialize the entity.
12
+ def initialize(**properties)
13
+ set_properties(properties, force: true)
14
+ end
15
+
16
+ # Compares the entity with the other object.
17
+ #
18
+ # The other object must be an instance of the current class. In addition,
19
+ # the properties hashes of the two objects must be equal.
20
+ #
21
+ # @return true if the object is a matching entity.
22
+ def ==(other)
23
+ return false unless other.class == self.class
24
+
25
+ properties == other.properties
26
+ end
27
+
28
+ # Retrieves the property with the given key.
29
+ #
30
+ # @param property [String, Symbol] The property key.
31
+ #
32
+ # @return [Object] the value of the property.
33
+ #
34
+ # @raise ArgumentError if the key is not a valid property.
35
+ def [](property)
36
+ tools.assertions.validate_name(property, as: 'property')
37
+
38
+ get_property(property)
39
+ end
40
+
41
+ # Sets the given property to the given value.
42
+ #
43
+ # @param property [String, Symbol] The property key.
44
+ # @param value [Object] The value for the property.
45
+ #
46
+ # @raise ArgumentError if the key is not a valid property.
47
+ def []=(property, value)
48
+ tools.assertions.validate_name(property, as: 'property')
49
+
50
+ set_property(property, value)
51
+ end
52
+
53
+ # Updates the struct's properties with the given values.
54
+ #
55
+ # This method is used to update some (but not all) of the properties of the
56
+ # struct. For each key in the hash, it calls the corresponding writer method
57
+ # with the value for that property. If the value is nil, this will set the
58
+ # property value to the default for that property.
59
+ #
60
+ # Any properties that are not in the given hash are unchanged.
61
+ #
62
+ # If the properties hash includes any keys that do not correspond to an
63
+ # property, the struct will raise an error.
64
+ #
65
+ # @param properties [Hash] The initial properties for the struct.
66
+ #
67
+ # @raise ArgumentError if the key is not a valid property.
68
+ #
69
+ # @see #properties=
70
+ def assign_properties(properties)
71
+ unless properties.is_a?(Hash)
72
+ raise ArgumentError, 'properties must be a Hash'
73
+ end
74
+
75
+ set_properties(properties, force: false)
76
+ end
77
+ alias assign assign_properties
78
+
79
+ # @return [String] a string representation of the entity and its properties.
80
+ def inspect
81
+ mapped = inspectable_properties.reduce('') do |memo, (key, value)|
82
+ memo + " #{key}: #{value.inspect}"
83
+ end
84
+
85
+ "#<#{self.class.name}#{mapped}>"
86
+ end
87
+
88
+ # Collects the entity properties.
89
+ #
90
+ # @return [Hash<String, Object>] the entity properties.
91
+ def properties
92
+ {}
93
+ end
94
+
95
+ # Replaces the entity's properties with the given values.
96
+ #
97
+ # This method is used to update all of the properties of the entity. For
98
+ # each property, the writer method is called with the value from the hash,
99
+ # or nil if the corresponding key is not present in the hash. Any nil or
100
+ # missing values set the property value to that property's default value, if
101
+ # any.
102
+ #
103
+ # If the properties hash includes any keys that do not correspond to a valid
104
+ # property, the entity will raise an error.
105
+ #
106
+ # @param properties [Hash] the properties to assign to the entity.
107
+ #
108
+ # @raise ArgumentError if any key is not a valid property.
109
+ #
110
+ # @see #assign_properties
111
+ def properties=(properties)
112
+ unless properties.is_a?(Hash)
113
+ raise ArgumentError, 'properties must be a Hash'
114
+ end
115
+
116
+ set_properties(properties, force: true)
117
+ end
118
+
119
+ # Returns a Hash representation of the entity.
120
+ #
121
+ # @return [Hash<String, Object>] the entity properties.
122
+ #
123
+ # @see #properties
124
+ def to_h
125
+ properties
126
+ end
127
+
128
+ private
129
+
130
+ def bisect_properties(properties, expected)
131
+ matching = {}
132
+ non_matching = {}
133
+
134
+ properties.each do |key, value|
135
+ if valid_property_key?(key) && expected.key?(key.to_s)
136
+ matching[key.to_s] = value
137
+ else
138
+ non_matching[key] = value
139
+ end
140
+ end
141
+
142
+ [matching, non_matching]
143
+ end
144
+
145
+ def get_property(key)
146
+ raise ArgumentError, "unknown property #{key.inspect}"
147
+ end
148
+
149
+ def handle_invalid_properties(properties, as: 'property')
150
+ properties.each_key do |key|
151
+ tools.assertions.assert_name(key, as: as, error_class: ArgumentError)
152
+ end
153
+
154
+ raise ArgumentError, invalid_properties_message(properties, as: as)
155
+ end
156
+
157
+ def inspectable_properties
158
+ {}
159
+ end
160
+
161
+ def invalid_properties_message(properties, as: 'property')
162
+ "unknown #{tools.int.pluralize(properties.size, as)} " +
163
+ properties.keys.map(&:inspect).join(', ')
164
+ end
165
+
166
+ def set_property(key, _)
167
+ raise ArgumentError, "unknown property #{key.inspect}"
168
+ end
169
+
170
+ def set_properties(properties, **_)
171
+ return if properties.empty?
172
+
173
+ handle_invalid_properties(properties)
174
+ end
175
+
176
+ def tools
177
+ SleepingKingStudios::Tools::Toolbelt.instance
178
+ end
179
+
180
+ def valid_property_key?(key)
181
+ return false unless key.is_a?(String) || key.is_a?(Symbol)
182
+
183
+ !key.empty?
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sleeping_king_studios/tools/toolbox/mixin'
4
+
5
+ require 'stannum/entities'
6
+
7
+ module Stannum
8
+ # Namespace for modules implementing Entity functionality.
9
+ module Entities
10
+ autoload :Attributes, 'stannum/entities/attributes'
11
+ autoload :Properties, 'stannum/entities/properties'
12
+ end
13
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stannum'
4
+ require 'stannum/entities/attributes'
5
+ require 'stannum/entities/constraints'
6
+ require 'stannum/entities/properties'
7
+
8
+ module Stannum
9
+ # Abstract module for defining objects with structured attributes.
10
+ #
11
+ # @example Defining Attributes
12
+ # class Widget
13
+ # include Stannum::Entity
14
+ #
15
+ # attribute :name, String
16
+ # attribute :description, String, optional: true
17
+ # attribute :quantity, Integer, default: 0
18
+ # end
19
+ #
20
+ # widget = Widget.new(name: 'Self-sealing Stem Bolt')
21
+ # widget.name #=> 'Self-sealing Stem Bolt'
22
+ # widget.description #=> nil
23
+ # widget.quantity #=> 0
24
+ # widget.attributes #=>
25
+ # # {
26
+ # # name: 'Self-sealing Stem Bolt',
27
+ # # description: nil,
28
+ # # quantity: 0
29
+ # # }
30
+ #
31
+ # @example Setting Attributes
32
+ # widget.description = 'A stem bolt, but self sealing.'
33
+ # widget.attributes #=>
34
+ # # {
35
+ # # name: 'Self-sealing Stem Bolt',
36
+ # # description: 'A stem bolt, but self sealing.',
37
+ # # quantity: 0
38
+ # # }
39
+ #
40
+ # widget.assign_attributes(quantity: 50)
41
+ # widget.attributes #=>
42
+ # # {
43
+ # # name: 'Self-sealing Stem Bolt',
44
+ # # description: 'A stem bolt, but self sealing.',
45
+ # # quantity: 50
46
+ # # }
47
+ #
48
+ # widget.attributes = (name: 'Inverse Chronoton Emitter')
49
+ # # {
50
+ # # name: 'Inverse Chronoton Emitter',
51
+ # # description: nil,
52
+ # # quantity: 0
53
+ # # }
54
+ #
55
+ # @example Defining Attribute Constraints
56
+ # Widget::Contract.matches?(quantity: -5) #=> false
57
+ # Widget::Contract.matches?(name: 'Capacitor', quantity: -5) #=> true
58
+ #
59
+ # class Widget
60
+ # constraint(:quantity) { |qty| qty >= 0 }
61
+ # end
62
+ #
63
+ # Widget::Contract.matches?(name: 'Capacitor', quantity: -5) #=> false
64
+ # Widget::Contract.matches?(name: 'Capacitor', quantity: 10) #=> true
65
+ #
66
+ # @example Defining Struct Constraints
67
+ # Widget::Contract.matches?(name: 'Diode') #=> true
68
+ #
69
+ # class Widget
70
+ # constraint { |struct| struct.description&.include?(struct.name) }
71
+ # end
72
+ #
73
+ # Widget::Contract.matches?(name: 'Diode') #=> false
74
+ # Widget::Contract.matches?(
75
+ # name: 'Diode',
76
+ # description: 'A low budget Diode',
77
+ # ) #=> true
78
+ module Entity
79
+ include Stannum::Entities::Properties
80
+ include Stannum::Entities::Attributes
81
+ include Stannum::Entities::Constraints
82
+ end
83
+ end
@@ -492,7 +492,7 @@ module Stannum
492
492
 
493
493
  # @return [String] a human-readable representation of the object.
494
494
  def inspect
495
- oid = super[2...-1].split.first.split(':').last
495
+ oid = super[2...].split.first.split(':').last
496
496
 
497
497
  "#<#{self.class.name}:#{oid} @summary=%{#{summary}}>"
498
498
  end
@@ -644,7 +644,7 @@ module Stannum
644
644
 
645
645
  return path.first.to_s if path.size == 1
646
646
 
647
- path[1..-1].reduce(path.first.to_s) do |str, item|
647
+ path[1..].reduce(path.first.to_s) do |str, item|
648
648
  item.is_a?(Integer) ? "#{str}[#{item}]" : "#{str}.#{item}"
649
649
  end
650
650
  end
@@ -724,7 +724,7 @@ module Stannum
724
724
 
725
725
  raise ArgumentError,
726
726
  'key must be an Integer, a String or a Symbol',
727
- caller(1..-1)
727
+ caller(1..)
728
728
  end
729
729
  end
730
730
  end