hash_with_dot_access 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []