otoindiff 0.1.0 → 0.2.1

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: d3ca434647f9c7853413d2a3cd0fd2965b763bfd50c404f583af67a98651b47b
4
- data.tar.gz: 9a3bde8b5cbe13efc52f585b77e5bad3a57ab1dc13eae88700a162238c5c986f
3
+ metadata.gz: 1aabb24819d8d4ac57b6694ac105527a9a9cf4b8faff156130928a635e288a91
4
+ data.tar.gz: 6c7bc3fbb207a33667448fb2af8223ab1562a00895f0940fca148cc12edfd92d
5
5
  SHA512:
6
- metadata.gz: a2bae63aaa05d12a3bac9e30df8e91f98e3e871fa0d445ccb6804c8f94b5c3c4453eacfbccac675983ac29d71ef4e0a71878eb2a543e105c45e00a5d7f6c1f63
7
- data.tar.gz: 983e9a7ccefdff0be3b9ff135a13c1a6df970c0ce1c00c5ea31e4b5a35cda5597e77aad7395ea145b5946ffe571ffcb5c097669eeb2c7e1010874efe10dd7021
6
+ metadata.gz: c370cca113c9e7e0120a866dcfbc9ce92637488d06338c0623bccb7ddbfe58a9afd8af1d7e31bb0e598fb2585053263beb820d3acf7571608966e47e00a910a6
7
+ data.tar.gz: 6c16fa6b2b110ea8ab20475fa6c1ddd5c81e5da003d2a6be201cf63e43fc301bc94d779c70b44f0f744ffa62d25eb8fcdf99046bd943860b8a2913f5fc4ad922
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1] - 2026-10-15
4
+
5
+ - Fix `dig` raising `NoMethodError` instead of Ruby's `TypeError` when the path
6
+ continues past a value that has no `#dig` (e.g. `{ a: 1 }.dig(:a, :x)`), both
7
+ globally and under `using Otoindiff::Disabled`.
8
+
9
+ ## [0.2.0] - 2026-10-15
10
+
11
+ - Add `Otoindiff::Disabled` refinement to opt a single class out of indifferent
12
+ access (`using Otoindiff::Disabled`) while the rest of the code base keeps it.
13
+ - Fix i18n lookups always returning "Translation missing": hashes with a default proc
14
+ (such as the i18n backend's translations store) now receive the caller's key instead
15
+ of a converted one, so entries are no longer stored under the wrong key type.
16
+ - Fix `fetch` with a default value or block ignoring a key stored as the other type
17
+ (`{ foo: 1 }.fetch('foo', 0)` now returns `1`).
18
+ - Fix `dig` returning `nil` when traversing into arrays or other non-Hash objects.
19
+ - Fix `false` values being returned as `nil` when accessed with the other key type.
20
+
3
21
  ## [0.1.0] - 2024-12-12
4
22
 
5
23
  - Initial release
data/README.md CHANGED
@@ -15,3 +15,35 @@ Add this line to your application's Gemfile:
15
15
  ```ruby
16
16
  gem 'otoindiff'
17
17
  ```
18
+
19
+ ## Usage
20
+
21
+ Requiring the gem enables indifferent access for every `Hash`:
22
+
23
+ ```ruby
24
+ hash = { foo: 'bar', 'baz' => 'qux' }
25
+ hash['foo'] # => "bar"
26
+ hash[:baz] # => "qux"
27
+ hash.fetch('foo') # => "bar"
28
+ hash.dig('foo') # => "bar"
29
+ ```
30
+
31
+ ### Opting a class out
32
+
33
+ To keep plain Hash behaviour in a single class, activate the
34
+ `Otoindiff::Disabled` refinement inside it. The rest of the code base stays
35
+ indifferent:
36
+
37
+ ```ruby
38
+ class StrictParser
39
+ using Otoindiff::Disabled
40
+
41
+ def call(hash)
42
+ hash['foo'] # => nil for { foo: 'bar' }; only the String key matches
43
+ end
44
+ end
45
+ ```
46
+
47
+ Refinements are lexical: they apply to `[]`, `fetch` and `dig` calls written
48
+ inside that class body, not to hashes the class hands to other code.
49
+
data/Rakefile CHANGED
@@ -5,4 +5,12 @@ require 'minitest/test_task'
5
5
 
6
6
  Minitest::TestTask.create
7
7
 
8
+ namespace :benchmark do
9
+ desc 'Run benchmarks'
10
+ task :run do
11
+ puts 'Running benchmarks...'
12
+ system('ruby benchmarks/benchmark.rb')
13
+ end
14
+ end
15
+
8
16
  task default: :test
data/bin/benchmark ADDED
@@ -0,0 +1,9 @@
1
+
2
+ #!/usr/bin/env ruby
3
+ # frozen_string_literal: true
4
+
5
+ require 'bundler/setup'
6
+
7
+ puts 'Running benchmarks...'
8
+ system('ruby benchmarks/benchmark.rb')
9
+
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Otoindiff
4
+ # Refinement that restores plain Hash key access where it is activated.
5
+ #
6
+ # otoindiff is enabled globally on require. Activate this refinement in a
7
+ # class (or file) that needs strict String/Symbol key distinction; Hash calls
8
+ # written lexically inside it bypass the indifferent lookup.
9
+ #
10
+ # @example Opt a single class out
11
+ # class StrictParser
12
+ # using Otoindiff::Disabled
13
+ #
14
+ # def call(hash)
15
+ # hash['foo'] # only matches the String key 'foo'
16
+ # end
17
+ # end
18
+ module Disabled
19
+ refine Hash do
20
+ def [](key)
21
+ original_brackets(key)
22
+ end
23
+
24
+ def fetch(key, *args, &block)
25
+ original_fetch(key, *args, &block)
26
+ end
27
+
28
+ # Native #dig (on Hash, Array, Struct) dispatches to the globally patched
29
+ # Hash#dig of nested hashes, so walk the path one key at a time to keep
30
+ # every level strict.
31
+ def dig(key, *rest)
32
+ [key, *rest].reduce(self) do |current, k|
33
+ break nil if current.nil?
34
+
35
+ next current.original_brackets(k) if current.is_a?(Hash)
36
+ raise TypeError, "#{current.class} does not have #dig method" unless current.respond_to?(:dig)
37
+
38
+ current.dig(k)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Otoindiff
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.1'
5
5
  end
data/lib/otoindiff.rb CHANGED
@@ -2,9 +2,6 @@
2
2
 
3
3
  require_relative 'otoindiff/version'
4
4
 
5
- require 'active_support'
6
- require 'active_support/core_ext/hash/indifferent_access'
7
-
8
5
  # Extends Ruby's Hash class with more flexible key access methods
9
6
  #
10
7
  # This extension provides enhanced hash key lookup capabilities:
@@ -27,33 +24,22 @@ class Hash
27
24
  # Retrieve a hash value with flexible key type conversion
28
25
  #
29
26
  # @param key [Object] The key to retrieve
30
- # @return [Object, nil] The value associated with the key, or nil if not found
27
+ # @return [Object, nil] The value associated with the key, or the hash's default if not found
31
28
  def [](key)
32
- return original_brackets(key) if key?(key)
33
-
34
- if key.is_a?(String) || key.is_a?(Symbol)
35
- original_brackets(key.to_s) || original_brackets(key.to_sym)
36
- else
37
- original_brackets(key)
38
- end
29
+ original_brackets(otoindiff_key(key))
39
30
  end
40
31
 
41
32
  # Dig into a nested hash with flexible key type conversion
42
33
  #
43
- # @param keys [Array<Object>] Path of keys to traverse
34
+ # @param key [Object] The first key to retrieve
35
+ # @param rest [Array<Object>] Remaining path of keys, passed on to the nested object's #dig
44
36
  # @return [Object, nil] The nested value, or nil if path is invalid
45
- def dig(*keys)
46
- keys.reduce(self) do |current, key|
47
- break nil unless current.is_a?(Hash)
37
+ def dig(key, *rest)
38
+ value = self[key]
39
+ return value if rest.empty? || value.nil?
40
+ raise TypeError, "#{value.class} does not have #dig method" unless value.respond_to?(:dig)
48
41
 
49
- if key.is_a?(String) || key.is_a?(Symbol)
50
- current.original_brackets(key) ||
51
- current.original_brackets(key.to_s) ||
52
- current.original_brackets(key.to_sym)
53
- else
54
- current.original_brackets(key)
55
- end
56
- end
42
+ value.dig(*rest)
57
43
  end
58
44
 
59
45
  # Fetch a hash value with flexible key type conversion and error handling
@@ -63,16 +49,22 @@ class Hash
63
49
  # @return [Object] The value associated with the key
64
50
  # @raise [KeyError] If key is not found and no default is provided
65
51
  def fetch(key, *args, &block)
66
- return original_fetch(key, *args, &block) if key?(key)
52
+ original_fetch(otoindiff_key(key), *args, &block)
53
+ end
67
54
 
68
- if key.is_a?(String) || key.is_a?(Symbol)
69
- begin
70
- original_fetch(key.to_s, *args, &block)
71
- rescue KeyError
72
- original_fetch(key.to_sym, *args, &block)
73
- end
74
- else
75
- original_fetch(key, *args, &block)
76
- end
55
+ private
56
+
57
+ # Resolve a String/Symbol key to whichever form is actually stored.
58
+ # Falls back to the caller's key so defaults and default procs receive it unchanged.
59
+ def otoindiff_key(key)
60
+ return key if key?(key)
61
+
62
+ alt = case key
63
+ when String then key.to_sym
64
+ when Symbol then key.to_s
65
+ end
66
+ alt && key?(alt) ? alt : key
77
67
  end
78
68
  end
69
+
70
+ require_relative 'otoindiff/disabled'
data/sig/otoindiff.rbs CHANGED
@@ -1,6 +1,10 @@
1
1
  module Otoindiff
2
2
  VERSION: String
3
3
 
4
+ # Refinement restoring plain Hash key access where activated with `using`
5
+ module Disabled
6
+ end
7
+
4
8
  # Extended methods for Hash class with flexible key access
5
9
  module HashExtensions
6
10
  # Retrieve a value with flexible key type conversion
metadata CHANGED
@@ -1,29 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: otoindiff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - bugloper
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-12-12 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: activesupport
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '8.0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '8.0'
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
27
12
  description: The otoindiff gem modifies all Ruby Hash objects to support indifferent
28
13
  access by default, allowing seamless use of string or symbol keys interchangeably.
29
14
  email:
@@ -35,11 +20,13 @@ files:
35
20
  - CHANGELOG.md
36
21
  - README.md
37
22
  - Rakefile
38
- - benchmarks/oto_benchmarks.rb
23
+ - benchmarks/benchmark.rb
24
+ - bin/benchmark
39
25
  - bin/console
40
26
  - bin/release
41
27
  - bin/setup
42
28
  - lib/otoindiff.rb
29
+ - lib/otoindiff/disabled.rb
43
30
  - lib/otoindiff/version.rb
44
31
  - sig/otoindiff.rbs
45
32
  homepage: https://github.com/bugloper/otoindiff
@@ -50,7 +37,6 @@ metadata:
50
37
  source_code_uri: https://github.com/bugloper/otoindiff
51
38
  changelog_uri: https://github.com/bugloper/otoindiff/blob/main/CHANGELOG.md
52
39
  rubygems_mfa_required: 'false'
53
- post_install_message:
54
40
  rdoc_options: []
55
41
  require_paths:
56
42
  - lib
@@ -65,8 +51,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
51
  - !ruby/object:Gem::Version
66
52
  version: '0'
67
53
  requirements: []
68
- rubygems_version: 3.5.11
69
- signing_key:
54
+ rubygems_version: 4.0.16
70
55
  specification_version: 4
71
56
  summary: Enhances Ruby hashes with default indifferent access.
72
57
  test_files: []
File without changes