stannum 0.2.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -0
  3. data/README.md +130 -1200
  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 +13 -13
  15. data/lib/stannum/constraints/hashes/indifferent_extra_keys.rb +47 -0
  16. data/lib/stannum/constraints/hashes.rb +6 -2
  17. data/lib/stannum/constraints/identity.rb +1 -1
  18. data/lib/stannum/constraints/properties/base.rb +124 -0
  19. data/lib/stannum/constraints/properties/do_not_match_property.rb +117 -0
  20. data/lib/stannum/constraints/properties/match_property.rb +117 -0
  21. data/lib/stannum/constraints/properties/matching.rb +112 -0
  22. data/lib/stannum/constraints/properties.rb +17 -0
  23. data/lib/stannum/constraints/signature.rb +2 -2
  24. data/lib/stannum/constraints/tuples/extra_items.rb +6 -6
  25. data/lib/stannum/constraints/type.rb +4 -4
  26. data/lib/stannum/constraints/types/array_type.rb +2 -2
  27. data/lib/stannum/constraints/types/hash_type.rb +4 -4
  28. data/lib/stannum/constraints/union.rb +1 -1
  29. data/lib/stannum/constraints/uuid.rb +30 -0
  30. data/lib/stannum/constraints.rb +3 -0
  31. data/lib/stannum/contract.rb +7 -7
  32. data/lib/stannum/contracts/array_contract.rb +2 -7
  33. data/lib/stannum/contracts/base.rb +15 -15
  34. data/lib/stannum/contracts/builder.rb +15 -4
  35. data/lib/stannum/contracts/hash_contract.rb +3 -9
  36. data/lib/stannum/contracts/indifferent_hash_contract.rb +15 -2
  37. data/lib/stannum/contracts/map_contract.rb +6 -10
  38. data/lib/stannum/contracts/parameters/arguments_contract.rb +1 -1
  39. data/lib/stannum/contracts/parameters/keywords_contract.rb +1 -1
  40. data/lib/stannum/contracts/parameters/signature_contract.rb +1 -1
  41. data/lib/stannum/contracts/parameters_contract.rb +4 -4
  42. data/lib/stannum/contracts/tuple_contract.rb +6 -6
  43. data/lib/stannum/entities/associations.rb +451 -0
  44. data/lib/stannum/entities/attributes.rb +316 -0
  45. data/lib/stannum/entities/constraints.rb +178 -0
  46. data/lib/stannum/entities/primary_key.rb +148 -0
  47. data/lib/stannum/entities/properties.rb +208 -0
  48. data/lib/stannum/entities.rb +16 -0
  49. data/lib/stannum/entity.rb +87 -0
  50. data/lib/stannum/errors.rb +12 -16
  51. data/lib/stannum/messages/default_strategy.rb +2 -2
  52. data/lib/stannum/parameter_validation.rb +10 -10
  53. data/lib/stannum/rspec/match_errors_matcher.rb +7 -7
  54. data/lib/stannum/rspec/validate_parameter.rb +2 -2
  55. data/lib/stannum/rspec/validate_parameter_matcher.rb +22 -20
  56. data/lib/stannum/schema.rb +117 -76
  57. data/lib/stannum/struct.rb +12 -346
  58. data/lib/stannum/support/optional.rb +1 -1
  59. data/lib/stannum/version.rb +4 -4
  60. data/lib/stannum.rb +6 -0
  61. metadata +26 -85
@@ -0,0 +1,316 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stannum/entities'
4
+
5
+ module Stannum::Entities
6
+ # Methods for defining and accessing entity attributes.
7
+ module Attributes # rubocop:disable Metrics/ModuleLength
8
+ # Class methods to extend the class when including Attributes.
9
+ module ClassMethods
10
+ # Defines an attribute on the entity.
11
+ #
12
+ # When an attribute is defined, each of the following steps is executed:
13
+ #
14
+ # - Adds the attribute to ::Attributes and the .attributes class method.
15
+ # - Adds the attribute to #attributes and the associated methods, such as
16
+ # #assign_attributes, #[] and #[]=.
17
+ # - Defines reader and writer methods.
18
+ #
19
+ # @param attr_name [String, Symbol] The name of the attribute. Must be a
20
+ # non-empty String or Symbol.
21
+ # @param attr_type [Class, String] The type of the attribute. Must be a
22
+ # Class or Module, or the name of a class or module.
23
+ # @param options [Hash] Additional options for the attribute.
24
+ #
25
+ # @option options [Object] :default The default value for the attribute.
26
+ # Defaults to nil.
27
+ # @option options [Boolean] :primary_key true if the attribute represents
28
+ # the primary key for the entity; otherwise false. Defaults to false.
29
+ #
30
+ # @return [Symbol] the attribute name as a symbol.
31
+ def attribute(attr_name, attr_type, **options)
32
+ attributes.define(
33
+ name: attr_name,
34
+ type: attr_type,
35
+ options:
36
+ )
37
+
38
+ attr_name.intern
39
+ end
40
+ alias define_attribute attribute
41
+
42
+ # @return [Stannum::Schema] The attributes Schema object for the Entity.
43
+ def attributes
44
+ self::Attributes
45
+ end
46
+
47
+ private
48
+
49
+ def included(other)
50
+ super
51
+
52
+ other.include(Stannum::Entities::Attributes)
53
+
54
+ Stannum::Entities::Attributes.apply(other) if other.is_a?(Class)
55
+ end
56
+
57
+ def inherited(other)
58
+ super
59
+
60
+ Stannum::Entities::Attributes.apply(other)
61
+ end
62
+ end
63
+
64
+ class << self
65
+ # Generates Attributes schema for the class.
66
+ #
67
+ # Creates a new Stannum::Schema and sets it as the class's :Attributes
68
+ # constant. If the superclass is an entity class (and already defines its
69
+ # own Attributes, includes the superclass Attributes in the class
70
+ # Attributes). Finally, includes the class Attributes in the class.
71
+ #
72
+ # @param other [Class] the class to which attributes are added.
73
+ def apply(other)
74
+ return unless other.is_a?(Class)
75
+
76
+ return if entity_class?(other)
77
+
78
+ other.const_set(:Attributes, build_schema)
79
+
80
+ if entity_class?(other.superclass)
81
+ other::Attributes.include(other.superclass::Attributes)
82
+ end
83
+
84
+ other.include(other::Attributes)
85
+ end
86
+
87
+ private
88
+
89
+ def build_schema
90
+ Stannum::Schema.new(
91
+ property_class: Stannum::Attribute,
92
+ property_name: 'attributes'
93
+ )
94
+ end
95
+
96
+ def entity_class?(other)
97
+ other.const_defined?(:Attributes, false)
98
+ end
99
+
100
+ def included(other)
101
+ super
102
+
103
+ other.extend(self::ClassMethods)
104
+
105
+ apply(other) if other.is_a?(Class)
106
+ end
107
+ end
108
+
109
+ # @param properties [Hash] the properties used to initialize the entity.
110
+ def initialize(**properties)
111
+ @attributes = {}
112
+
113
+ self.class.attributes.each_key { |key| @attributes[key] = nil }
114
+
115
+ super
116
+ end
117
+
118
+ # Updates the struct's attributes with the given values.
119
+ #
120
+ # This method is used to update some (but not all) of the attributes of the
121
+ # struct. For each key in the hash, it calls the corresponding writer method
122
+ # with the value for that attribute. If the value is nil, this will set the
123
+ # attribute value to the default for that attribute.
124
+ #
125
+ # Any attributes that are not in the given hash are unchanged, as are any
126
+ # properties that are not attributes.
127
+ #
128
+ # If the attributes hash includes any keys that do not correspond to an
129
+ # attribute, the struct will raise an error.
130
+ #
131
+ # @param attributes [Hash] The attributes for the struct.
132
+ #
133
+ # @raise ArgumentError if any key is not a valid attribute.
134
+ #
135
+ # @see #attributes=
136
+ def assign_attributes(attributes)
137
+ unless attributes.is_a?(Hash)
138
+ raise ArgumentError, 'attributes must be a Hash'
139
+ end
140
+
141
+ set_attributes(attributes, force: false)
142
+ end
143
+
144
+ # Collects the entity attributes.
145
+ #
146
+ # @return [Hash<String, Object>] the entity attributes.
147
+ def attributes
148
+ @attributes.dup
149
+ end
150
+
151
+ # Replaces the entity's attributes with the given values.
152
+ #
153
+ # This method is used to update all of the attributes of the entity. For
154
+ # each attribute, the writer method is called with the value from the hash,
155
+ # or nil if the corresponding key is not present in the hash. Any nil or
156
+ # missing values set the attribute value to that attribute's default value,
157
+ # if any. Non-attribute properties are unchanged.
158
+ #
159
+ # If the attributes hash includes any keys that do not correspond to a valid
160
+ # attribute, the entity will raise an error.
161
+ #
162
+ # @param attributes [Hash] the attributes to assign to the entity.
163
+ #
164
+ # @raise ArgumentError if any key is not a valid attribute.
165
+ #
166
+ # @see #assign_attributes
167
+ def attributes=(attributes)
168
+ unless attributes.is_a?(Hash)
169
+ raise ArgumentError, 'attributes must be a Hash'
170
+ end
171
+
172
+ set_attributes(attributes, force: true)
173
+ end
174
+
175
+ # (see Stannum::Entities::Properties#properties)
176
+ def properties
177
+ super.merge(attributes)
178
+ end
179
+
180
+ # Retrieves the attribute value for the requested key.
181
+ #
182
+ # If the :safe flag is set, will verify that the attribute name is valid (a
183
+ # non-empty String or Symbol) and that there is a defined attribute by that
184
+ # name. By default, :safe is set to true.
185
+ #
186
+ # @param key [String, Symbol] the key of the attribute to retrieve.
187
+ # @param safe [Boolean] if true, validates the attribute key.
188
+ #
189
+ # @return [Object] the value of the requested attribute.
190
+ #
191
+ # @api private
192
+ def read_attribute(key, safe: true)
193
+ if safe
194
+ tools.assertions.validate_name(key, as: 'attribute')
195
+
196
+ unless self.class.attributes.key?(key.to_s)
197
+ raise ArgumentError, "unknown attribute #{key.inspect}"
198
+ end
199
+ end
200
+
201
+ @attributes[key.to_s]
202
+ end
203
+
204
+ # Assigns the attribute value for the requested key.
205
+ #
206
+ # If the :safe flag is set, will verify that the attribute name is valid (a
207
+ # non-empty String or Symbol) and that there is a defined attribute by that
208
+ # name. By default, :safe is set to true.
209
+ #
210
+ # @param key [String, Symbol] the key of the attribute to assign.
211
+ # @param value [Object] the value to assign.
212
+ # @param safe [Boolean] if true, validates the attribute key.
213
+ #
214
+ # @return [Object] the assigned value.
215
+ #
216
+ # @api private
217
+ def write_attribute(key, value, safe: true)
218
+ if safe
219
+ tools.assertions.validate_name(key, as: 'attribute')
220
+
221
+ unless self.class.attributes.key?(key.to_s)
222
+ raise ArgumentError, "unknown attribute #{key.inspect}"
223
+ end
224
+ end
225
+
226
+ @attributes[key.to_s] = value
227
+ end
228
+
229
+ private
230
+
231
+ def apply_defaults_for(attributes) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
232
+ with_value, with_proc = bisect_attributes_by_default_type
233
+
234
+ with_value.each do |attribute|
235
+ next unless @attributes[attribute.name].nil?
236
+ next unless attributes.key?(attribute.name)
237
+
238
+ send(attribute.writer_name, attribute.default)
239
+ end
240
+
241
+ with_proc.each do |attribute|
242
+ next unless @attributes[attribute.name].nil?
243
+ next unless attributes.key?(attribute.name)
244
+
245
+ send(attribute.writer_name, attribute.default_value_for(self))
246
+ end
247
+ end
248
+
249
+ def bisect_attributes_by_default_type
250
+ with_value = []
251
+ with_proc = []
252
+
253
+ self.class.attributes.each_value do |attribute|
254
+ next unless attribute.default?
255
+
256
+ (attribute.default.is_a?(Proc) ? with_proc : with_value) << attribute
257
+ end
258
+
259
+ [with_value, with_proc]
260
+ end
261
+
262
+ def get_property(key)
263
+ return @attributes[key.to_s] if attributes.key?(key.to_s)
264
+
265
+ super
266
+ end
267
+
268
+ def inspect_properties(**options)
269
+ return super unless options.fetch(:attributes, true)
270
+
271
+ @attributes.reduce(super) do |memo, (key, value)|
272
+ "#{memo} #{key}: #{value.inspect}"
273
+ end
274
+ end
275
+
276
+ def set_attributes(attributes, force:)
277
+ attributes, non_matching =
278
+ bisect_properties(attributes, self.class.attributes)
279
+
280
+ unless non_matching.empty?
281
+ handle_invalid_properties(non_matching, as: 'attribute')
282
+ end
283
+
284
+ write_attributes(attributes, force:)
285
+ end
286
+
287
+ def set_properties(properties, force:)
288
+ attributes, non_matching =
289
+ bisect_properties(properties, self.class.attributes)
290
+
291
+ super(non_matching, force:)
292
+
293
+ write_attributes(attributes, force:)
294
+ end
295
+
296
+ def set_property(key, value)
297
+ return super unless attributes.key?(key.to_s)
298
+
299
+ send(self.class.attributes[key.to_s].writer_name, value)
300
+ end
301
+
302
+ def write_attributes(attributes, force:)
303
+ self.class.attributes.each do |attr_name, attribute|
304
+ next unless attributes.key?(attr_name) || force
305
+
306
+ if attributes[attr_name].nil? && attribute.default?
307
+ @attributes[attr_name] = nil
308
+ else
309
+ send(attribute.writer_name, attributes[attr_name])
310
+ end
311
+ end
312
+
313
+ apply_defaults_for(force ? @attributes : attributes)
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,178 @@
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
+ alias define_attribute attribute
33
+ end
34
+
35
+ # Class methods to extend the class when including Constraints.
36
+ module ClassMethods
37
+ # Defines a constraint on the entity or one of its properties.
38
+ #
39
+ # @overload constraint()
40
+ # Defines a constraint on the entity.
41
+ #
42
+ # A new Stannum::Constraint instance will be generated, passing the
43
+ # block from .constraint to the new constraint. This constraint will be
44
+ # added to the contract.
45
+ #
46
+ # @yieldparam entity [Stannum::Entities::Constraints] The entity at the
47
+ # time the constraint is evaluated.
48
+ #
49
+ # @overload constraint(constraint)
50
+ # Defines a constraint on the entity.
51
+ #
52
+ # The given constraint is added to the contract. When the contract is
53
+ # evaluated, this constraint will be matched against the entity.
54
+ #
55
+ # @param constraint [Stannum::Constraints::Base] The constraint to add.
56
+ #
57
+ # @overload constraint(attr_name)
58
+ # Defines a constraint on the given attribute or property.
59
+ #
60
+ # A new Stannum::Constraint instance will be generated, passing the
61
+ # block from .constraint to the new constraint. This constraint will be
62
+ # added to the contract.
63
+ #
64
+ # @param attr_name [String, Symbol] The name of the attribute or
65
+ # property to constrain.
66
+ #
67
+ # @yieldparam value [Object] The value of the attribute or property of
68
+ # the entity at the time the constraint is evaluated.
69
+ #
70
+ # @overload constraint(attr_name, constraint)
71
+ # Defines a constraint on the given attribute or property.
72
+ #
73
+ # The given constraint is added to the contract. When the contract is
74
+ # evaluated, this constraint will be matched against the value of the
75
+ # attribute or property.
76
+ #
77
+ # @param attr_name [String, Symbol] The name of the attribute or
78
+ # property to constrain.
79
+ # @param constraint [Stannum::Constraints::Base] The constraint to add.
80
+ def constraint(attr_name = nil, constraint = nil, &)
81
+ attr_name, constraint = resolve_constraint(attr_name, constraint)
82
+
83
+ if block_given?
84
+ constraint = Stannum::Constraint.new(&)
85
+ else
86
+ validate_constraint(constraint)
87
+ end
88
+
89
+ contract.add_constraint(constraint, property: attr_name)
90
+ end
91
+
92
+ # @return [Stannum::Contract] The Contract object for the entity.
93
+ def contract
94
+ self::Contract
95
+ end
96
+
97
+ private
98
+
99
+ def included(other)
100
+ super
101
+
102
+ other.include(Stannum::Entities::Constraints)
103
+
104
+ Stannum::Entities::Constraints.apply(other) if other.is_a?(Class)
105
+ end
106
+
107
+ def inherited(other)
108
+ super
109
+
110
+ Stannum::Entities::Constraints.apply(other)
111
+ end
112
+
113
+ def resolve_constraint(attr_name, constraint)
114
+ return [nil, attr_name] if attr_name.is_a?(Stannum::Constraints::Base)
115
+
116
+ unless attr_name.nil?
117
+ tools.assertions.validate_name(attr_name, as: 'attribute')
118
+ end
119
+
120
+ [attr_name.nil? ? attr_name : attr_name.intern, constraint]
121
+ end
122
+
123
+ def tools
124
+ SleepingKingStudios::Tools::Toolbelt.instance
125
+ end
126
+
127
+ def validate_constraint(constraint)
128
+ raise ArgumentError, "constraint can't be blank" if constraint.nil?
129
+
130
+ return if constraint.is_a?(Stannum::Constraints::Base)
131
+
132
+ raise ArgumentError, 'constraint must be a Stannum::Constraints::Base'
133
+ end
134
+ end
135
+
136
+ class << self
137
+ # Generates a Contract for the class.
138
+ #
139
+ # Creates a new Stannum::Contract and sets it as the class's :Contract
140
+ # constant. If the superclass is an entity class (and already defines its
141
+ # own Contract, concatenates the superclass Contract into the class
142
+ # Contract).
143
+ #
144
+ # @param other [Class] the class to which attributes are added.
145
+ def apply(other)
146
+ return unless other.is_a?(Class)
147
+
148
+ return if entity_class?(other)
149
+
150
+ contract = Stannum::Contract.new
151
+
152
+ other.const_set(:Contract, contract)
153
+
154
+ return unless entity_class?(other.superclass)
155
+
156
+ contract.concat(other.superclass::Contract)
157
+ end
158
+
159
+ private
160
+
161
+ def entity_class?(other)
162
+ other.const_defined?(:Contract, false)
163
+ end
164
+
165
+ def included(other)
166
+ super
167
+
168
+ other.extend(self::ClassMethods)
169
+
170
+ if other < Stannum::Entities::Attributes
171
+ other.extend(self::AttributesMethods)
172
+ end
173
+
174
+ apply(other) if other.is_a?(Class)
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stannum/entities'
4
+
5
+ module Stannum::Entities
6
+ # Methods for defining and accessing an entity's primary key attribute.
7
+ module PrimaryKey
8
+ # Raised when adding a primary key to an entity that already has one.
9
+ class PrimaryKeyAlreadyExists < StandardError; end
10
+
11
+ # Raised when accessing a primary key for an entity that does not have one.
12
+ class PrimaryKeyMissing < StandardError; end
13
+
14
+ # Class methods to extend the class when including PrimaryKey.
15
+ module ClassMethods
16
+ # Defines a primary key attribute on the entity.
17
+ #
18
+ # @param attr_name [String, Symbol] The name of the attribute. Must be a
19
+ # non-empty String or Symbol.
20
+ # @param attr_type [Class, String] The type of the attribute. Must be a
21
+ # Class or Module, or the name of a class or module.
22
+ # @param options [Hash] Additional options for the attribute.
23
+ #
24
+ # @option options [Object] :default The default value for the attribute.
25
+ # Defaults to nil.
26
+ # @option options [Boolean] :primary_key true if the attribute represents
27
+ # the primary key for the entity; otherwise false. Defaults to false.
28
+ #
29
+ # @return [Symbol] The attribute name as a symbol.
30
+ #
31
+ # @see Stannum::Entities::Attributes::ClassMethods#define_attribute.
32
+ def define_primary_key(attr_name, attr_type, **options)
33
+ if primary_key?
34
+ raise PrimaryKeyAlreadyExists,
35
+ "#{name} already defines primary key #{primary_key_name.inspect}"
36
+ end
37
+
38
+ attribute(attr_name, attr_type, **options, primary_key: true)
39
+ end
40
+
41
+ # @return [Stannum::Attribute] the primary key attribute.
42
+ #
43
+ # @raise [Stannum::Entities::PrimaryKey::PrimaryKeyMissing] if the entity
44
+ # does not define a primary key.
45
+ def primary_key
46
+ primary_key =
47
+ attributes
48
+ .find { |_, attribute| attribute.primary_key? }
49
+ &.last
50
+
51
+ return primary_key if primary_key
52
+
53
+ raise PrimaryKeyMissing, "#{name} does not define a primary key"
54
+ end
55
+
56
+ # @return [Boolean] true if the entity class defines a primary key;
57
+ # otherwise false.
58
+ def primary_key?
59
+ attributes.any? { |_, attribute| attribute.primary_key? }
60
+ end
61
+
62
+ # @return [String, nil] the name of the primary key attribute, or nil if
63
+ # the entity does not define a primary key.
64
+ def primary_key_name
65
+ attributes
66
+ .find { |_, attribute| attribute.primary_key? }
67
+ &.last
68
+ &.name
69
+ end
70
+
71
+ # @return [Class, nil] the type of the primary key attribute, or nil if
72
+ # the entity does not define a primary key.
73
+ def primary_key_type
74
+ attributes
75
+ .find { |_, attribute| attribute.primary_key? }
76
+ &.last
77
+ &.resolved_type
78
+ end
79
+
80
+ private
81
+
82
+ def included(other)
83
+ super
84
+
85
+ other.include(Stannum::Entities::PrimaryKey)
86
+ end
87
+ end
88
+
89
+ class << self
90
+ private
91
+
92
+ def included(other)
93
+ super
94
+
95
+ other.extend(self::ClassMethods)
96
+ end
97
+ end
98
+
99
+ # @return [Boolean] true if the entity class defines a primary key and if
100
+ # the entity has a non-empty value for that attribute; otherwise false.
101
+ def primary_key?
102
+ return false unless self.class.primary_key?
103
+
104
+ value = attributes[self.class.primary_key_name]
105
+
106
+ return false if value.nil? || (value.respond_to?(:empty?) && value.empty?)
107
+
108
+ true
109
+ end
110
+
111
+ # @return [String] the name of the primary key attribute.
112
+ #
113
+ # @raise [Stannum::Entities::PrimaryKey::PrimaryKeyMissing] if the entity
114
+ # does not define a primary key.
115
+ def primary_key_name
116
+ unless self.class.primary_key?
117
+ raise PrimaryKeyMissing, "#{self.class} does not define a primary key"
118
+ end
119
+
120
+ self.class.primary_key_name
121
+ end
122
+
123
+ # @return [Class] the type of the primary key attribute.
124
+ #
125
+ # @raise [Stannum::Entities::PrimaryKey::PrimaryKeyMissing] if the entity
126
+ # does not define a primary key.
127
+ def primary_key_type
128
+ unless self.class.primary_key?
129
+ raise PrimaryKeyMissing, "#{self.class} does not define a primary key"
130
+ end
131
+
132
+ self.class.primary_key_type
133
+ end
134
+
135
+ # @return [Object] the current value of the primary key attribute.
136
+ #
137
+ # @raise [Stannum::Entities::PrimaryKey::PrimaryKeyMissing] if the entity
138
+ # does not define a primary key.
139
+ def primary_key_value
140
+ unless self.class.primary_key?
141
+ raise PrimaryKeyMissing, "#{self.class} does not define a primary key"
142
+ end
143
+
144
+ attributes[self.class.primary_key_name]
145
+ end
146
+ alias primary_key primary_key_value
147
+ end
148
+ end