stannum 0.1.0 → 0.3.0

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 +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