Skip to content

Commit 4791c62

Browse files
committed
Merge branch 'release/v2.0.3' into 'main'
✨ New features and bug fixes See merge request oauth-xx/snaky_hash!3
2 parents 0abfaff + caccdc0 commit 4791c62

36 files changed

+930
-284
lines changed

.github/workflows/ancient.yml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,26 +37,26 @@ jobs:
3737
# Ruby 2.3
3838
- ruby: "ruby-2.3"
3939
appraisal: "ruby-2-3"
40-
exec_cmd: "rspec spec/snaky_hash/serializer_spec.rb:13"
40+
exec_cmd: "rake test"
4141
gemfile: "Appraisal.root"
4242
rubygems: "3.3.27"
4343
bundler: "2.3.27"
4444

45-
# # Ruby 2.4
46-
# - ruby: "ruby-2.4"
47-
# appraisal: "ruby-2-4"
48-
# exec_cmd: "rake test"
49-
# gemfile: "Appraisal.root"
50-
# rubygems: "3.3.27"
51-
# bundler: "2.3.27"
52-
#
53-
# # Ruby 2.5
54-
# - ruby: "ruby-2.5"
55-
# appraisal: "ruby-2-5"
56-
# exec_cmd: "rake test"
57-
# gemfile: "Appraisal.root"
58-
# rubygems: "3.3.27"
59-
# bundler: "2.3.27"
45+
# Ruby 2.4
46+
- ruby: "ruby-2.4"
47+
appraisal: "ruby-2-4"
48+
exec_cmd: "rake test"
49+
gemfile: "Appraisal.root"
50+
rubygems: "3.3.27"
51+
bundler: "2.3.27"
52+
53+
# Ruby 2.5
54+
- ruby: "ruby-2.5"
55+
appraisal: "ruby-2-5"
56+
exec_cmd: "rake test"
57+
gemfile: "Appraisal.root"
58+
rubygems: "3.3.27"
59+
bundler: "2.3.27"
6060

6161
steps:
6262
- name: Checkout

.rubocop_gradual.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
"snaky_hash.gemspec:1781125773": [
66
[4, 23, 12, "Gemspec/RubyVersionGlobalsUsage: Do not use `RUBY_VERSION` in gemspec file.", 31296028]
77
],
8-
"spec/shared_contexts/base_hash.rb:829438978": [
9-
[3, 22, 11, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 3936427458]
8+
"spec/shared_contexts/base_hash.rb:2702242093": [
9+
[12, 22, 11, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 3936427458]
1010
],
1111
"spec/snaky_hash/bad_snake_spec.rb:3931746112": [
1212
[3, 16, 11, "RSpec/DescribeClass: The first argument to describe should be the class or module being tested.", 1577626599]

CHANGELOG.md

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,31 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2.
77
## [Unreleased]
88
### Added
99
### Changed
10-
### Fixed
10+
### Deprecated
1111
### Removed
12+
### Fixed
13+
### Security
14+
15+
## [2.0.3] - 2025-05-23
16+
- TAG: [v2.0.3][2.0.3t]
17+
- COVERAGE: 100.00% -- 132/132 lines in 7 files
18+
- BRANCH COVERAGE: 100.00% -- 38/38 branches in 7 files
19+
- 100.00% documented
20+
### Added
21+
- `#dump` instance method injected by `extend SnakyHash::Serializer` (@pboling)
22+
- `dump_hash_extensions` - new feature, analogous to `load_hash_extensions` (@pboling)
23+
- `dump_value_extensions` - alternate name for `dump_extensions` (@pboling)
24+
- `load_value_extensions` - alternate name for `load_extensions` (@pboling)
25+
- Clarifying documentation (@pboling)
26+
### Fixed
27+
- [gh4](https://github.com/oauth-xx/snaky_hash/pull/4) - Serializer extensions dump and load empty values properly (@pboling)
28+
- Fixed `dump_extensions`, `load_extensions`, `load_hash_extensions`
29+
- Intended usage is primarily JSON, and oauth2 gem
30+
- OAuth2 spec can have legitimately empty values (e.g. scopes could be empty)
31+
- Previous logic was inherited from design decisions made by `serialized_hashie` gem; doesn't apply here
1232

13-
## [2.0.2] - 2025-05-21 ([tag][2.0.2t])
33+
## [2.0.2] - 2025-05-21
34+
- TAG: [v2.0.2][2.0.2t]
1435
- COVERAGE: 100.00% -- 119/119 lines in 7 files
1536
- BRANCH COVERAGE: 100.00% -- 35/35 branches in 7 files
1637
- 100.00% documented
@@ -26,7 +47,8 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2.
2647
- Documentation site at [snaky-hash.galtzo.com](https://snaky-hash.galtzo.com) (@pboling)
2748
- 100% documented! (@pboling)
2849

29-
## [2.0.1] - 2022-09-23 ([tag][2.0.1t])
50+
## [2.0.1] - 2022-09-23
51+
- TAG: [v2.0.1][2.0.1t]
3052
### Added
3153
- Certificate for signing gem releases (@pboling)
3254
- Gemspec metadata (@pboling)
@@ -36,7 +58,8 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2.
3658
### Changed
3759
- Gem releases are now cryptographically signed (@pboling)
3860

39-
## [2.0.0] - 2022-08-29 ([tag][2.0.0t])
61+
## [2.0.0] - 2022-08-29
62+
- TAG: [v2.0.0][2.0.0t]
4063
### Changed
4164
- **BREAKING**: `SnakeHash::Snake` is now a mixin, now with support for symbol or string keys
4265
```ruby
@@ -48,18 +71,22 @@ end
4871
- `SnakyHash::StringKeyed`: a Hashie::Mash class with snake-cased String keys
4972
- `SnakyHash::SymbolKeyed`: a Hashie::Mash class with snake-cased Symbol keys
5073

51-
## [1.0.1] - 2022-08-26 ([tag][1.0.1t])
74+
## [1.0.1] - 2022-08-26
75+
- TAG: [v1.0.1][1.0.1t]
5276
### Added
5377
- Missing LICENSE.txt file to release
5478
### Removed
5579
- Accidentally added bundler dependency (vestige of transpec process) is now removed
5680

57-
## [1.0.0] - 2022-08-26 ([tag][1.0.0t])
81+
## [1.0.0] - 2022-08-26
82+
- TAG: [v1.0.0][1.0.0t]
5883
### Added
5984
- Initial release
6085

61-
[Unreleased]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.2...main
62-
[2.0.21]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.1...v2.0.2
86+
[Unreleased]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.3...main
87+
[2.0.3]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.2...v2.0.3
88+
[2.0.3t]: https://gitlab.com/oauth-xx/snaky_hash/-/releases/tag/v2.0.3
89+
[2.0.2]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.1...v2.0.2
6390
[2.0.2t]: https://gitlab.com/oauth-xx/snaky_hash/-/releases/tag/v2.0.2
6491
[2.0.1]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.0...v2.0.1
6592
[2.0.1t]: https://gitlab.com/oauth-xx/snaky_hash/-/releases/tag/v2.0.1

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ GIT
2323
PATH
2424
remote: .
2525
specs:
26-
snaky_hash (2.0.2)
26+
snaky_hash (2.0.3)
2727
hashie (>= 0.1.0, < 6)
2828
version_gem (>= 1.1.8, < 3)
2929

README.md

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 🐍 SnakyHash
22

3-
[![Version][👽versioni]][👽version] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![Depfu][🔑depfui♻️]][🔑depfu] [![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls] [![QLTY Test Coverage][🔑qlty-covi♻️]][🔑qlty-cov] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf] [![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf] [![CI Ancient][🚎1-an-wfi]][🚎1-an-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL]
3+
[![Version][👽versioni]][👽version] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![Depfu][🔑depfui♻️]][🔑depfu] [![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls] [![QLTY Test Coverage][🔑qlty-covi♻️]][🔑qlty-cov] [![QLTY Maintainability][🔑qlty-mnti♻️]][🔑qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf] [![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf] [![CI Ancient][🚎1-an-wfi]][🚎1-an-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL]
44

55
---
66

@@ -14,7 +14,7 @@ and provide a nice psuedo-object interface.
1414
It can be thought of as a mashup of:
1515

1616
* `Rash` (specifically the [`rash_alt`](https://github.com/shishi/rash_alt) flavor), which is a special `Mash`, made popular by the `hashie` gem, and
17-
* `serialized_hashie` [gem by krystal](https://github.com/krystal/serialized-hashie)
17+
* `serialized_hashie` [gem by krystal](https://github.com/krystal/serialized-hashie), rewritten, with some behavior changes
1818

1919
Classes that `include SnakyHash::Snake.new` should inherit from `Hashie::Mash`.
2020

@@ -46,6 +46,17 @@ SnakyHash::StringKeyed.class_eval do
4646
end
4747
```
4848

49+
or you can create a custom class
50+
51+
```ruby
52+
class MyHash < Hashie::Mash
53+
include SnakyHash::Snake.new(key_type: :string, serializer: true)
54+
# Which is the same as:
55+
# include SnakyHash::Snake.new(key_type: :string)
56+
# extend SnakyHash::Serializer
57+
end
58+
```
59+
4960
You can then add serialization extensions as needed. See [serialization](#serialization) and [extensions](#extensions) for more.
5061

5162
| Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
@@ -212,71 +223,95 @@ This is also not a bug, though if you need different behavior, there is a soluti
212223

213224
You can write your own arbitrary extensions:
214225

215-
* "Hash Load" extensions operate on the hash, and nested hashes
226+
* "Hash Load" extensions operate on the hash and nested hashes
216227
* use `::load_hash_extensions.add(:extension_name) { |hash| }`
217-
* "Load" extensions operate on the values, and nested hash's values, if any
218-
* use `::load_extensions.add(:extension_name) { |value| }`
219-
* "Dump" extensions operate on the values, and nested hash's values, if any
220-
* use `::dump_extensions.add(:extension_name) { |value| }`
228+
* since v2.0.2, bugs fixed in v2.0.3
229+
* "Value Load" extensions operate on the values, and nested hashes' values, if any
230+
* use `::load_value_extensions.add(:extension_name) { |value| }`
231+
* since v2.0.2, bugs fixed in v2.0.3
232+
* "Hash Dump" extensions operate on the hash and nested hashes
233+
* use `::dump_hash_extensions.add(:extension_name) { |value| }`
234+
* since v2.0.3
235+
* "Value Dump" extensions operate on the values, and nested hashes' values, if any
236+
* use `::dump_value_extensions.add(:extension_name) { |value| }`
237+
* since v2.0.2, bugs fixed in v2.0.3
221238

222239
#### Example
223240

224-
Let's say I want all integer-like keys, except 0, to be integer keys,
225-
while 0 converts to, and stays, a string forever.
241+
Let's say I want to really smash up my hash and make it more food-like.
226242

227243
```ruby
228244
class MyExtSnakedHash < Hashie::Mash
229245
include SnakyHash::Snake.new(
230246
key_type: :symbol, # default :string
231-
serializer: true, # default: false
247+
serializer: true, # default: false
232248
)
233249
end
234250

235-
MyExtSnakedHash.load_hash_extensions.add(:non_zero_keys_to_int) do |value|
236-
if value.is_a?(Hash)
237-
value.transform_keys do |key|
238-
key_int = key.to_s.to_i
239-
if key_int > 0
240-
key_int
241-
else
242-
key
243-
end
244-
end
245-
else
246-
value
251+
# We could swap all values with indexed apples (obliteraating nested data!)
252+
MyExtSnakedHash.dump_hash_extensions.add(:to_apple) do |value|
253+
num = 0
254+
value.transform_values do |_key|
255+
key = "apple-#{num}"
256+
num += 1
257+
key
247258
end
248259
end
249260

250-
snake = MyExtSnakedHash.new(1 => "a", 0 => 4, "VeryFineHat" => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}) # => {1 => "a", 0 => 4, very_fine_hat: {3 => "v", 5 => 7, very_fine_hat: "feathers"}}
251-
dump = MyExtSnakedHash.dump(snake) # => "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}"
252-
hydrated = MyExtSnakedHash.load(dump) # => {1 => "a", "0": 4, very_fine_hat: {3 => "v", 5 => 7, very_fine_hat: "feathers"}}
253-
hydrated.class # => MyExtSnakedHash
254-
hydrated["1"] # => nil
255-
hydrated[1] # => "a"
256-
hydrated["0"] # => 4
257-
hydrated[0] # => nil
258-
hydrated.very_fine_hat # => {3 => "v", 5 => 7, very_fine_hat: "feathers"}
259-
hydrated.very_fine_hat.very_fine_hat # => "feathers"
260-
hydrated.very_fine_hat[:very_fine_hat] # => 'feathers'
261-
hydrated.very_fine_hat["very_fine_hat"] # => 'feathers'
261+
# And then when loading the dump we could convert the yum to pear
262+
MyExtSnakedHash.load_hash_extensions.add(:apple_to_pear) do |value|
263+
value.transform_keys do |key|
264+
key.to_s.sub("yum", "pear")
265+
end
266+
end
267+
268+
# We could swap all index numbers "beet-<number>"
269+
MyExtSnakedHash.dump_value_extensions.add(:to_beet) do |value|
270+
value.to_s.sub(/(\d+)/) { |match| "beet-#{match[0]}" }
271+
end
272+
273+
# And then when loading the dump we could convert beet to corn
274+
MyExtSnakedHash.load_value_extensions.add(:beet_to_corn) do |value|
275+
value.to_s.sub("beet", "corn")
276+
end
277+
278+
snake = MyExtSnakedHash.new({"YumBread" => "b", "YumCake" => {"b" => "b"}, "YumBoba" => [1, 2, 3]})
279+
snake # => {yum_bread: "b", yum_cake: {b: "b"}, yum_boba: [1, 2, 3]}
280+
snake.yum_bread # => "b"
281+
snake.yum_cake # => {b: "b"}
282+
snake.yum_boba # => [1, 2, 3]
283+
dump = snake.dump
284+
dump # => "{\"yum_bread\":\"apple-beet-0\",\"yum_cake\":\"apple-beet-1\",\"yum_boba\":\"apple-beet-2\"}"
285+
hydrated = MyExtSnakedHash.load(dump)
286+
hydrated # => {pear_bread: "apple-corn-0", pear_cake: "apple-corn-1", pear_boba: "apple-corn-2"}
262287
```
263288

264289
See the specs for more examples.
265290

266-
### Stranger Things
291+
### Bad Ideas
267292

268293
I don't recommend using these features... but they exist (for now).
294+
295+
<details>
296+
<summary>Show me what I should *not* do!</summary>
297+
269298
You can still access the original un-snaked camel keys.
270299
And through them you can even use un-snaked camel methods.
271300
But don't.
272301

273302
```ruby
303+
snake = SnakyHash::StringKeyed["VeryFineHat" => "Feathers"]
274304
snake.key?("VeryFineHat") # => true
275305
snake["VeryFineHat"] # => 'Feathers'
276306
snake.VeryFineHat # => 'Feathers', PLEASE don't do this!!!
277307
snake["VeryFineHat"] = "pop" # Please don't do this... you'll get a warning, and it works (for now), but no guarantees.
278308
# WARN -- : You are setting a key that conflicts with a built-in method MySnakedHash#VeryFineHat defined in MySnakedHash. This can cause unexpected behavior when accessing the key as a property. You can still access the key via the #[] method.
279309
# => "pop"
310+
```
311+
312+
Since you are reading this, here's what to do instead.
313+
314+
```ruby
280315
snake.very_fine_hat = "pop" # => 'pop', do this instead!!!
281316
snake.very_fine_hat # => 'pop'
282317
snake[:very_fine_hat] = "moose" # => 'moose', or do this instead!!!
@@ -285,6 +320,8 @@ snake["very_fine_hat"] = "cheese" # => 'cheese', or do this instead!!!
285320
snake.very_fine_hat # => 'cheese'
286321
```
287322

323+
</details>
324+
288325
### 🚀 Release Instructions
289326

290327
See [CONTRIBUTING.md][🤝contributing].
@@ -553,7 +590,7 @@ or one of the others at the head of this README.
553590
[📌gitmoji]:https://gitmoji.dev
554591
[📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20😜%20😍-34495e.svg?style=flat-square
555592
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
556-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.119-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
593+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.132-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
557594
[🔐security]: SECURITY.md
558595
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
559596
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year

0 commit comments

Comments
 (0)