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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 910e3c46d5bcd1d4f7bce77cc41b89468ee2a3e7fce57bd281a484eb8e114c48
4
- data.tar.gz: 488d7ca201c5e973ae5e183154ecb917561d756af14533caa4f06b2f2889c9ac
3
+ metadata.gz: 26c83184ecb824d29bc3d258555f6f915d0478e7e1d5e8dde8bf8a21935b69a4
4
+ data.tar.gz: 73caf82b999e6e65cd158316982f5051a19f219eabe85e96434c98be72d973ca
5
5
  SHA512:
6
- metadata.gz: 208de749d05602d4914bcd9257ff6d902c9bcb3882c1a44ee2f52aab6b5fa6a1889e5a98743a2b2a64f750aac1d15b373599616b82d810ad6798c53fa3e14c85
7
- data.tar.gz: d89f379d4f35f69ab269713852194cf04b276db8d25ef08cc602deadcb7148dc03b240711cc9709f2d56a6b9a799ee2253e93d16d5b44f3da9cef45a115b73aa
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.2"
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@v4
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@v4
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] - NOW
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
@@ -7,7 +7,7 @@ require "rspec/core/rake_task"
7
7
 
8
8
  RSpec::Core::RakeTask.new(:spec)
9
9
 
10
- if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
10
+ if !ENV["APPRAISAL_INITIALIZED"] && !ENV["CI"]
11
11
  task :default do
12
12
  sh "appraisal install && rake appraisal spec"
13
13
  end
@@ -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", "~> 1.0"
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).each_attribute do |attribute|
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(klass)
307
+ def inherited(child_klass)
284
308
  super
285
309
 
286
- klass.instance_variable_set(:@attribute_set, AttributeSet.new)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ContractedValue
4
- VERSION = "0.1.3"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -48,12 +48,13 @@ require "spec_helper"
48
48
  }.to_not raise_error
49
49
  end
50
50
 
51
- example "does not raise error when declaring 1 attribute with number name" do
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.1.3
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: 2023-10-11 00:00:00.000000000 Z
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: '1.0'
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: '1.0'
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
- post_install_message:
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: 3.4.20
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: