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 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
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ pkg/
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,2 @@
1
+ this work is licensed under the terms of the MIT license,
2
+ see https://opensource.org/licenses/MIT
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
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.test_files = FileList['test/**/*_test.rb']
6
+ end
7
+
8
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "simple/hash"
5
+
6
+ require "irb"
7
+
8
+ IRB.start(__FILE__)
@@ -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
@@ -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: []