key_dial 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 +17 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +35 -0
- data/README.md +82 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/key_dial.gemspec +29 -0
- data/lib/key_dial.rb +37 -0
- data/lib/key_dial/key_dialler.rb +76 -0
- data/lib/key_dial/version.rb +3 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 80905cbb9d3d2375534b67233544440b17c3a9aa2e092089c304db1d7f48161e
|
4
|
+
data.tar.gz: 6fca290e125cf5cc240e7d420b9bf504c0932bb31bf916ab30cb4fe2f73e4de0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e1c107522a250b151d0ff974d5acf41712adfeba5e1294464cd689c97265d488497c385f4eb181695e607c2bd353b7250a96dba5de80e0fa60f02b3f87ba9744
|
7
|
+
data.tar.gz: 523ccc88302b8ec64ae711b91b06556bf5a29d414accee9095ee1df0675ee37e7814628d7920cc13d9c0e7e871b5583954d7c6f5f6a930591ed49c403eeb9ec5
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
key_dial (1.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.3)
|
10
|
+
rake (10.5.0)
|
11
|
+
rspec (3.8.0)
|
12
|
+
rspec-core (~> 3.8.0)
|
13
|
+
rspec-expectations (~> 3.8.0)
|
14
|
+
rspec-mocks (~> 3.8.0)
|
15
|
+
rspec-core (3.8.0)
|
16
|
+
rspec-support (~> 3.8.0)
|
17
|
+
rspec-expectations (3.8.2)
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
+
rspec-support (~> 3.8.0)
|
20
|
+
rspec-mocks (3.8.0)
|
21
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
22
|
+
rspec-support (~> 3.8.0)
|
23
|
+
rspec-support (3.8.0)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
bundler (~> 1.17)
|
30
|
+
key_dial!
|
31
|
+
rake (~> 10.0)
|
32
|
+
rspec (~> 3.0)
|
33
|
+
|
34
|
+
BUNDLED WITH
|
35
|
+
1.17.2
|
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# KeyDial (Ruby Gem)
|
2
|
+
|
3
|
+
**Avoid all errors when accessing a deeply nested Hash key,** or Array or Struct key. KeyDial goes one step beyond Ruby 2.3's `dig()` method by quietly returning `nil` (or your default) if the keys requested are invalid for any reason, and never an error.
|
4
|
+
|
5
|
+
In particular, if you try to access a key on a value that can't have keys, `dig()` will cause an error where KeyDial will not.
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
hash = {a: {b: {c: true}, d: 5}}
|
9
|
+
|
10
|
+
hash.dig( :a, :d, :c) #=> TypeError: Integer does not have #dig method
|
11
|
+
hash.call(:a, :d, :c) #=> nil
|
12
|
+
hash.call(:a, :b, :c) #=> true
|
13
|
+
```
|
14
|
+
|
15
|
+
**Bonus: you don't even need to fiddle with existing code.** If you have already written something to access a deeply nested key, just surround this with `dial` and `call` (rather than changing it to the form above as function parameters).
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
hash[:a][:d][:c] #=> TypeError: no implicit conversion of Symbol into Integer
|
19
|
+
|
20
|
+
#hash → [:a][:d][:c]
|
21
|
+
# ↓ ↓
|
22
|
+
hash.dial[:a][:d][:c].call #=> nil
|
23
|
+
```
|
24
|
+
|
25
|
+
**KeyDial will work on mixed objects**, such as structs containing arrays containing hashes. It can be called from any hash, array or struct.
|
26
|
+
|
27
|
+
## Explanation
|
28
|
+
|
29
|
+
We use the concept of placing a phone-call: you can 'dial' any set of keys regardless of whether they exist (like entering a phone number), then finally place the 'call'. If the key is invalid for any reason you get nil/default (like a wrong number); otherwise you get the value (you're connected).
|
30
|
+
|
31
|
+
This works by intermediating your request with a KeyDialler object. Trying to access keys on this object simply builds up a list of keys to use when you later place the 'call'. The call then `dig`s for the keys safely.
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
require 'hash_dial'
|
37
|
+
```
|
38
|
+
|
39
|
+
### Use it like `dig()`
|
40
|
+
|
41
|
+
If you want to follow this pattern, it works in the same way. You can't change the default return value when using this pattern.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
array = [0, {a: [true, false], b: 'foo'}, 2]
|
45
|
+
|
46
|
+
array.call(1, :a, 0) #=> true (i.e. array[1][:a][0])
|
47
|
+
array.call(1, :b, 0) #=> nil (i.e. array[1][:b][0] doesn't exist)
|
48
|
+
```
|
49
|
+
|
50
|
+
You can `call` on any Hash, Array or Struct after requiring this gem.
|
51
|
+
|
52
|
+
Note that KeyDial does not treat strings as arrays. Trying to access a key on a string will return nil or your default. (This is also how `dig()` works.)
|
53
|
+
|
54
|
+
### Use key access syntax (allows default return value)
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
hash.dial[:a][4][:c].call # Returns the value at hash[:a][4][:c] or nil
|
58
|
+
hash.dial[:a][4][:c].call('Ooops') # Returns the value at hash[:a][4][:c] or 'Ooops'
|
59
|
+
```
|
60
|
+
|
61
|
+
You can `dial` on any Hash, Array or Struct after requiring this gem.
|
62
|
+
|
63
|
+
### Use the KeyDialler object
|
64
|
+
|
65
|
+
If you don't do this all in one line, you can access the KeyDialler object should you want to manipulate it:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
dialler = KeyDial::KeyDialler.new(struct) # Returns a KeyDialler object referencing struct
|
69
|
+
dialler[:a] # Adds :a to the list of keys to dial (returns self)
|
70
|
+
dialler.dial!(:b, :c) # Longhand way of adding more keys (returns self)
|
71
|
+
dialler.undial! # Removes the last-added key (returns self)
|
72
|
+
dialler[:c][:d] # Adds two more keys (returns self)
|
73
|
+
dialler += :e # Adds yet one more (returns self)
|
74
|
+
dialler -= :a # Removes all such keys from the list (returns self)
|
75
|
+
# So far we have dialled [:b][:c][:d][:e]
|
76
|
+
dialler.call # Returns the value at struct[:b][:c][:d][:e] or nil
|
77
|
+
dialler.hangup # Returns the original keyed object by reference
|
78
|
+
```
|
79
|
+
|
80
|
+
## Note
|
81
|
+
|
82
|
+
KeyDial is a generic version of the gem HashDial, replacing and deprecating it.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "key_dial"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/key_dial.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "key_dial/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "key_dial"
|
8
|
+
spec.version = KeyDial::VERSION
|
9
|
+
spec.authors = ["Convincible"]
|
10
|
+
spec.email = ["development@convincible.media"]
|
11
|
+
|
12
|
+
spec.summary = "Access (deeply nested) Hash, Array or Struct keys. Get the value, or nil/default instead of any error. (Even safer than Ruby 2.3's dig method)."
|
13
|
+
spec.description = "Avoid all errors when accessing (deeply nested) Hash, Array or Struct keys. Safer than dig(), as will quietly return nil (or your default) if the keys requested are invalid for any reason at all. Bonus: you don't even need to fiddle with existing code. If you have already written something to access a deep key (e.g. hash[:a][:b][:c]), just surround this with '.dial' and '.call'."
|
14
|
+
spec.homepage = "https://github.com/ConvincibleMedia/ruby-gem-key_dial"
|
15
|
+
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
19
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
end
|
21
|
+
spec.bindir = "exe"
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
spec.required_ruby_version = '>= 2.3'
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
29
|
+
end
|
data/lib/key_dial.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require "key_dial/version"
|
2
|
+
require "key_dial/key_dialler"
|
3
|
+
|
4
|
+
module KeyDial
|
5
|
+
|
6
|
+
# Called on a Hash, Array or Struct, returns a KeyDialler object.
|
7
|
+
#
|
8
|
+
# @param lookup Parameters to this method form initial keys to dial. This is unnecessary but works anyway. For simplicity, dial keys by accessing them as if KeyDialler were a keyed object itself.
|
9
|
+
#
|
10
|
+
def to_dial(*lookup)
|
11
|
+
return KeyDialler.new(self, *lookup)
|
12
|
+
end
|
13
|
+
|
14
|
+
alias_method :dial, :to_dial
|
15
|
+
|
16
|
+
# Called directly on a keyed object, immediately dials and calls the keys specified as arguments. Returns the value found, or nil.
|
17
|
+
#
|
18
|
+
# @param lookup The keys to attempt to retrieve.
|
19
|
+
#
|
20
|
+
def call(*lookup)
|
21
|
+
return KeyDialler.new(self, *lookup).call
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
# Extend core class so that .dial can be called seamlessly
|
27
|
+
class Hash
|
28
|
+
include KeyDial
|
29
|
+
end
|
30
|
+
|
31
|
+
class Array
|
32
|
+
include KeyDial
|
33
|
+
end
|
34
|
+
|
35
|
+
class Struct
|
36
|
+
include KeyDial
|
37
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module KeyDial
|
2
|
+
|
3
|
+
class KeyDialler
|
4
|
+
|
5
|
+
@obj_with_keys
|
6
|
+
@lookup
|
7
|
+
@default = nil
|
8
|
+
|
9
|
+
def initialize(obj_with_keys, *lookup)
|
10
|
+
if obj_with_keys.respond_to?(:dig)
|
11
|
+
@obj_with_keys = obj_with_keys
|
12
|
+
else
|
13
|
+
raise ArgumentError.new('HashDialler must be initialized on a Hash, Array or Struct, or an object that responds to :dig.')
|
14
|
+
end
|
15
|
+
@lookup = []
|
16
|
+
if lookup.length > 0
|
17
|
+
dial!(*lookup)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Adds a key to the list of nested keys to try, one level deeper.
|
22
|
+
#
|
23
|
+
# @param keys The key to add. Multiple arguments would add multiple keys.
|
24
|
+
#
|
25
|
+
def dial!(*keys)
|
26
|
+
#unless key.is_a(Symbol) || key.is_a(String)
|
27
|
+
@lookup += keys
|
28
|
+
return self
|
29
|
+
end
|
30
|
+
|
31
|
+
# Digs into the object to the list of keys specified by dialling. Returns nil or default if specified.
|
32
|
+
#
|
33
|
+
# @param default What to return if no key is found.
|
34
|
+
#
|
35
|
+
def call(default = nil)
|
36
|
+
begin
|
37
|
+
value = @obj_with_keys.dig(*@lookup)
|
38
|
+
rescue
|
39
|
+
value = default
|
40
|
+
end
|
41
|
+
return value
|
42
|
+
end
|
43
|
+
|
44
|
+
# Return the original keyed object.
|
45
|
+
def hangup
|
46
|
+
return @obj_with_keys
|
47
|
+
end
|
48
|
+
|
49
|
+
# Remove keys from the dialling list.
|
50
|
+
#
|
51
|
+
# @param keys If specified, these keys would be removed from wherever they appear in the dialling list. Otherwise, the last added key is removed.
|
52
|
+
#
|
53
|
+
def undial!(*keys)
|
54
|
+
if keys.length > 0
|
55
|
+
@lookup -= keys
|
56
|
+
elsif @lookup.length > 0
|
57
|
+
@lookup.pop
|
58
|
+
end
|
59
|
+
return self
|
60
|
+
end
|
61
|
+
|
62
|
+
# The preferred way to build up your dialling list. Access KeyDialler as if it were a keyed object, e.g. keydialler[a][b][c]. This does not actually return any value, rather it dials those keys (awaiting a call).
|
63
|
+
#
|
64
|
+
def [](key)
|
65
|
+
return dial!(key)
|
66
|
+
end
|
67
|
+
def +(key)
|
68
|
+
return dial!(key)
|
69
|
+
end
|
70
|
+
def -(key)
|
71
|
+
return undial!(key)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: key_dial
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Convincible
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-01-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.17'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.17'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: 'Avoid all errors when accessing (deeply nested) Hash, Array or Struct
|
56
|
+
keys. Safer than dig(), as will quietly return nil (or your default) if the keys
|
57
|
+
requested are invalid for any reason at all. Bonus: you don''t even need to fiddle
|
58
|
+
with existing code. If you have already written something to access a deep key (e.g.
|
59
|
+
hash[:a][:b][:c]), just surround this with ''.dial'' and ''.call''.'
|
60
|
+
email:
|
61
|
+
- development@convincible.media
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- ".gitignore"
|
67
|
+
- ".rspec"
|
68
|
+
- ".travis.yml"
|
69
|
+
- Gemfile
|
70
|
+
- Gemfile.lock
|
71
|
+
- README.md
|
72
|
+
- Rakefile
|
73
|
+
- bin/console
|
74
|
+
- bin/setup
|
75
|
+
- key_dial.gemspec
|
76
|
+
- lib/key_dial.rb
|
77
|
+
- lib/key_dial/key_dialler.rb
|
78
|
+
- lib/key_dial/version.rb
|
79
|
+
homepage: https://github.com/ConvincibleMedia/ruby-gem-key_dial
|
80
|
+
licenses: []
|
81
|
+
metadata: {}
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '2.3'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubygems_version: 3.0.0
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: Access (deeply nested) Hash, Array or Struct keys. Get the value, or nil/default
|
101
|
+
instead of any error. (Even safer than Ruby 2.3's dig method).
|
102
|
+
test_files: []
|