Skip to content

Commit 7f98a12

Browse files
committed
Merge branch 'feat/extensions' into 'main'
✨ Serialization Extensions See merge request oauth-xx/snaky_hash!2
2 parents 70e4c19 + c63d03d commit 7f98a12

26 files changed

+467
-62
lines changed

.rubocop_gradual.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
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]
1313
],
14-
"spec/snaky_hash/snake_spec.rb:3264128361": [
14+
"spec/snaky_hash/snake_spec.rb:1921604918": [
1515
[4, 3, 92, "RSpec/LeakyConstantDeclaration: Stub class constant instead of declaring explicitly.", 3047242215]
1616
]
1717
}

CHANGELOG.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,21 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2.
66

77
## [Unreleased]
88
### Added
9+
### Changed
10+
### Fixed
11+
### Removed
12+
13+
## [2.0.2] - 2025-05-21 ([tag][2.0.2t])
14+
### Added
915
- Gem is signed by 20-year cert (@pboling)
1016
- Expires 2045-04-29
1117
- Gemspec metadata updates (@pboling)
1218
- Documentation updates (@pboling)
1319
- CI covering all code, and all key versions of runtime dependencies (@pboling)
20+
- Including Hashie v0, v1, v2, v3, v4, v5, and HEAD
1421
- [gh2](https://github.com/oauth-xx/snaky_hash/pull/2) - Serializer option (@pboling)
15-
### Changed
16-
### Fixed
17-
### Removed
22+
- [gh3](https://github.com/oauth-xx/snaky_hash/pull/3) - Serializer Extensions (@pboling)
23+
- Documentation site at [snaky-hash.galtzo.com](https://snaky-hash.galtzo.com)
1824

1925
## [2.0.1] - 2022-09-23 ([tag][2.0.1t])
2026
### Added
@@ -48,7 +54,9 @@ end
4854
### Added
4955
- Initial release
5056

51-
[Unreleased]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.1...main
57+
[Unreleased]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.2...main
58+
[2.0.21]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.1...v2.0.2
59+
[2.0.2t]: https://gitlab.com/oauth-xx/snaky_hash/-/releases/tag/v2.0.2
5260
[2.0.1]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.0...v2.0.1
5361
[2.0.1t]: https://gitlab.com/oauth-xx/snaky_hash/-/releases/tag/v2.0.1
5462
[2.0.0]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v1.0.1...v2.0.0

README.md

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

33
[![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]
44

55
---
66

77
[![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
88

9-
This library is similar in purpose to the HashWithIndifferentAccess that is famously used in Rails.
9+
This library is similar in purpose to the HashWithIndifferentAccess that is famously used in Rails, but does a lot more.
1010

11-
This gem is used by `oauth`, `oauth2`, and other, gems to normalize hash keys to `snake_case` and lookups,
11+
This gem is used by `oauth` and `oauth2` gems to normalize hash keys to `snake_case` and lookups,
1212
and provide a nice psuedo-object interface.
1313

14-
It can be thought of as a mashup, with upgrades, to the `Rash` (specifically the [`rash_alt`](https://github.com/shishi/rash_alt) flavor), which is a special `Mash`, made popular by the `hashie` gem, and the `serialized_hashie` [gem by krystal](https://github.com/krystal/serialized-hashie).
14+
It can be thought of as a mashup of:
1515

16-
Classes that include `SnakyHash::Snake` should inherit from `Hashie::Mash`.
16+
* `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)
18+
19+
Classes that `include SnakyHash::Snake.new` should inherit from `Hashie::Mash`.
1720

1821
## New for v2.0.2: Serialization Support
1922

@@ -29,6 +32,8 @@ class MyStringKeyedHash < Hashie::Mash
2932
end
3033
```
3134

35+
✨ Also new dump & load plugin extensions to control the way your data is dumped and loaded.
36+
3237
| Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
3338
|-----------------------------------------------|-------------------------------------------------------------------|---------------------------|--------------------------|---------------------------|--------------------------|------------------------------|
3439
| 🧪 [oauth-xx/snaky_hash on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜wiki] | 🏀 Tiny Matrix ||
@@ -147,7 +152,8 @@ end
147152
snake = MySnakedHash.new(:a => "a", "b" => "b", 2 => 2, "VeryFineHat" => "Feathers")
148153
snake.a # => 'a'
149154
snake.b # => 'b'
150-
snake[2] # 2
155+
snake[2] # => 2
156+
snake["2"] # => nil, note that this gem only affects string / symbol keys.
151157
snake.very_fine_hat # => 'Feathers'
152158
snake[:very_fine_hat] # => 'Feathers'
153159
snake["very_fine_hat"] # => 'Feathers'
@@ -158,11 +164,97 @@ The `key_type` determines how the key is actually stored, but the hash acts as "
158164
Note also that keys which do not respond to `to_sym`, because they don't have a natural conversion to a Symbol,
159165
are left as-is.
160166

167+
### Serialization
168+
169+
```ruby
170+
class MySerializedSnakedHash < Hashie::Mash
171+
include SnakyHash::Snake.new(
172+
key_type: :symbol, # default :string
173+
serializer: true, # default: false
174+
)
175+
end
176+
177+
snake = MySerializedSnakedHash.new(:a => "a", "b" => "b", 2 => 2, "VeryFineHat" => "Feathers") # => {a: "a", b: "b", 2 => 2, very_fine_hat: "Feathers"}
178+
dump = MySerializedSnakedHash.dump(snake) # => "{\"a\":\"a\",\"b\":\"b\",\"2\":2,\"very_fine_hat\":\"Feathers\"}"
179+
hydrated = MySerializedSnakedHash.load(dump) # => {a: "a", b: "b", "2": 2, very_fine_hat: "Feathers"}
180+
hydrated.class # => MySerializedSnakedHash
181+
hydrated.a # => 'a'
182+
hydrated.b # => 'b'
183+
hydrated[2] # => nil # NOTE: this is the opposite of snake[2] => 2
184+
hydrated["2"] # => 2 # NOTE: this is the opposite of snake["2"] => nil
185+
hydrated.very_fine_hat # => 'Feathers'
186+
hydrated[:very_fine_hat] # => 'Feathers'
187+
hydrated["very_fine_hat"] # => 'Feathers'
188+
```
189+
190+
Note that the key `VeryFineHat` changed to `very_fine_hat`.
191+
That is indeed the point of this library, so not a bug.
192+
193+
Note that the key `2` changed to `"2"` (because JSON keys are strings).
194+
When the JSON dump was reloaded it did not know to restore it as `2` instead of `"2"`.
195+
This is also not a bug, though if you need different behavior, there is a solution in the [next section](#extensions).
196+
197+
### Extensions
198+
199+
You can write your own arbitrary extensions:
200+
201+
* "Hash Load" extensions operate on the hash, and nested hashes
202+
* use `::load_hash_extensions.add(:extension_name) { |hash| }`
203+
* "Load" extensions operate on the values, and nested hash's values, if any
204+
* use `::load_extensions.add(:extension_name) { |value| }`
205+
* "Dump" extensions operate on the values, and nested hash's values, if any
206+
* use `::dump_extensions.add(:extension_name) { |value| }`
207+
208+
#### Example
209+
210+
Let's say I want all integer-like keys, except 0, to be integer keys,
211+
while 0 converts to, and stays, a string forever.
212+
213+
```ruby
214+
class MyExtSnakedHash < Hashie::Mash
215+
include SnakyHash::Snake.new(
216+
key_type: :symbol, # default :string
217+
serializer: true, # default: false
218+
)
219+
end
220+
221+
MyExtSnakedHash.load_hash_extensions.add(:non_zero_keys_to_int) do |value|
222+
if value.is_a?(Hash)
223+
value.transform_keys do |key|
224+
key_int = key.to_s.to_i
225+
if key_int > 0
226+
key_int
227+
else
228+
key
229+
end
230+
end
231+
else
232+
value
233+
end
234+
end
235+
236+
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"}}
237+
dump = MyExtSnakedHash.dump(snake) # => "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}"
238+
hydrated = MyExtSnakedHash.load(dump) # => {1 => "a", "0": 4, very_fine_hat: {3 => "v", 5 => 7, very_fine_hat: "feathers"}}
239+
hydrated.class # => MyExtSnakedHash
240+
hydrated["1"] # => nil
241+
hydrated[1] # => "a"
242+
hydrated["0"] # => 4
243+
hydrated[0] # => nil
244+
hydrated.very_fine_hat # => {3 => "v", 5 => 7, very_fine_hat: "feathers"}
245+
hydrated.very_fine_hat.very_fine_hat # => "feathers"
246+
hydrated.very_fine_hat[:very_fine_hat] # => 'feathers'
247+
hydrated.very_fine_hat["very_fine_hat"] # => 'feathers'
248+
```
249+
250+
See the specs for more examples.
251+
161252
### Stranger Things
162253

163254
I don't recommend using these features... but they exist (for now).
164255
You can still access the original un-snaked camel keys.
165256
And through them you can even use un-snaked camel methods.
257+
But don't.
166258

167259
```ruby
168260
snake.key?("VeryFineHat") # => true

doc/SnakyHash.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ <h2>Overview</h2><div class="docstring">
123123
</div>
124124

125125
<div id="footer">
126-
Generated on Thu May 22 00:16:01 2025 by
126+
Generated on Thu May 22 03:35:07 2025 by
127127
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
128128
0.9.37 (ruby-3.4.3).
129129
</div>

doc/SnakyHash/Error.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
</div>
115115

116116
<div id="footer">
117-
Generated on Thu May 22 00:16:01 2025 by
117+
Generated on Thu May 22 03:35:07 2025 by
118118
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
119119
0.9.37 (ruby-3.4.3).
120120
</div>

doc/SnakyHash/Extensions.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ <h3 class="signature " id="run-instance_method">
446446
</div>
447447

448448
<div id="footer">
449-
Generated on Thu May 22 00:16:01 2025 by
449+
Generated on Thu May 22 03:35:07 2025 by
450450
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
451451
0.9.37 (ruby-3.4.3).
452452
</div>

doc/SnakyHash/Serializer.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ <h3 class="signature " id="load-instance_method">
303303

304304
<span class='kw'>def</span> <span class='id identifier rubyid_load'>load</span><span class='lparen'>(</span><span class='id identifier rubyid_raw_hash'>raw_hash</span><span class='rparen'>)</span>
305305
<span class='id identifier rubyid_hash'>hash</span> <span class='op'>=</span> <span class='const'>JSON</span><span class='period'>.</span><span class='id identifier rubyid_parse'>parse</span><span class='lparen'>(</span><span class='id identifier rubyid_presence'>presence</span><span class='lparen'>(</span><span class='id identifier rubyid_raw_hash'>raw_hash</span><span class='rparen'>)</span> <span class='op'>||</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>{}</span><span class='tstring_end'>&quot;</span></span><span class='rparen'>)</span>
306-
<span class='id identifier rubyid_hash'>hash</span> <span class='op'>=</span> <span class='id identifier rubyid_load_hash'>load_hash</span><span class='lparen'>(</span><span class='id identifier rubyid_hash'>hash</span><span class='rparen'>)</span>
306+
<span class='id identifier rubyid_hash'>hash</span> <span class='op'>=</span> <span class='id identifier rubyid_load_value'>load_value</span><span class='lparen'>(</span><span class='id identifier rubyid_new'>new</span><span class='lparen'>(</span><span class='id identifier rubyid_hash'>hash</span><span class='rparen'>)</span><span class='rparen'>)</span>
307307
<span class='id identifier rubyid_new'>new</span><span class='lparen'>(</span><span class='id identifier rubyid_hash'>hash</span><span class='rparen'>)</span>
308308
<span class='kw'>end</span></pre>
309309
</td>
@@ -316,7 +316,7 @@ <h3 class="signature " id="load-instance_method">
316316
</div>
317317

318318
<div id="footer">
319-
Generated on Thu May 22 00:16:01 2025 by
319+
Generated on Thu May 22 03:35:07 2025 by
320320
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
321321
0.9.37 (ruby-3.4.3).
322322
</div>

doc/SnakyHash/Serializer/BackportedInstanceMethods.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ <h3 class="signature first" id="transform_values-instance_method">
190190
</div>
191191

192192
<div id="footer">
193-
Generated on Thu May 22 00:16:01 2025 by
193+
Generated on Thu May 22 03:35:07 2025 by
194194
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
195195
0.9.37 (ruby-3.4.3).
196196
</div>

doc/SnakyHash/Serializer/Modulizer.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ <h3 class="signature first" id="to_extended_mod-class_method">
189189
</div>
190190

191191
<div id="footer">
192-
Generated on Thu May 22 00:16:01 2025 by
192+
Generated on Thu May 22 03:35:07 2025 by
193193
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
194194
0.9.37 (ruby-3.4.3).
195195
</div>

doc/SnakyHash/Snake.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ <h3 class="signature first" id="included-instance_method">
275275
</div>
276276

277277
<div id="footer">
278-
Generated on Thu May 22 00:16:01 2025 by
278+
Generated on Thu May 22 03:35:07 2025 by
279279
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
280280
0.9.37 (ruby-3.4.3).
281281
</div>

0 commit comments

Comments
 (0)