simple-hash 1.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 +7 -0
- data/.gitignore +2 -0
- data/Gemfile +2 -0
- data/LICENSE +2 -0
- data/README.md +33 -0
- data/Rakefile +8 -0
- data/bin/console +8 -0
- data/lib/simple/hash.rb +36 -0
- data/lib/simple/hash_with_indifferent_access.rb +237 -0
- data/simple-hash.gemspec +14 -0
- data/test/simple/hash_test.rb +97 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 86c14cd18a17ad8363b701cbdeb19b67fa397a89261ace9efbfb259191ade56f
|
4
|
+
data.tar.gz: 88cae9a242fe2688fd637df91a6f90e455cb5c12d5106e72c9529530ca38c0d0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 35fcc9911bdbe6fd66d454488ff2e0cec18e4df5f0cb891a851223178ab7692722ab87d9a64134b261c4761320a62da5ad037846725738d3e76d4cd1b8b7e1a1
|
7
|
+
data.tar.gz: 7786b3ced8331dae799a3713ae790a1e313b90369621383ae09f97ef5df9792d59fa37b9b1cf980d65a334fcb186d54ac5b908e7d5135f1d31253c11380dbb85
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Simple::Hash
|
2
|
+
|
3
|
+
typically make JSON into object with accessors
|
4
|
+
|
5
|
+
## installation
|
6
|
+
|
7
|
+
$ gem install simple-hash
|
8
|
+
|
9
|
+
**or**
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'simple-hash'
|
13
|
+
```
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
## usage
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
user = SimpleHash.new(name: "localhostdotdev", writing?: true)
|
21
|
+
user.name # => "localhostdotdev"
|
22
|
+
user.writing? # => true
|
23
|
+
user[:name] # => "localhostdotdev"
|
24
|
+
user["name"] # => "localhostdotdev"
|
25
|
+
user.namme # => NoMethodError, did you mean? name
|
26
|
+
user.try(:nammmes) # => nil
|
27
|
+
user.keys # => [:name, :writing?]
|
28
|
+
user.values # => ["localhostdotdev", true]
|
29
|
+
|
30
|
+
# what about that?
|
31
|
+
user = SimpleHash[emails: [{ domain: "localhost.dev" }]]
|
32
|
+
user.emails.first.domain # "localhost.dev"
|
33
|
+
```
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/lib/simple/hash.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative 'hash_with_indifferent_access'
|
2
|
+
|
3
|
+
module Simple
|
4
|
+
end
|
5
|
+
|
6
|
+
class Simple::Hash < Simple::HashWithIndifferentAccess
|
7
|
+
VERSION = "1.0.0"
|
8
|
+
|
9
|
+
def method_missing(method_name, *args, &block)
|
10
|
+
if keys.map(&:to_s).include?(method_name.to_s) && args.empty? && block.nil?
|
11
|
+
fetch(method_name)
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def respond_to_missing?(method_name, include_private = false)
|
18
|
+
keys.map(&:to_s).include?(method_name.to_s) || super
|
19
|
+
end
|
20
|
+
|
21
|
+
def methods
|
22
|
+
super + keys.map(&:to_sym)
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def convert_value(value, options = {})
|
28
|
+
if value.is_a?(Hash)
|
29
|
+
Simple::Hash.new(value).freeze
|
30
|
+
elsif value.is_a?(Array)
|
31
|
+
value.map { |val| convert_value(val, options) }
|
32
|
+
else
|
33
|
+
value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
require "active_support/core_ext/hash/keys"
|
2
|
+
require "active_support/core_ext/hash/reverse_merge"
|
3
|
+
require "active_support/core_ext/hash/except"
|
4
|
+
|
5
|
+
module Simple
|
6
|
+
end
|
7
|
+
|
8
|
+
class Simple::HashWithIndifferentAccess < Hash
|
9
|
+
def extractable_options?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def with_indifferent_access
|
14
|
+
dup
|
15
|
+
end
|
16
|
+
|
17
|
+
def nested_under_indifferent_access
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(constructor = {})
|
22
|
+
if constructor.respond_to?(:to_hash)
|
23
|
+
super()
|
24
|
+
update(constructor)
|
25
|
+
|
26
|
+
hash = constructor.to_hash
|
27
|
+
self.default = hash.default if hash.default
|
28
|
+
self.default_proc = hash.default_proc if hash.default_proc
|
29
|
+
else
|
30
|
+
super(constructor)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.[](*args)
|
35
|
+
new.merge!(Hash[*args])
|
36
|
+
end
|
37
|
+
|
38
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
39
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
40
|
+
|
41
|
+
def []=(key, value)
|
42
|
+
regular_writer(convert_key(key), convert_value(value, for: :assignment))
|
43
|
+
end
|
44
|
+
|
45
|
+
alias_method :store, :[]=
|
46
|
+
|
47
|
+
def update(other_hash)
|
48
|
+
if other_hash.is_a?(Simple::HashWithIndifferentAccess)
|
49
|
+
super(other_hash)
|
50
|
+
else
|
51
|
+
other_hash.to_hash.each_pair do |key, value|
|
52
|
+
if block_given? && key?(key)
|
53
|
+
value = yield(convert_key(key), self[key], value)
|
54
|
+
end
|
55
|
+
regular_writer(convert_key(key), convert_value(value))
|
56
|
+
end
|
57
|
+
self
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
alias_method :merge!, :update
|
62
|
+
|
63
|
+
def key?(key)
|
64
|
+
super(convert_key(key))
|
65
|
+
end
|
66
|
+
|
67
|
+
alias_method :include?, :key?
|
68
|
+
alias_method :has_key?, :key?
|
69
|
+
alias_method :member?, :key?
|
70
|
+
|
71
|
+
def [](key)
|
72
|
+
convert_value(super(convert_key(key)))
|
73
|
+
end
|
74
|
+
|
75
|
+
def assoc(key)
|
76
|
+
convert_values(super(convert_key(key)))
|
77
|
+
end
|
78
|
+
|
79
|
+
def fetch(key, *extras)
|
80
|
+
convert_value(super(convert_key(key), *extras))
|
81
|
+
end
|
82
|
+
|
83
|
+
def dig(*args)
|
84
|
+
args[0] = convert_key(args[0]) if args.size > 0
|
85
|
+
convert_value(super(*args))
|
86
|
+
end
|
87
|
+
|
88
|
+
def default(*args)
|
89
|
+
super(*args.map { |arg| convert_key(arg) })
|
90
|
+
end
|
91
|
+
|
92
|
+
def values_at(*keys)
|
93
|
+
convert_values(super(*keys.map { |key| convert_key(key) }))
|
94
|
+
end
|
95
|
+
|
96
|
+
def fetch_values(*indices, &block)
|
97
|
+
convert_values(super(*indices.map { |key| convert_key(key) }, &block))
|
98
|
+
end
|
99
|
+
|
100
|
+
def dup
|
101
|
+
self.class.new(self).tap do |new_hash|
|
102
|
+
set_defaults(new_hash)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def merge(hash, &block)
|
107
|
+
dup.update(hash, &block)
|
108
|
+
end
|
109
|
+
|
110
|
+
def reverse_merge(other_hash)
|
111
|
+
super(self.class.new(other_hash))
|
112
|
+
end
|
113
|
+
|
114
|
+
alias_method :with_defaults, :reverse_merge
|
115
|
+
|
116
|
+
def reverse_merge!(other_hash)
|
117
|
+
super(self.class.new(other_hash))
|
118
|
+
end
|
119
|
+
|
120
|
+
alias_method :with_defaults!, :reverse_merge!
|
121
|
+
|
122
|
+
def replace(other_hash)
|
123
|
+
super(self.class.new(other_hash))
|
124
|
+
end
|
125
|
+
|
126
|
+
def delete(key)
|
127
|
+
super(convert_key(key))
|
128
|
+
end
|
129
|
+
|
130
|
+
def except(*keys)
|
131
|
+
slice(*self.keys - keys.map { |key| convert_key(key) })
|
132
|
+
end
|
133
|
+
|
134
|
+
alias_method :without, :except
|
135
|
+
|
136
|
+
def stringify_keys!; self end
|
137
|
+
def deep_stringify_keys!; self end
|
138
|
+
def stringify_keys; dup end
|
139
|
+
def deep_stringify_keys; dup end
|
140
|
+
undef :symbolize_keys!
|
141
|
+
undef :deep_symbolize_keys!
|
142
|
+
def symbolize_keys; to_hash.symbolize_keys! end
|
143
|
+
alias_method :to_options, :symbolize_keys
|
144
|
+
def deep_symbolize_keys; to_hash.deep_symbolize_keys! end
|
145
|
+
def to_options!; self end
|
146
|
+
|
147
|
+
def select(*args, &block)
|
148
|
+
return to_enum(:select) unless block_given?
|
149
|
+
dup.tap { |hash| hash.select!(*args, &block) }
|
150
|
+
end
|
151
|
+
|
152
|
+
def reject(*args, &block)
|
153
|
+
return to_enum(:reject) unless block_given?
|
154
|
+
dup.tap { |hash| hash.reject!(*args, &block) }
|
155
|
+
end
|
156
|
+
|
157
|
+
def transform_values(*args, &block)
|
158
|
+
return to_enum(:transform_values) unless block_given?
|
159
|
+
dup.tap { |hash| hash.transform_values!(*args, &block) }
|
160
|
+
end
|
161
|
+
|
162
|
+
def transform_keys(*args, &block)
|
163
|
+
return to_enum(:transform_keys) unless block_given?
|
164
|
+
dup.tap { |hash| hash.transform_keys!(*args, &block) }
|
165
|
+
end
|
166
|
+
|
167
|
+
def transform_keys!
|
168
|
+
return enum_for(:transform_keys!) { size } unless block_given?
|
169
|
+
keys.each do |key|
|
170
|
+
self[yield(key)] = delete(key)
|
171
|
+
end
|
172
|
+
self
|
173
|
+
end
|
174
|
+
|
175
|
+
def slice(*keys)
|
176
|
+
keys.map! { |key| convert_key(key) }
|
177
|
+
self.class.new(super)
|
178
|
+
end
|
179
|
+
|
180
|
+
def slice!(*keys)
|
181
|
+
keys.map! { |key| convert_key(key) }
|
182
|
+
super
|
183
|
+
end
|
184
|
+
|
185
|
+
def compact
|
186
|
+
dup.tap(&:compact!)
|
187
|
+
end
|
188
|
+
|
189
|
+
def to_hash
|
190
|
+
_new_hash = Hash.new
|
191
|
+
set_defaults(_new_hash)
|
192
|
+
|
193
|
+
each do |key, value|
|
194
|
+
_new_hash[key] = convert_value(value, for: :to_hash)
|
195
|
+
end
|
196
|
+
_new_hash
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
def convert_keys(keys)
|
202
|
+
keys.map { |key| convert_key(key) }
|
203
|
+
end
|
204
|
+
|
205
|
+
def convert_key(key)
|
206
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
207
|
+
end
|
208
|
+
|
209
|
+
def convert_values(values)
|
210
|
+
values.map { |value| convert_value(value) }
|
211
|
+
end
|
212
|
+
|
213
|
+
def convert_value(value, options = {})
|
214
|
+
if value.is_a? Hash
|
215
|
+
if options[:for] == :to_hash
|
216
|
+
value.to_hash
|
217
|
+
else
|
218
|
+
value.nested_under_indifferent_access
|
219
|
+
end
|
220
|
+
elsif value.is_a?(Array)
|
221
|
+
if options[:for] != :assignment || value.frozen?
|
222
|
+
value = value.dup
|
223
|
+
end
|
224
|
+
value.map! { |e| convert_value(e, options) }
|
225
|
+
else
|
226
|
+
value
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def set_defaults(target)
|
231
|
+
if default_proc
|
232
|
+
target.default_proc = default_proc.dup
|
233
|
+
else
|
234
|
+
target.default = default
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
data/simple-hash.gemspec
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative "lib/simple/hash"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "simple-hash"
|
5
|
+
spec.version = Simple::Hash::VERSION
|
6
|
+
spec.authors = ["localhostdotdev"]
|
7
|
+
spec.email = ["localhostdotdev@protonmail.com"]
|
8
|
+
spec.summary = "typically make JSON into object with accessors"
|
9
|
+
spec.homepage = "https://github.com/simple-updates/simple-hash"
|
10
|
+
spec.license = "MIT"
|
11
|
+
spec.files = `git ls-files`.split("\n")
|
12
|
+
spec.require_paths = ["lib"]
|
13
|
+
spec.add_dependency "activesupport", "~> 6.0.0beta3"
|
14
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require_relative '../../lib/simple/hash'
|
3
|
+
|
4
|
+
class TestSimpleHash < Minitest::Test
|
5
|
+
extend MiniTest::Spec::DSL
|
6
|
+
|
7
|
+
@@counter = 0
|
8
|
+
|
9
|
+
def self.it(&block)
|
10
|
+
@@counter += 1
|
11
|
+
|
12
|
+
define_method("test_#{@@counter}", &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def assert_nothing_raised(&block)
|
16
|
+
error = nil
|
17
|
+
|
18
|
+
begin
|
19
|
+
block.call
|
20
|
+
rescue => e
|
21
|
+
error = e
|
22
|
+
puts e.message
|
23
|
+
puts e.backtrace
|
24
|
+
end
|
25
|
+
|
26
|
+
assert_nil(error)
|
27
|
+
end
|
28
|
+
|
29
|
+
def assert_raises_message(message, &block)
|
30
|
+
error = nil
|
31
|
+
|
32
|
+
begin
|
33
|
+
block.call
|
34
|
+
rescue => e
|
35
|
+
error = e
|
36
|
+
end
|
37
|
+
|
38
|
+
assert_includes(error&.message.to_s, message)
|
39
|
+
end
|
40
|
+
|
41
|
+
before do
|
42
|
+
@data = { a: 1, b: [{ c: { d: 2 } }, { e: 3 }], f: { g: 4 } }
|
43
|
+
@simple_hash = Simple::Hash[@data]
|
44
|
+
end
|
45
|
+
|
46
|
+
# initialize
|
47
|
+
it { assert_nothing_raised { Simple::Hash.new(@data).b } }
|
48
|
+
it { assert_nothing_raised { Simple::Hash[@data].b } }
|
49
|
+
it { assert_nothing_raised { Simple::Hash[@data.to_a].b } }
|
50
|
+
it { assert_nothing_raised { Simple::Hash[:a, 1, :b, 2].b } }
|
51
|
+
it { assert_raises(ArgumentError) { Simple::Hash.new(:a, 1, :b, 2).b } }
|
52
|
+
|
53
|
+
# access by method
|
54
|
+
it { assert_equal(1, @simple_hash.a) }
|
55
|
+
it { assert_equal(2, @simple_hash.b[0].c.d) }
|
56
|
+
it { assert_equal(2, @simple_hash.b[0].c.d) }
|
57
|
+
it { assert_equal(3, @simple_hash.b[1].e) }
|
58
|
+
it { assert_equal(4, @simple_hash.f.g) }
|
59
|
+
|
60
|
+
# raise if no method
|
61
|
+
it { assert_raises(NoMethodError) { @simple_hash.something } }
|
62
|
+
it { assert_raises(NoMethodError) { @simple_hash.a.something } }
|
63
|
+
it { assert_raises(NoMethodError) { @simple_hash.b.first.something } }
|
64
|
+
it { assert_raises(NoMethodError) { @simple_hash.b.first.c.something } }
|
65
|
+
|
66
|
+
# access by indifferent key
|
67
|
+
it { assert_equal(1, @simple_hash["a"]) }
|
68
|
+
it { assert_equal(2, @simple_hash[:b][0].fetch("c").d) }
|
69
|
+
it { assert_equal(2, @simple_hash[:b][0][:c][:d]) }
|
70
|
+
it { assert_equal(2, @simple_hash.b[0][:c]["d"]) }
|
71
|
+
it { assert_equal(3, @simple_hash.b[1].fetch("e")) }
|
72
|
+
it { assert_equal(4, @simple_hash.fetch(:f).g) }
|
73
|
+
|
74
|
+
# can get keys/values
|
75
|
+
it { assert_equal(["a", "b", "f"], @simple_hash.keys) }
|
76
|
+
it { assert_equal(1, @simple_hash.values.first) }
|
77
|
+
it { assert_equal({ "g" => 4 }, @simple_hash.values.last) }
|
78
|
+
|
79
|
+
# did you mean
|
80
|
+
it { assert_raises_message('Did you mean? a') { @simple_hash.aa } }
|
81
|
+
it { assert_raises_message('Did you mean? b') { @simple_hash.bb } }
|
82
|
+
it { assert_raises_message('Did you mean? d') { @simple_hash.b[0].c.dd } }
|
83
|
+
|
84
|
+
# to_json
|
85
|
+
it do
|
86
|
+
require 'json'
|
87
|
+
assert_equal('{"a":1}', Simple::Hash[a: 1].to_json)
|
88
|
+
end
|
89
|
+
|
90
|
+
it do
|
91
|
+
require 'json'
|
92
|
+
assert_equal('{"a":{"b":2}}', Simple::Hash[a: { b: 2 }].to_json)
|
93
|
+
end
|
94
|
+
|
95
|
+
# can't modify deep keys
|
96
|
+
it { assert_raises { @simple_hash.b.first.c.merge!(d: 5) } }
|
97
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple-hash
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- localhostdotdev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-04-24 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: 6.0.0beta3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 6.0.0beta3
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
- localhostdotdev@protonmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".gitignore"
|
35
|
+
- Gemfile
|
36
|
+
- LICENSE
|
37
|
+
- README.md
|
38
|
+
- Rakefile
|
39
|
+
- bin/console
|
40
|
+
- lib/simple/hash.rb
|
41
|
+
- lib/simple/hash_with_indifferent_access.rb
|
42
|
+
- simple-hash.gemspec
|
43
|
+
- test/simple/hash_test.rb
|
44
|
+
homepage: https://github.com/simple-updates/simple-hash
|
45
|
+
licenses:
|
46
|
+
- MIT
|
47
|
+
metadata: {}
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubygems_version: 3.0.3
|
64
|
+
signing_key:
|
65
|
+
specification_version: 4
|
66
|
+
summary: typically make JSON into object with accessors
|
67
|
+
test_files: []
|