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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e56312c78a8c5fa38cac0c7f3b533ca5d5e28c69de1da5b5aa26d7f1be3c7b7e
4
- data.tar.gz: 9a5e3cf1d52fcb8f81a3b20ad645a13afe5b30cf1430dcd0649d4059f2e8781e
3
+ metadata.gz: be6e40c79e1f2c6148e84b6ef6b3521d9c07500bac9603f793a39dac7b3278ce
4
+ data.tar.gz: ace5ebdd3d951858e6dda87a96cfb9301b4ce7dc9802184cdee1cbd69e664538
5
5
  SHA512:
6
- metadata.gz: 14723cc0870582ebe4278162e890e028b0515c543069bf97efd2cd16a122f6630cadf39fb64d5c4998331afda494d8388bc5320a9f8a06f011a6870b25672646
7
- data.tar.gz: 8650367713ed20fb9b235a44d61d923244084836da9acdc8ca2125f6691cf08956741c3f0dc03f4f99e2047d1bc33f3081e2f061315e216864725fcaf118ad94
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.2.0)
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
- activesupport (7.0.0)
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.1.4
20
+ 2.5.6
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Hash with Dot Access
2
2
 
3
- Rails' ActiveSupport gem provides `HashWithIndifferentAccess` which allows you to access hash keys with either strings or symbols. This gem provides `HashWithDotAccess` which subclasses `HashWithIndifferentAccess` and allows you to access or set those keys using dot access (aka methods). It utilizes `method_missing` to access key data if available, and you can also set data using `keyname=`.
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
- hsh2 = {test: "dot access"}.with_dot_access
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}}.with_dot_access
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}.with_dot_access
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!"}.with_dot_access
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!"}.with_dot_access
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{Subclass of HashWithIndifferentAccess which provides dot access via method_missing}
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
@@ -1,3 +1,3 @@
1
1
  module HashWithDotAccess
2
- VERSION = "1.2.0"
2
+ VERSION = "2.1.0"
3
3
  end
@@ -1,62 +1,176 @@
1
- require "active_support/core_ext/hash/indifferent_access"
2
-
3
1
  module HashWithDotAccess
4
- class Hash < ActiveSupport::HashWithIndifferentAccess
5
- def respond_to_missing?(key, *)
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 method_missing(key, *args)
12
- if "#{key}".end_with?("=")
13
- self["#{key}".chop] = args.first
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(other_hash)
24
- if other_hash.is_a? HashWithDotAccess::Hash
25
- super(other_hash)
26
- else
27
- other_hash.to_hash.each_pair do |key, value|
28
- if block_given? && key?(key)
29
- value = yield(convert_key(key), self[key], value)
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
- private
113
+ alias_method :merge!, :update
38
114
 
39
- def convert_value(value, options = {}) # :doc:
40
- if value.is_a? ::Hash
41
- if options[:for] == :to_hash
42
- value.to_hash
43
- else
44
- value.with_dot_access
45
- end
46
- elsif value.is_a?(Array)
47
- if options[:for] != :assignment || value.frozen?
48
- value = value.dup
49
- end
50
- value.map! { |e| convert_value(e, options) }
51
- else
52
- value
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
- class Hash
59
- def with_dot_access
60
- HashWithDotAccess::Hash.new(self)
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.2.0
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: 2021-12-16 00:00:00.000000000 Z
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: '0'
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.1.4
97
- signing_key:
78
+ rubygems_version: 3.3.26
79
+ signing_key:
98
80
  specification_version: 4
99
- summary: Subclass of HashWithIndifferentAccess which provides dot access via method_missing
81
+ summary: Performance-oriented subclass of Hash which provides symbolized keys and
82
+ method access
100
83
  test_files: []