hash_with_dot_access 1.2.0 → 2.0.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: 66ad7a8ba3e1aaadf2526e3642d8d9b43e86602da6498e29058caa132a107630
4
+ data.tar.gz: 3225eff99492d25a661997ae4a20d95ea573c22aeb6ba6d41be8c28d4496372a
5
5
  SHA512:
6
- metadata.gz: 14723cc0870582ebe4278162e890e028b0515c543069bf97efd2cd16a122f6630cadf39fb64d5c4998331afda494d8388bc5320a9f8a06f011a6870b25672646
7
- data.tar.gz: 8650367713ed20fb9b235a44d61d923244084836da9acdc8ca2125f6691cf08956741c3f0dc03f4f99e2047d1bc33f3081e2f061315e216864725fcaf118ad94
6
+ metadata.gz: 544048deb5346dbf49460f66e32e546d4fd31532cac0fc00d89def4aee03101473295a58b77bea39d8aab5f5cfe4ecdf892c806e32493c1e560cde08f3a51776
7
+ data.tar.gz: 46c68788e0b6edf892d3d5d26a7f6b7a7c1504d553e9275e48f083cf051d5264d57cf7226cfd593b640eb7aebddc3b96e426d61de159d0f4ee874b219ff39ef2
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.1.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## [2.0.0] - 2024-04-20
4
+
5
+ - Completely rewrite internals and remove Active Support as a dependency
6
+ (this means HashWithDotAccess::Hash supports string, symbol, and dot access with zero dependencies)
7
+ - Dot access now writes accessors to the class, improving performance (`method_missing` only runs once per key/accessor, essentially)
8
+
9
+ ## [1.2.0] - 2021-12-16
10
+
11
+ - 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.0.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
@@ -27,6 +15,3 @@ DEPENDENCIES
27
15
  bundler
28
16
  hash_with_dot_access!
29
17
  rake (~> 13.0)
30
-
31
- BUNDLED WITH
32
- 2.1.4
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
@@ -63,12 +69,22 @@ Then simply require `hash_with_dot_access`:
63
69
  require "hash_with_dot_access"
64
70
  ```
65
71
 
72
+ > [!IMPORTANT]
73
+ > 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`:
74
+ > ```ruby
75
+ > class Hash
76
+ > def with_dot_access
77
+ > HashWithDotAccess::Hash.new(self)
78
+ > end
79
+ > end
80
+ > ```
81
+
66
82
  ## Caveats
67
83
 
68
84
  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
85
 
70
86
  ```ruby
71
- hsh = {each: "this won't work!"}.with_dot_access
87
+ hsh = {each: "this won't work!"}.as_dots
72
88
  hsh.each
73
89
  # => #<Enumerator: {"each"=>"this won't work!"}:each>
74
90
  #
@@ -78,7 +94,7 @@ hsh.each
78
94
  Of course, the easy fix is to simply use standard ways of accessing hash data in these cases:
79
95
 
80
96
  ```ruby
81
- hsh = {each: "this will work!"}.with_dot_access
97
+ hsh = {each: "this will work!"}.as_dots
82
98
  hsh[:each]
83
99
  # => "this will work!"
84
100
  ```
@@ -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.0.0"
3
3
  end
@@ -1,62 +1,150 @@
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
+ end
17
+
18
+ class Hash < ::Hash
19
+ class << self
20
+ undef_method :[]
21
+ end
22
+
23
+ def initialize(hsh = nil)
24
+ super
25
+ return unless hsh
26
+
27
+ update(hsh)
28
+ self.default_proc = hsh.default_proc
29
+ self.default = hsh.default
30
+ end
31
+
32
+ def respond_to_missing?(key, *args)
33
+ return false unless args.empty?
34
+
6
35
  return true if "#{key}".end_with?("=")
7
36
 
8
37
  key?(key)
9
38
  end
10
39
 
11
- def method_missing(key, *args)
12
- if "#{key}".end_with?("=")
13
- self["#{key}".chop] = args.first
40
+ def key?(key) = super(key.to_s)
41
+
42
+ alias_method :has_key?, :key?
43
+ alias_method :include?, :key?
44
+ alias_method :member?, :key?
45
+
46
+ # save previous method
47
+ alias_method :_assign, :[]=
48
+
49
+ def [](key) = super(key.to_s)
50
+
51
+ def []=(key, value)
52
+ _assign(key.to_s, Utils.normalized_value(self, value))
53
+ end
54
+
55
+ alias_method :store, :[]=
56
+
57
+ def fetch(key, *args) = super(key.to_s, *args)
58
+
59
+ def assoc(key, *args) = super(key.to_s)
60
+
61
+ def values_at(*keys) = super(*keys.map!(&:to_s))
62
+
63
+ def fetch_values(*keys) = super(*keys.map!(&:to_s))
64
+
65
+ def method_missing(method_name, *args)
66
+ key = method_name.to_s
67
+ if key.end_with?("=")
68
+ key_chop = key.chop
69
+ self.class.define_method(key) { |value| self[key_chop] = value }
70
+ self[key.chop] = args.first
14
71
  elsif self.key?(key)
72
+ self.class.define_method(key) { self[key] }
15
73
  self[key]
16
74
  elsif default_proc
75
+ super unless args.empty?
17
76
  default_proc.call(self, key)
18
77
  else
78
+ super unless args.empty?
19
79
  default
20
80
  end
21
81
  end
22
82
 
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)
83
+ def update(*other_hashes)
84
+ other_hashes.each do |other_hash|
85
+ if other_hash.is_a? HashWithDotAccess::Hash
86
+ super(other_hash)
87
+ else
88
+ other_hash.to_hash.each do |key, value|
89
+ key = key.to_s
90
+ if block_given? && key?(key)
91
+ value = yield(key, self[key], value)
92
+ end
93
+ _assign(key, Utils.normalized_value(self, value))
30
94
  end
31
- regular_writer(convert_key(key), convert_value(value))
32
95
  end
33
- self
34
96
  end
97
+
98
+ self
35
99
  end
36
100
 
37
- private
101
+ alias_method :merge!, :update
38
102
 
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
53
- end
103
+ def merge(...) = dup.update(...)
104
+
105
+ def replace(...)
106
+ clear
107
+ update(...)
108
+ end
109
+
110
+ def dig(*args)
111
+ super(args[0].to_s, *args[1..])
112
+ end
113
+
114
+ def delete(key) = super(key.to_s)
115
+
116
+ def except(*keys) = super(*keys.map!(&:to_s))
117
+
118
+ def slice(*keys) = self.class.new(super(*keys.map!(&:to_s)))
119
+
120
+ def select(...)
121
+ return to_enum(:select) unless block_given?
122
+
123
+ dup.tap { _1.select!(...) }
124
+ end
125
+
126
+ def reject(...)
127
+ return to_enum(:reject) unless block_given?
128
+
129
+ dup.tap { _1.reject!(...) }
130
+ end
131
+
132
+ def transform_values(...)
133
+ return to_enum(:transform_values) unless block_given?
134
+
135
+ dup.tap { _1.transform_values!(...) }
136
+ end
137
+
138
+ def compact
139
+ dup.tap { _1.compact! }
54
140
  end
55
141
  end
56
- end
57
142
 
58
- class Hash
59
- def with_dot_access
60
- HashWithDotAccess::Hash.new(self)
143
+ module Refinements
144
+ refine ::Hash do
145
+ def as_dots
146
+ HashWithDotAccess::Hash.new(self)
147
+ end
148
+ end
61
149
  end
62
150
  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.0.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-20 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: []