hash_with_dot_access 1.2.0 → 2.1.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/.ruby-version +1 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile.lock +3 -15
- data/README.md +25 -6
- data/hash_with_dot_access.gemspec +3 -3
- data/lib/hash_with_dot_access/version.rb +1 -1
- data/lib/hash_with_dot_access.rb +149 -35
- metadata +12 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be6e40c79e1f2c6148e84b6ef6b3521d9c07500bac9603f793a39dac7b3278ce
|
4
|
+
data.tar.gz: ace5ebdd3d951858e6dda87a96cfb9301b4ce7dc9802184cdee1cbd69e664538
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8763ae8a9cd9aec38d1cf2c4e0d659824633295576dfc3132968b82d848f92e354e5fb417f182b5b9be32ba8e2276996e3f36a2e3b8ce71de404c817cd599f1f
|
7
|
+
data.tar.gz: 8ee6d9ba1bdbc2322c40c1a4c79136d43c94376ec167f334123e17844cde1e9364a6a8bee75573827704a12152f328a55bf86980ab249c4170fac9482caa043e
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.1.4
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [2.1.0] - 2024-04-20
|
4
|
+
|
5
|
+
- Adds conversion back to a hash with `to_h` and `to_dot_h` for enumeration.
|
6
|
+
|
7
|
+
## [2.0.0] - 2024-04-20
|
8
|
+
|
9
|
+
- Completely rewrite internals and remove Active Support as a dependency
|
10
|
+
(this means HashWithDotAccess::Hash supports string, symbol, and dot access with zero dependencies)
|
11
|
+
- Dot access now writes accessors to the class, improving performance (`method_missing` only runs once per key/accessor, essentially)
|
12
|
+
|
13
|
+
## [1.2.0] - 2021-12-16
|
14
|
+
|
15
|
+
- Support Rails 7
|
data/Gemfile.lock
CHANGED
@@ -1,24 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
hash_with_dot_access (1.
|
5
|
-
activesupport (>= 5.0.0, < 8.0)
|
4
|
+
hash_with_dot_access (2.1.0)
|
6
5
|
|
7
6
|
GEM
|
8
7
|
remote: https://rubygems.org/
|
9
8
|
specs:
|
10
|
-
|
11
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
12
|
-
i18n (>= 1.6, < 2)
|
13
|
-
minitest (>= 5.1)
|
14
|
-
tzinfo (~> 2.0)
|
15
|
-
concurrent-ruby (1.1.9)
|
16
|
-
i18n (1.8.11)
|
17
|
-
concurrent-ruby (~> 1.0)
|
18
|
-
minitest (5.15.0)
|
19
|
-
rake (13.0.6)
|
20
|
-
tzinfo (2.0.4)
|
21
|
-
concurrent-ruby (~> 1.0)
|
9
|
+
rake (13.2.1)
|
22
10
|
|
23
11
|
PLATFORMS
|
24
12
|
ruby
|
@@ -29,4 +17,4 @@ DEPENDENCIES
|
|
29
17
|
rake (~> 13.0)
|
30
18
|
|
31
19
|
BUNDLED WITH
|
32
|
-
2.
|
20
|
+
2.5.6
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Hash with Dot Access
|
2
2
|
|
3
|
-
|
3
|
+
This gem provides a Ruby `Hash` subclass which lets you access hash values with either string or symbol keys, as well as via methods (aka dot access). It utilizes `method_missing` to access key data if available, and you can also set data using `keyname=`. Our goal is on providing good performance and if anything offering a _subset_ of standard Hash functionality (it's a non-goal to add all-new Hash-related functionality to this class).
|
4
|
+
|
5
|
+
Performance is improved over long-running processes (such as the build process of the [Bridgetown](https://www.bridgetownrb.com) framework) by automatically defining accessors on the class so that `method_missing` is only called once per key/accessor pair.
|
4
6
|
|
5
7
|
## Example
|
6
8
|
|
@@ -26,13 +28,17 @@ hsh[:d]
|
|
26
28
|
hsh["d"]
|
27
29
|
# => "Indeed!"
|
28
30
|
|
29
|
-
|
31
|
+
# You can use the `as_dots` method on Hash by loading in our refinement.
|
32
|
+
|
33
|
+
using HashWithDotAccess::Refinements
|
34
|
+
|
35
|
+
hsh2 = {test: "dot access"}.as_dots
|
30
36
|
hsh2.test
|
31
37
|
# => "dot access"
|
32
38
|
|
33
39
|
## Nested hashes work too! Pairs nicely with lonely operator: &.
|
34
40
|
|
35
|
-
nested = {a: 1, b: {c: 3}}.
|
41
|
+
nested = {a: 1, b: {c: 3}}.as_dots
|
36
42
|
nested.b.c
|
37
43
|
# => 3
|
38
44
|
|
@@ -41,7 +47,7 @@ nested&.d&.e&.f
|
|
41
47
|
|
42
48
|
## You can also set default return values when key is missing
|
43
49
|
|
44
|
-
hsh = {a: 1, b: 2}.
|
50
|
+
hsh = {a: 1, b: 2}.as_dots
|
45
51
|
hsh.default = 0
|
46
52
|
hsh.a
|
47
53
|
# => 1
|
@@ -49,6 +55,9 @@ hsh.x
|
|
49
55
|
# => 0
|
50
56
|
```
|
51
57
|
|
58
|
+
You can convert a `HashWithDotAccess::Hash` back to a regular Hash with `to_h`, which even works with
|
59
|
+
block enumeration. Or use `to_dot_h` as a `to_h`-like enumerator which preserves dot access.
|
60
|
+
|
52
61
|
## Installation
|
53
62
|
|
54
63
|
Add this line to your application's Gemfile:
|
@@ -63,12 +72,22 @@ Then simply require `hash_with_dot_access`:
|
|
63
72
|
require "hash_with_dot_access"
|
64
73
|
```
|
65
74
|
|
75
|
+
> [!IMPORTANT]
|
76
|
+
> If you're upgrading from an earlier version, and you don't want to modify your code away from using `with_dot_access`, you can add a monkey-patch to `Hash`:
|
77
|
+
> ```ruby
|
78
|
+
> class Hash
|
79
|
+
> def with_dot_access
|
80
|
+
> HashWithDotAccess::Hash.new(self)
|
81
|
+
> end
|
82
|
+
> end
|
83
|
+
> ```
|
84
|
+
|
66
85
|
## Caveats
|
67
86
|
|
68
87
|
As with any Ruby object which provides arbitrary data through dynamic method calls, you may encounter collisions between your key names and existing `Hash` methods. For example:
|
69
88
|
|
70
89
|
```ruby
|
71
|
-
hsh = {each: "this won't work!"}.
|
90
|
+
hsh = {each: "this won't work!"}.as_dots
|
72
91
|
hsh.each
|
73
92
|
# => #<Enumerator: {"each"=>"this won't work!"}:each>
|
74
93
|
#
|
@@ -78,7 +97,7 @@ hsh.each
|
|
78
97
|
Of course, the easy fix is to simply use standard ways of accessing hash data in these cases:
|
79
98
|
|
80
99
|
```ruby
|
81
|
-
hsh = {each: "this will work!"}.
|
100
|
+
hsh = {each: "this will work!"}.as_dots
|
82
101
|
hsh[:each]
|
83
102
|
# => "this will work!"
|
84
103
|
```
|
@@ -6,10 +6,12 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.author = "Bridgetown Team"
|
7
7
|
spec.email = "maintainers@bridgetownrb.com"
|
8
8
|
|
9
|
-
spec.summary = %q{
|
9
|
+
spec.summary = %q{Performance-oriented subclass of Hash which provides symbolized keys and method access}
|
10
10
|
spec.homepage = "https://github.com/bridgetownrb/hash_with_dot_access"
|
11
11
|
spec.license = "MIT"
|
12
12
|
|
13
|
+
spec.required_ruby_version = ">= 3.1.0"
|
14
|
+
|
13
15
|
# Specify which files should be added to the gem when it is released.
|
14
16
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
15
17
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
@@ -17,8 +19,6 @@ Gem::Specification.new do |spec|
|
|
17
19
|
end
|
18
20
|
spec.require_paths = ["lib"]
|
19
21
|
|
20
|
-
spec.add_runtime_dependency "activesupport", [">= 5.0.0", "< 8.0"]
|
21
|
-
|
22
22
|
spec.add_development_dependency "bundler"
|
23
23
|
spec.add_development_dependency "rake", "~> 13.0"
|
24
24
|
end
|
data/lib/hash_with_dot_access.rb
CHANGED
@@ -1,62 +1,176 @@
|
|
1
|
-
require "active_support/core_ext/hash/indifferent_access"
|
2
|
-
|
3
1
|
module HashWithDotAccess
|
4
|
-
|
5
|
-
def
|
2
|
+
module Utils
|
3
|
+
def self.normalized_value(obj, value)
|
4
|
+
return value if value.instance_of?(obj.class)
|
5
|
+
|
6
|
+
case value
|
7
|
+
when ::Hash
|
8
|
+
obj.class.new(value)
|
9
|
+
when Array
|
10
|
+
value = value.dup if value.frozen?
|
11
|
+
value.map! { normalized_value(obj, _1) }
|
12
|
+
else
|
13
|
+
value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.primitive_value(value)
|
18
|
+
case value
|
19
|
+
when ::Hash
|
20
|
+
value.to_h
|
21
|
+
when Array
|
22
|
+
value = value.dup if value.frozen?
|
23
|
+
value.map! { primitive_value(_1) }
|
24
|
+
else
|
25
|
+
value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Hash < ::Hash
|
31
|
+
class << self
|
32
|
+
undef_method :[]
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(hsh = nil)
|
36
|
+
super
|
37
|
+
return unless hsh
|
38
|
+
|
39
|
+
update(hsh)
|
40
|
+
self.default_proc = hsh.default_proc
|
41
|
+
self.default = hsh.default
|
42
|
+
end
|
43
|
+
|
44
|
+
def respond_to_missing?(key, *args)
|
45
|
+
return false unless args.empty?
|
46
|
+
|
6
47
|
return true if "#{key}".end_with?("=")
|
7
48
|
|
8
49
|
key?(key)
|
9
50
|
end
|
10
51
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
52
|
+
def key?(key) = super(key.to_s)
|
53
|
+
|
54
|
+
alias_method :has_key?, :key?
|
55
|
+
alias_method :include?, :key?
|
56
|
+
alias_method :member?, :key?
|
57
|
+
|
58
|
+
# save previous method
|
59
|
+
alias_method :_assign, :[]=
|
60
|
+
|
61
|
+
def [](key) = super(key.to_s)
|
62
|
+
|
63
|
+
def []=(key, value)
|
64
|
+
_assign(key.to_s, Utils.normalized_value(self, value))
|
65
|
+
end
|
66
|
+
|
67
|
+
alias_method :store, :[]=
|
68
|
+
|
69
|
+
def fetch(key, *args) = super(key.to_s, *args)
|
70
|
+
|
71
|
+
def assoc(key, *args) = super(key.to_s)
|
72
|
+
|
73
|
+
def values_at(*keys) = super(*keys.map!(&:to_s))
|
74
|
+
|
75
|
+
def fetch_values(*keys) = super(*keys.map!(&:to_s))
|
76
|
+
|
77
|
+
def method_missing(method_name, *args)
|
78
|
+
key = method_name.to_s
|
79
|
+
if key.end_with?("=")
|
80
|
+
key_chop = key.chop
|
81
|
+
self.class.define_method(key) { |value| self[key_chop] = value }
|
82
|
+
self[key.chop] = args.first
|
14
83
|
elsif self.key?(key)
|
84
|
+
self.class.define_method(key) { self[key] }
|
15
85
|
self[key]
|
16
86
|
elsif default_proc
|
87
|
+
super unless args.empty?
|
17
88
|
default_proc.call(self, key)
|
18
89
|
else
|
90
|
+
super unless args.empty?
|
19
91
|
default
|
20
92
|
end
|
21
93
|
end
|
22
94
|
|
23
|
-
def update(
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
95
|
+
def update(*other_hashes)
|
96
|
+
other_hashes.each do |other_hash|
|
97
|
+
if other_hash.is_a? HashWithDotAccess::Hash
|
98
|
+
super(other_hash)
|
99
|
+
else
|
100
|
+
other_hash.to_hash.each do |key, value|
|
101
|
+
key = key.to_s
|
102
|
+
if block_given? && key?(key)
|
103
|
+
value = yield(key, self[key], value)
|
104
|
+
end
|
105
|
+
_assign(key, Utils.normalized_value(self, value))
|
30
106
|
end
|
31
|
-
regular_writer(convert_key(key), convert_value(value))
|
32
107
|
end
|
33
|
-
self
|
34
108
|
end
|
109
|
+
|
110
|
+
self
|
35
111
|
end
|
36
112
|
|
37
|
-
|
113
|
+
alias_method :merge!, :update
|
38
114
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
115
|
+
def merge(...) = dup.update(...)
|
116
|
+
|
117
|
+
def replace(...)
|
118
|
+
clear
|
119
|
+
update(...)
|
120
|
+
end
|
121
|
+
|
122
|
+
def dig(*args)
|
123
|
+
super(args[0].to_s, *args[1..])
|
124
|
+
end
|
125
|
+
|
126
|
+
def delete(key) = super(key.to_s)
|
127
|
+
|
128
|
+
def except(*keys) = super(*keys.map!(&:to_s))
|
129
|
+
|
130
|
+
def slice(*keys) = self.class.new(super(*keys.map!(&:to_s)))
|
131
|
+
|
132
|
+
def select(...)
|
133
|
+
return to_enum(:select) unless block_given?
|
134
|
+
|
135
|
+
dup.tap { _1.select!(...) }
|
136
|
+
end
|
137
|
+
|
138
|
+
def reject(...)
|
139
|
+
return to_enum(:reject) unless block_given?
|
140
|
+
|
141
|
+
dup.tap { _1.reject!(...) }
|
142
|
+
end
|
143
|
+
|
144
|
+
def transform_values(...)
|
145
|
+
return to_enum(:transform_values) unless block_given?
|
146
|
+
|
147
|
+
dup.tap { _1.transform_values!(...) }
|
148
|
+
end
|
149
|
+
|
150
|
+
def compact
|
151
|
+
dup.tap { _1.compact! }
|
152
|
+
end
|
153
|
+
|
154
|
+
alias_method :_to_h, :to_h
|
155
|
+
|
156
|
+
def to_dot_h(...)
|
157
|
+
self.class.new _to_h(...)
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_h
|
161
|
+
::Hash.new.update(self).to_h do |k, v|
|
162
|
+
value = Utils.primitive_value(v)
|
163
|
+
k, value = yield k, value if block_given?
|
164
|
+
[k, value]
|
53
165
|
end
|
54
166
|
end
|
55
167
|
end
|
56
|
-
end
|
57
168
|
|
58
|
-
|
59
|
-
|
60
|
-
|
169
|
+
module Refinements
|
170
|
+
refine ::Hash do
|
171
|
+
def as_dots
|
172
|
+
HashWithDotAccess::Hash.new(self)
|
173
|
+
end
|
174
|
+
end
|
61
175
|
end
|
62
176
|
end
|
metadata
CHANGED
@@ -1,35 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hash_with_dot_access
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bridgetown Team
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-04-21 00:00:00.000000000 Z
|
12
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: 5.0.0
|
20
|
-
- - "<"
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: '8.0'
|
23
|
-
type: :runtime
|
24
|
-
prerelease: false
|
25
|
-
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
requirements:
|
27
|
-
- - ">="
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: 5.0.0
|
30
|
-
- - "<"
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '8.0'
|
33
13
|
- !ruby/object:Gem::Dependency
|
34
14
|
name: bundler
|
35
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,13 +38,15 @@ dependencies:
|
|
58
38
|
- - "~>"
|
59
39
|
- !ruby/object:Gem::Version
|
60
40
|
version: '13.0'
|
61
|
-
description:
|
41
|
+
description:
|
62
42
|
email: maintainers@bridgetownrb.com
|
63
43
|
executables: []
|
64
44
|
extensions: []
|
65
45
|
extra_rdoc_files: []
|
66
46
|
files:
|
67
47
|
- ".gitignore"
|
48
|
+
- ".ruby-version"
|
49
|
+
- CHANGELOG.md
|
68
50
|
- CODE_OF_CONDUCT.md
|
69
51
|
- Gemfile
|
70
52
|
- Gemfile.lock
|
@@ -78,7 +60,7 @@ homepage: https://github.com/bridgetownrb/hash_with_dot_access
|
|
78
60
|
licenses:
|
79
61
|
- MIT
|
80
62
|
metadata: {}
|
81
|
-
post_install_message:
|
63
|
+
post_install_message:
|
82
64
|
rdoc_options: []
|
83
65
|
require_paths:
|
84
66
|
- lib
|
@@ -86,15 +68,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
86
68
|
requirements:
|
87
69
|
- - ">="
|
88
70
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
71
|
+
version: 3.1.0
|
90
72
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
73
|
requirements:
|
92
74
|
- - ">="
|
93
75
|
- !ruby/object:Gem::Version
|
94
76
|
version: '0'
|
95
77
|
requirements: []
|
96
|
-
rubygems_version: 3.
|
97
|
-
signing_key:
|
78
|
+
rubygems_version: 3.3.26
|
79
|
+
signing_key:
|
98
80
|
specification_version: 4
|
99
|
-
summary:
|
81
|
+
summary: Performance-oriented subclass of Hash which provides symbolized keys and
|
82
|
+
method access
|
100
83
|
test_files: []
|