contracted_value 0.1.3 → 0.2.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.
- checksums.yaml +4 -4
- data/.github/workflows/coverage.yml +3 -2
- data/.github/workflows/tests.yaml +4 -2
- data/CHANGELOG.md +15 -6
- data/README.md +33 -0
- data/Rakefile +1 -1
- data/contracted_value.gemspec +5 -1
- data/lib/contracted_value/core.rb +44 -3
- data/lib/contracted_value/version.rb +1 -1
- data/spec/contracted_value/value_spec.rb +94 -2
- metadata +7 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 26c83184ecb824d29bc3d258555f6f915d0478e7e1d5e8dde8bf8a21935b69a4
|
|
4
|
+
data.tar.gz: 73caf82b999e6e65cd158316982f5051a19f219eabe85e96434c98be72d973ca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b712d12af9079d88a5d44c56aaad07fa8e29ae56bed4f37f6fdcd5f36ab2c1e392e1afc56b1e84e98ef9d621a16843ca407dc31002345761c74e16e2301654cc
|
|
7
|
+
data.tar.gz: eece1cec55a0e03b15da9c7c97d4b8dc5aa55f3e7138ecc5448a2f0b36bcc73da5f6e0b245f4c1d03819342cdee0c39de2c8516afeae47d671bb8db841d8af80
|
|
@@ -26,7 +26,8 @@ jobs:
|
|
|
26
26
|
os:
|
|
27
27
|
- ubuntu-latest
|
|
28
28
|
ruby:
|
|
29
|
-
- "3.
|
|
29
|
+
- "3.4"
|
|
30
|
+
- "4.0"
|
|
30
31
|
gemfile:
|
|
31
32
|
- gemfiles/contracts_17_0.gemfile
|
|
32
33
|
env:
|
|
@@ -35,7 +36,7 @@ jobs:
|
|
|
35
36
|
runs-on: ${{ matrix.os }}
|
|
36
37
|
steps:
|
|
37
38
|
- name: Checkout
|
|
38
|
-
uses: actions/checkout@
|
|
39
|
+
uses: actions/checkout@v6
|
|
39
40
|
|
|
40
41
|
- name: Setup Ruby
|
|
41
42
|
uses: ruby/setup-ruby@v1
|
|
@@ -26,9 +26,11 @@ jobs:
|
|
|
26
26
|
os:
|
|
27
27
|
- ubuntu-latest
|
|
28
28
|
ruby:
|
|
29
|
-
- "3.0"
|
|
30
29
|
- "3.1"
|
|
31
30
|
- "3.2"
|
|
31
|
+
- "3.3"
|
|
32
|
+
- "3.4"
|
|
33
|
+
- "4.0"
|
|
32
34
|
gemfile:
|
|
33
35
|
- gemfiles/contracts_17_0.gemfile
|
|
34
36
|
allow_failures:
|
|
@@ -45,7 +47,7 @@ jobs:
|
|
|
45
47
|
continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }}
|
|
46
48
|
steps:
|
|
47
49
|
- name: Checkout
|
|
48
|
-
uses: actions/checkout@
|
|
50
|
+
uses: actions/checkout@v6
|
|
49
51
|
- name: Setup Ruby
|
|
50
52
|
uses: ruby/setup-ruby@v1
|
|
51
53
|
with:
|
data/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,8 @@ All notable changes to this project will be documented in this file.
|
|
|
3
3
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
## [Unreleased]
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
[Unreleased]: https://github.com/PikachuEXE/contracted_value/compare/v0.2.0...master
|
|
7
8
|
|
|
8
9
|
### Added
|
|
9
10
|
|
|
@@ -18,7 +19,17 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
18
19
|
- Nothing
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
## [0.2.0] - 2026-04-29
|
|
23
|
+
[0.2.0]: https://github.com/PikachuEXE/contracted_value/releases/tag/v0.2.0
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Add feature detect_unexpected_keys
|
|
28
|
+
(https://github.com/PikachuEXE/contracted_value/pull/9)
|
|
29
|
+
|
|
30
|
+
|
|
21
31
|
## [0.1.3] - 2023-10-11
|
|
32
|
+
[0.1.3]: https://github.com/PikachuEXE/contracted_value/releases/tag/v0.1.3
|
|
22
33
|
|
|
23
34
|
### Fixed
|
|
24
35
|
|
|
@@ -27,6 +38,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
27
38
|
|
|
28
39
|
|
|
29
40
|
## [0.1.2] - 2023-10-11
|
|
41
|
+
[0.1.2]: https://github.com/PikachuEXE/contracted_value/releases/tag/v0.1.2
|
|
30
42
|
|
|
31
43
|
### Changed
|
|
32
44
|
|
|
@@ -36,6 +48,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
36
48
|
|
|
37
49
|
|
|
38
50
|
## [0.1.1] - 2022-09-07
|
|
51
|
+
[0.1.1]: https://github.com/PikachuEXE/contracted_value/releases/tag/v0.1.1
|
|
39
52
|
|
|
40
53
|
### Changed
|
|
41
54
|
|
|
@@ -47,12 +60,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
47
60
|
|
|
48
61
|
|
|
49
62
|
## [0.1.0] - 2019-06-05
|
|
63
|
+
[0.1.0]: https://github.com/PikachuEXE/contracted_value/releases/tag/v0.1.0
|
|
50
64
|
|
|
51
65
|
### Added
|
|
52
66
|
|
|
53
67
|
- Initial release
|
|
54
|
-
|
|
55
|
-
[0.1.3]: https://github.com/PikachuEXE/contracted_value/releases/tag/v0.1.3
|
|
56
|
-
[0.1.2]: https://github.com/PikachuEXE/contracted_value/releases/tag/v0.1.2
|
|
57
|
-
[0.1.1]: https://github.com/PikachuEXE/contracted_value/releases/tag/v0.1.1
|
|
58
|
-
[0.1.0]: https://github.com/PikachuEXE/contracted_value/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -247,6 +247,35 @@ WhatIsThis::Entry.new(
|
|
|
247
247
|
).something_optional # => nil
|
|
248
248
|
```
|
|
249
249
|
|
|
250
|
+
#### Unexpected keys
|
|
251
|
+
Call `detect_unexpected_keys` to detect unexpected keys by raising error
|
|
252
|
+
It's off by default
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
module ::WhatIsThis
|
|
257
|
+
class Entry < ::ContractedValue::Value
|
|
258
|
+
include ::Contracts::Core
|
|
259
|
+
include ::Contracts::Builtin
|
|
260
|
+
|
|
261
|
+
detect_unexpected_keys
|
|
262
|
+
|
|
263
|
+
attribute(
|
|
264
|
+
:something_required,
|
|
265
|
+
)
|
|
266
|
+
attribute(
|
|
267
|
+
:something_optional,
|
|
268
|
+
default_value: nil,
|
|
269
|
+
)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
WhatIsThis::Entry.new(
|
|
274
|
+
something_required: 123,
|
|
275
|
+
something_unexpected: "whatever",
|
|
276
|
+
) # => error
|
|
277
|
+
```
|
|
278
|
+
|
|
250
279
|
|
|
251
280
|
### Object Freezing
|
|
252
281
|
All input values are frozen using [`ice_nine`](https://github.com/dkubb/ice_nine) by default
|
|
@@ -353,6 +382,10 @@ Pikachu.new.name.frozen? # => true, as mentioned above default value are always
|
|
|
353
382
|
Pikachu.new(name: "Pikaaaachuuu").name.frozen? # => false
|
|
354
383
|
```
|
|
355
384
|
|
|
385
|
+
#### `detect_unexpected_keys` inherited
|
|
386
|
+
If parent class already called `detect_unexpected_keys`, all children classes would have it enabled too
|
|
387
|
+
Otherwise you can call `detect_unexpected_keys` in child class(es) without affecting parent class
|
|
388
|
+
|
|
356
389
|
|
|
357
390
|
## Related gems
|
|
358
391
|
Here is a list of gems which I found and I have tried some of them.
|
data/Rakefile
CHANGED
data/contracted_value.gemspec
CHANGED
|
@@ -38,7 +38,7 @@ Gem::Specification.new do |s|
|
|
|
38
38
|
s.add_development_dependency "appraisal", "~> 2.0", ">= 2.5.0"
|
|
39
39
|
|
|
40
40
|
s.add_development_dependency "rspec", "~> 3.0"
|
|
41
|
-
s.add_development_dependency "rspec-its", "~>
|
|
41
|
+
s.add_development_dependency "rspec-its", "~> 2.0"
|
|
42
42
|
|
|
43
43
|
s.add_development_dependency "simplecov", ">= 0.21"
|
|
44
44
|
s.add_development_dependency "simplecov-lcov", ">= 0.8"
|
|
@@ -52,4 +52,8 @@ Gem::Specification.new do |s|
|
|
|
52
52
|
s.required_ruby_version = ">= 3.0.0"
|
|
53
53
|
|
|
54
54
|
s.required_rubygems_version = ">= 1.4.0"
|
|
55
|
+
|
|
56
|
+
# requiring all owners to enable MFA on their account
|
|
57
|
+
# https://guides.rubygems.org/mfa-requirement-opt-in/
|
|
58
|
+
s.metadata["rubygems_mfa_required"] = "true"
|
|
55
59
|
end
|
|
@@ -79,6 +79,16 @@ module ContractedValue
|
|
|
79
79
|
)
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
|
+
|
|
83
|
+
class UnexpectedInputKeys < ArgumentError
|
|
84
|
+
def initialize(keys)
|
|
85
|
+
super(
|
|
86
|
+
<<~MSG
|
|
87
|
+
Unexpected key(s) #{keys.map{|key| ":#{key}" }.join(", ")} detected in input
|
|
88
|
+
MSG
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
82
92
|
end
|
|
83
93
|
|
|
84
94
|
module Private
|
|
@@ -112,6 +122,10 @@ module ContractedValue
|
|
|
112
122
|
end
|
|
113
123
|
end
|
|
114
124
|
|
|
125
|
+
def detect_unexpected_keys(hash)
|
|
126
|
+
hash.keys - attr_names
|
|
127
|
+
end
|
|
128
|
+
|
|
115
129
|
protected
|
|
116
130
|
|
|
117
131
|
def merge!(other_attr_set)
|
|
@@ -238,7 +252,8 @@ module ContractedValue
|
|
|
238
252
|
)
|
|
239
253
|
end
|
|
240
254
|
|
|
241
|
-
self.class.send(:attribute_set)
|
|
255
|
+
attribute_set = self.class.send(:attribute_set)
|
|
256
|
+
attribute_set.each_attribute do |attribute|
|
|
242
257
|
attr_value = attribute.extract_value(input_attr_values_hash)
|
|
243
258
|
|
|
244
259
|
sometimes_frozen_attr_value =
|
|
@@ -266,6 +281,15 @@ module ContractedValue
|
|
|
266
281
|
)
|
|
267
282
|
end
|
|
268
283
|
|
|
284
|
+
if self.class.send(:should_detect_unexpected_keys)
|
|
285
|
+
unexpected_keys = attribute_set.detect_unexpected_keys(input_attr_values_hash)
|
|
286
|
+
if unexpected_keys.any?
|
|
287
|
+
raise Errors::UnexpectedInputKeys.new(
|
|
288
|
+
unexpected_keys,
|
|
289
|
+
)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
269
293
|
freeze
|
|
270
294
|
end
|
|
271
295
|
# rubocop:enable Metrics/AbcSize
|
|
@@ -280,10 +304,19 @@ module ContractedValue
|
|
|
280
304
|
|
|
281
305
|
# == Class interface == #
|
|
282
306
|
class << self
|
|
283
|
-
def inherited(
|
|
307
|
+
def inherited(child_klass)
|
|
284
308
|
super
|
|
285
309
|
|
|
286
|
-
|
|
310
|
+
child_klass.instance_variable_set(:@attribute_set, AttributeSet.new)
|
|
311
|
+
child_klass.instance_variable_set(
|
|
312
|
+
:@should_detect_unexpected_keys,
|
|
313
|
+
if child_klass.superclass.respond_to?(:should_detect_unexpected_keys, true)
|
|
314
|
+
child_klass.superclass.send(:should_detect_unexpected_keys)
|
|
315
|
+
else
|
|
316
|
+
false
|
|
317
|
+
end
|
|
318
|
+
)
|
|
319
|
+
|
|
287
320
|
end
|
|
288
321
|
|
|
289
322
|
private
|
|
@@ -326,6 +359,14 @@ module ContractedValue
|
|
|
326
359
|
# @attribute_set would be nil
|
|
327
360
|
super_attribute_set.merge(@attribute_set || AttributeSet.new)
|
|
328
361
|
end
|
|
362
|
+
|
|
363
|
+
# @api
|
|
364
|
+
def detect_unexpected_keys
|
|
365
|
+
@should_detect_unexpected_keys = true
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# @api private
|
|
369
|
+
attr_reader :should_detect_unexpected_keys
|
|
329
370
|
end
|
|
330
371
|
# == Class interface == #
|
|
331
372
|
end
|
|
@@ -48,12 +48,13 @@ require "spec_helper"
|
|
|
48
48
|
}.to_not raise_error
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
example "does
|
|
51
|
+
example "does raise error when declaring 1 attribute with number name" do
|
|
52
52
|
expect {
|
|
53
53
|
value_class.class_eval do
|
|
54
54
|
attribute(1)
|
|
55
55
|
end
|
|
56
|
-
}.to raise_error(::NoMethodError, /undefined method `to_sym'/)
|
|
56
|
+
}.to raise_error(::NoMethodError, /undefined method `to_sym'|undefined method 'to_sym'/)
|
|
57
|
+
# ruby 3.4+ uses different quotes
|
|
57
58
|
end
|
|
58
59
|
end
|
|
59
60
|
|
|
@@ -83,6 +84,12 @@ require "spec_helper"
|
|
|
83
84
|
}
|
|
84
85
|
end
|
|
85
86
|
|
|
87
|
+
let(:inputs_with_extra) do
|
|
88
|
+
default_inputs.merge(
|
|
89
|
+
attribute_3: 1,
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
86
93
|
let(:non_hash) do
|
|
87
94
|
[]
|
|
88
95
|
end
|
|
@@ -156,6 +163,30 @@ require "spec_helper"
|
|
|
156
163
|
end
|
|
157
164
|
end
|
|
158
165
|
end
|
|
166
|
+
|
|
167
|
+
it "does not raise error when extra attribute is input" do
|
|
168
|
+
aggregate_failures do
|
|
169
|
+
expect {
|
|
170
|
+
value_class.new(
|
|
171
|
+
inputs_with_extra,
|
|
172
|
+
)
|
|
173
|
+
}.to_not raise_error
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it "does not raise error when extra attribute is input" do
|
|
178
|
+
aggregate_failures do
|
|
179
|
+
value_class.class_eval do
|
|
180
|
+
detect_unexpected_keys
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
expect {
|
|
184
|
+
value_class.new(
|
|
185
|
+
inputs_with_extra,
|
|
186
|
+
)
|
|
187
|
+
}.to raise_error(::ContractedValue::Errors::UnexpectedInputKeys)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
159
190
|
end
|
|
160
191
|
|
|
161
192
|
context "with class with some attributes declared with contract" do
|
|
@@ -621,6 +652,7 @@ require "spec_helper"
|
|
|
621
652
|
end
|
|
622
653
|
child_value_class.new(attribute_1: "")
|
|
623
654
|
}.to raise_error(::ContractedValue::Errors::InvalidAttributeValue)
|
|
655
|
+
# Note that the error above is for the input not the attribute declaration
|
|
624
656
|
end
|
|
625
657
|
|
|
626
658
|
example "does not raise error when declaring existing attribute with different default_value" do
|
|
@@ -651,6 +683,66 @@ require "spec_helper"
|
|
|
651
683
|
|
|
652
684
|
end
|
|
653
685
|
|
|
686
|
+
describe "for detect_unexpected_keys" do
|
|
687
|
+
let(:child_value_class) do
|
|
688
|
+
Class.new(parent_value_class)
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
context "when detect_unexpected_keys declared inside parent class" do
|
|
692
|
+
let(:parent_value_class) do
|
|
693
|
+
Class.new(described_class).tap do |klass|
|
|
694
|
+
klass.class_eval do
|
|
695
|
+
detect_unexpected_keys
|
|
696
|
+
|
|
697
|
+
# Too lazy to include parent attributes in all examples
|
|
698
|
+
attribute(:attribute_1)
|
|
699
|
+
end
|
|
700
|
+
end
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
it "does raise error on parent class object" do
|
|
704
|
+
expect {
|
|
705
|
+
parent_value_class.new(attribute_1: 1, attribute_2: 2)
|
|
706
|
+
}.to raise_error(::ContractedValue::Errors::UnexpectedInputKeys)
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
it "does raise error on child class object" do
|
|
710
|
+
expect {
|
|
711
|
+
child_value_class.new(attribute_1: 1, attribute_2: 2)
|
|
712
|
+
}.to raise_error(::ContractedValue::Errors::UnexpectedInputKeys)
|
|
713
|
+
end
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
context "when detect_unexpected_keys declared inside child class" do
|
|
717
|
+
let(:parent_value_class) do
|
|
718
|
+
Class.new(described_class).tap do |klass|
|
|
719
|
+
klass.class_eval do
|
|
720
|
+
# Too lazy to include parent attributes in all examples
|
|
721
|
+
attribute(:attribute_1)
|
|
722
|
+
end
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
before do
|
|
727
|
+
child_value_class.class_eval do
|
|
728
|
+
detect_unexpected_keys
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
it "does not raise error on parent class object" do
|
|
733
|
+
expect {
|
|
734
|
+
parent_value_class.new(attribute_1: 1, attribute_2: 2)
|
|
735
|
+
}.to_not raise_error
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
it "does raise error on child class object" do
|
|
739
|
+
expect {
|
|
740
|
+
child_value_class.new(attribute_1: 1, attribute_2: 2)
|
|
741
|
+
}.to raise_error(::ContractedValue::Errors::UnexpectedInputKeys)
|
|
742
|
+
end
|
|
743
|
+
end
|
|
744
|
+
end
|
|
745
|
+
|
|
654
746
|
end
|
|
655
747
|
|
|
656
748
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: contracted_value
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- PikachuEXE
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: contracts
|
|
@@ -126,14 +125,14 @@ dependencies:
|
|
|
126
125
|
requirements:
|
|
127
126
|
- - "~>"
|
|
128
127
|
- !ruby/object:Gem::Version
|
|
129
|
-
version: '
|
|
128
|
+
version: '2.0'
|
|
130
129
|
type: :development
|
|
131
130
|
prerelease: false
|
|
132
131
|
version_requirements: !ruby/object:Gem::Requirement
|
|
133
132
|
requirements:
|
|
134
133
|
- - "~>"
|
|
135
134
|
- !ruby/object:Gem::Version
|
|
136
|
-
version: '
|
|
135
|
+
version: '2.0'
|
|
137
136
|
- !ruby/object:Gem::Dependency
|
|
138
137
|
name: simplecov
|
|
139
138
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -238,8 +237,8 @@ files:
|
|
|
238
237
|
homepage: http://github.com/PikachuEXE/contracted_value
|
|
239
238
|
licenses:
|
|
240
239
|
- MIT
|
|
241
|
-
metadata:
|
|
242
|
-
|
|
240
|
+
metadata:
|
|
241
|
+
rubygems_mfa_required: 'true'
|
|
243
242
|
rdoc_options: []
|
|
244
243
|
require_paths:
|
|
245
244
|
- lib
|
|
@@ -254,8 +253,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
254
253
|
- !ruby/object:Gem::Version
|
|
255
254
|
version: 1.4.0
|
|
256
255
|
requirements: []
|
|
257
|
-
rubygems_version:
|
|
258
|
-
signing_key:
|
|
256
|
+
rubygems_version: 4.0.9
|
|
259
257
|
specification_version: 4
|
|
260
258
|
summary: Contracted immutable(by default) value objects
|
|
261
259
|
test_files:
|