recursive-open-struct 1.0.4 → 1.1.3
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 +5 -5
- data/.travis.yml +16 -7
- data/AUTHORS.txt +8 -0
- data/CHANGELOG.md +49 -2
- data/CONTRIBUTING.md +51 -0
- data/LICENSE.txt +1 -1
- data/README.md +75 -24
- data/lib/recursive_open_struct.rb +90 -37
- data/lib/recursive_open_struct/dig.rb +22 -0
- data/lib/recursive_open_struct/version.rb +1 -1
- data/recursive-open-struct.gemspec +1 -1
- data/spec/recursive_open_struct/ostruct_2_0_0_spec.rb +9 -3
- data/spec/recursive_open_struct/ostruct_2_3_0_spec.rb +49 -0
- data/spec/recursive_open_struct/recursion_spec.rb +37 -0
- metadata +8 -6
- data/lib/recursive_open_struct/ruby_19_backport.rb +0 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3cc972f20fd1aba4f8eea6fe1766c17c7d9e663a3ed999e8375549ac37ae4b08
|
|
4
|
+
data.tar.gz: 9c0d0230d431aedeae47f0f2026d77196dfb230c3ebe9359763b4265c9a4c4d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dcdc725048e24182c562551875b2fa90d100248008af5dd4a974bb8df82f48ec5f618b3d76458d16f868b76b55263e96d3576a6d92f5ace69979beb29ae3213f
|
|
7
|
+
data.tar.gz: d67c50798fcaa7ca49003a924e57df0a22e83a5d688ef6ff90b61f462855f2b6ef8882e5873c0994558416030a9b265fe60cccfe586fd5bf9688e463d92279fe
|
data/.travis.yml
CHANGED
|
@@ -1,24 +1,33 @@
|
|
|
1
1
|
---
|
|
2
2
|
language: ruby
|
|
3
3
|
rvm:
|
|
4
|
-
#
|
|
4
|
+
# No longer supported (but test anyways)
|
|
5
5
|
- 2.0.0
|
|
6
6
|
- 2.1.10
|
|
7
|
-
- 2.2.
|
|
8
|
-
- 2.3.4
|
|
9
|
-
- 2.4.1
|
|
10
|
-
- ruby-head
|
|
7
|
+
- 2.2.10
|
|
11
8
|
- jruby-19mode
|
|
12
|
-
-
|
|
13
|
-
-
|
|
9
|
+
- 2.3.8
|
|
10
|
+
- 2.4.10
|
|
11
|
+
# Current stable supported by Travis
|
|
12
|
+
- 2.5.8
|
|
13
|
+
- 2.6.6
|
|
14
|
+
- 2.7.1
|
|
15
|
+
- jruby-9.1.9.0
|
|
16
|
+
# Future
|
|
17
|
+
- ruby-head
|
|
14
18
|
- jruby-head
|
|
19
|
+
- truffleruby-head
|
|
15
20
|
sudo: false
|
|
16
21
|
matrix:
|
|
17
22
|
allow_failures:
|
|
18
23
|
# No longer supported
|
|
19
24
|
- rvm: 2.0.0
|
|
20
25
|
- rvm: 2.1.10
|
|
26
|
+
- rvm: 2.2.10
|
|
27
|
+
- rvm: 2.3.8
|
|
28
|
+
- rvm: 2.4.10
|
|
21
29
|
- rvm: jruby-19mode
|
|
22
30
|
# Future
|
|
23
31
|
- rvm: ruby-head
|
|
24
32
|
- rvm: jruby-head
|
|
33
|
+
- rvm: truffleruby-head
|
data/AUTHORS.txt
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
Recursive-open-struct was written by these fine people:
|
|
2
2
|
|
|
3
|
+
* Ben Langfeld <ben@langfeld.me>
|
|
4
|
+
* Beni Cherniavsky-Paskin <cben@redhat.com>
|
|
3
5
|
* Cédric Felizard <cedric@felizard.fr>
|
|
6
|
+
* David Feldman <dbfeldman@gmail.com>
|
|
7
|
+
* Edward Betts <edward@4angle.com>
|
|
8
|
+
* Ewoud Kohl van Wijngaarden <ewoud@kohlvanwijngaarden.nl>
|
|
4
9
|
* Federico Aloi <federico.aloi@gmail.com>
|
|
5
10
|
* fervic <roberto@runawaybit.com>
|
|
11
|
+
* Igor Victor <gogainda@yandex.ru>
|
|
12
|
+
* Jean Boussier <jean.boussier@gmail.com>
|
|
6
13
|
* Joe Rafaniello <jrafanie@redhat.com>
|
|
7
14
|
* Kris Dekeyser <kris.dekeyser@libis.be>
|
|
8
15
|
* Matt Culpepper <matt@culpepper.co>
|
|
@@ -10,6 +17,7 @@ Recursive-open-struct was written by these fine people:
|
|
|
10
17
|
* Offirmo <offirmo.net@gmail.com>
|
|
11
18
|
* Pedro Sena <sena.pedro@gmail.com>
|
|
12
19
|
* Peter Yeremenko <peter.yeremenko@gmail.com>
|
|
20
|
+
* Pirate Praveen <praveen@debian.org>
|
|
13
21
|
* Sebastian Gaul <sebastian@mgvmedia.com>
|
|
14
22
|
* Thiago Guimaraes <thiagogsr@gmail.com>
|
|
15
23
|
* Tom Chapin <tchapin@gmail.com>
|
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,55 @@
|
|
|
1
|
+
1.1.3 / 2020/10/15
|
|
2
|
+
==================
|
|
3
|
+
|
|
4
|
+
* No longer officially supporting Ruby 2.4.x, but compatiblity continues.
|
|
5
|
+
* [#68](https://github.com/aetherknight/recursive-open-struct/pull/68): Igor
|
|
6
|
+
Victor: Add truffleruby-head to travis
|
|
7
|
+
* FIX [#67](https://github.com/aetherknight/recursive-open-struct/pull/67):
|
|
8
|
+
Jean Boussier: Support upstream changes to OpenStruct in ruby-head (Ruby
|
|
9
|
+
3.0.0-dev)
|
|
10
|
+
|
|
11
|
+
1.1.2 / 2020/06/20
|
|
12
|
+
==================
|
|
13
|
+
|
|
14
|
+
* FIX [#58](https://github.com/aetherknight/recursive-open-struct/pull/58):
|
|
15
|
+
David Feldman: Fix `[]=` so that it properly updates sub-elements
|
|
16
|
+
* [#58](https://github.com/aetherknight/recursive-open-struct/pull/58):
|
|
17
|
+
David Feldman: Make the default options configurable at the class level to
|
|
18
|
+
simplify adding additional options in subclasses
|
|
19
|
+
|
|
20
|
+
1.1.1 / 2020/03/10
|
|
21
|
+
==================
|
|
22
|
+
|
|
23
|
+
* FIX [#64](https://github.com/aetherknight/recursive-open-struct/pull/64):
|
|
24
|
+
Pirate Praveen: Support Ruby 2.7.0. `OpenStruct#modifiable` support was
|
|
25
|
+
finally dropped, and has to be replaced with `OpenStruct#modifiable?`.
|
|
26
|
+
* Made some additional changes to continue supporting pre-2.4.x Rubies,
|
|
27
|
+
including the current stable JRuby (9.1.x.x, which tracks Ruby 2.3.x for
|
|
28
|
+
features)
|
|
29
|
+
|
|
30
|
+
1.1.0 / 2018-02-03
|
|
31
|
+
==================
|
|
32
|
+
|
|
33
|
+
* NEW/FIX [#56](https://github.com/aetherknight/recursive-open-struct/issues/56):
|
|
34
|
+
Add better support for Ruby 2.3+'s `#dig` method (when it exists for the
|
|
35
|
+
current version of Ruby), so that nested Hashes are properly converted to
|
|
36
|
+
RecursiveOpenStructs. `OpenStruct#dig`'s implementation was returning Hashes
|
|
37
|
+
and does not handle `recurse_over_arrays` so ROS needs special support.
|
|
38
|
+
Thanks to maxp-edcast for reporting the issue.
|
|
39
|
+
* FIX [#55](https://github.com/aetherknight/recursive-open-struct/pull/55):
|
|
40
|
+
EdwardBetts: Fixed a typo in the documentation/comment for `#method_missing`
|
|
41
|
+
|
|
42
|
+
1.0.5 / 2017-06-21
|
|
43
|
+
==================
|
|
44
|
+
|
|
45
|
+
* FIX [#54](https://github.com/aetherknight/recursive-open-struct/pull/54):
|
|
46
|
+
Beni Cherniavsky-Paskin: Improve performance of `new_ostruct_member` by using
|
|
47
|
+
`self.singleton_class.method_defined?` instead of `self.methods.include?`
|
|
48
|
+
|
|
1
49
|
1.0.4 / 2017-04-29
|
|
2
50
|
==================
|
|
3
51
|
|
|
4
|
-
* FIX
|
|
5
|
-
[#52](https://github.com/aetherknight/recursive-open-struct/pull/52): Joe
|
|
52
|
+
* FIX [#52](https://github.com/aetherknight/recursive-open-struct/pull/52): Joe
|
|
6
53
|
Rafaniello: Improve performance of DeepDup by using Set instead of an Array
|
|
7
54
|
to track visited nodes.
|
|
8
55
|
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Contributing to recursive-open-struct
|
|
2
|
+
|
|
3
|
+
Thanks for wanting to contribute a bug or code to recursive-open-struct!
|
|
4
|
+
|
|
5
|
+
To help you out with understanding the direction and philosophy of this project
|
|
6
|
+
with regards to to new features/how it should behave (and whether to file a bug
|
|
7
|
+
report), please review the following contribution guidelines.
|
|
8
|
+
|
|
9
|
+
## ROS Feature Philosophy
|
|
10
|
+
|
|
11
|
+
Recursive-open-struct tries to be a minimal extension to the Ruby stdlib's
|
|
12
|
+
`ostruct`/OpenStruct that allows for a nested set of Hashes (and Arrays) to
|
|
13
|
+
initialize similarly structured OpenStruct-like objects. This has the benefit
|
|
14
|
+
of creating arbitrary objects whose values can be accessed with accessor
|
|
15
|
+
methods, similar to JavaScript Objects' dot-notation.
|
|
16
|
+
|
|
17
|
+
To phrase it another way, RecursiveOpenStruct tries to behave as closely as
|
|
18
|
+
possible to OpenStruct, except for the recursive functionality that it adds.
|
|
19
|
+
|
|
20
|
+
If Recursive-open-struct were to add additional features (particularly methods)
|
|
21
|
+
that are not implemented by OpenStruct, then those method names would not be
|
|
22
|
+
available for use for accessing fields with the dot-notation that OpenStruct
|
|
23
|
+
and RecursiveOpenStruct provide.
|
|
24
|
+
|
|
25
|
+
For example, OpenStruct is not (at the time this is written) a
|
|
26
|
+
subclass/specialization of Hash, so several methods implemented by Hash do not
|
|
27
|
+
work with OpenStruct (and thus Recursive OpenStruct), such as `#fetch`.
|
|
28
|
+
|
|
29
|
+
If you want to add features into RecursiveOpenStruct that would "pollute" the
|
|
30
|
+
method namespace more than OpenStruct already does, consider creating your own
|
|
31
|
+
subclass instead of submitting a code change to RecursiveOpenStruct itself.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Filing/Fixing Bugs and Requesting/Proposing New Features
|
|
35
|
+
|
|
36
|
+
For simple bug fixes, feel free to provide a pull request. This includes bugs
|
|
37
|
+
in stated features of RecursiveOpenStruct, as well as features added to
|
|
38
|
+
OpenStruct in a newer version of Ruby that RecursiveOpenStruct needs custom
|
|
39
|
+
support to handle.
|
|
40
|
+
|
|
41
|
+
For anything else (new features, bugs that you want to report, and bugs that
|
|
42
|
+
are difficult to fix), I recommend opening an issue first to discuss the
|
|
43
|
+
feature or bug. I am fairly cautious about adding new features that might cause
|
|
44
|
+
RecursiveOpenStruct's API to deviate radically from OpenStruct's (since it
|
|
45
|
+
might introduce new reserved method names), and it is useful to discuss the
|
|
46
|
+
best way to solve a problem when there are tradeoffs or imperfect solutions.
|
|
47
|
+
|
|
48
|
+
When contributing code that changes behavior or fixes bugs, please include unit
|
|
49
|
+
tests to cover the new behavior or to provide regression testing for bugs.
|
|
50
|
+
Also, treat the unit tests as documentation --- make sure they are clean,
|
|
51
|
+
clear, and concise, and well organized.
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -3,58 +3,109 @@
|
|
|
3
3
|
OpenStruct subclass that returns nested hash attributes as
|
|
4
4
|
RecursiveOpenStructs.
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
6
9
|
It allows for hashes within hashes to be called in a chain of methods:
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
```ruby
|
|
12
|
+
ros = RecursiveOpenStruct.new( { wha: { tagoo: 'siam' } } )
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
ros.wha.tagoo # => 'siam'
|
|
15
|
+
```
|
|
11
16
|
|
|
12
17
|
Also, if needed, nested hashes can still be accessed as hashes:
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
```ruby
|
|
20
|
+
ros.wha_as_a_hash # { tagoo: 'siam' }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Optional: Recurse Over Arrays
|
|
15
25
|
|
|
16
26
|
RecursiveOpenStruct can also optionally recurse across arrays, although you
|
|
17
|
-
have to explicitly enable it
|
|
27
|
+
have to explicitly enable it.
|
|
28
|
+
|
|
29
|
+
Default behavior:
|
|
30
|
+
```ruby
|
|
31
|
+
h = { :somearr => [ { name: 'a'}, { name: 'b' } ] }
|
|
18
32
|
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
ros = RecursiveOpenStruct.new(h)
|
|
34
|
+
ros.somearr # => [ { name: 'a'}, { name: 'b' } ]
|
|
35
|
+
```
|
|
21
36
|
|
|
22
|
-
|
|
23
|
-
|
|
37
|
+
Enabling `recurse_over_arrays`:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
ros = RecursiveOpenStruct.new(h, recurse_over_arrays: true )
|
|
41
|
+
|
|
42
|
+
ros.somearr[0].name # => 'a'
|
|
43
|
+
ros.somearr[1].name # => 'b'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Optional: Preserve Original Keys
|
|
24
48
|
|
|
25
49
|
Also, by default it will turn all hash keys into symbols internally:
|
|
26
50
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
51
|
+
```ruby
|
|
52
|
+
h = { 'fear' => 'is', 'the' => 'mindkiller' } }
|
|
53
|
+
ros = RecursiveOpenStruct.new(h)
|
|
54
|
+
ros.to_h # => { fear: 'is', the: 'mindkiller' }
|
|
55
|
+
```
|
|
30
56
|
|
|
31
57
|
You can preserve the original keys by enabling `:preserve_original_keys`:
|
|
32
58
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
59
|
+
```ruby
|
|
60
|
+
h = { 'fear' => 'is', 'the' => 'mindkiller' } }
|
|
61
|
+
ros = RecursiveOpenStruct.new(h, preserve_original_keys: true)
|
|
62
|
+
ros.to_h # => { 'fear' => 'is', 'the' => 'mindkiller' }
|
|
63
|
+
```
|
|
64
|
+
|
|
36
65
|
|
|
37
66
|
## Installation
|
|
38
67
|
|
|
39
68
|
Available as a gem in rubygems, the default gem repository.
|
|
40
69
|
|
|
41
|
-
If you use bundler, just
|
|
70
|
+
If you use bundler, just add recursive-open-struct to your gemfile :
|
|
42
71
|
|
|
43
|
-
|
|
72
|
+
```ruby
|
|
73
|
+
gem 'recursive-open-struct'
|
|
74
|
+
```
|
|
44
75
|
|
|
45
|
-
You may also install the gem manually
|
|
76
|
+
You may also install the gem manually:
|
|
46
77
|
|
|
47
78
|
gem install recursive-open-struct
|
|
48
79
|
|
|
80
|
+
|
|
49
81
|
## Contributing
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
82
|
+
|
|
83
|
+
If you would like to file or fix a bug, or propose a new feature, please review
|
|
84
|
+
[CONTRIBUTING](CONTRIBUTING.md) first.
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
## Supported Ruby Versions
|
|
88
|
+
|
|
89
|
+
Recursive-open-struct attempts to support just the versions of Ruby that are
|
|
90
|
+
still actively maintained. Once a given major/minor version of Ruby no longer
|
|
91
|
+
receives patches, they will no longer be supported (but recursive-open-struct
|
|
92
|
+
may still work). I usually update the travis.yml file to reflect this when
|
|
93
|
+
preparing for a new release or do some other work on recursive-open-struct.
|
|
94
|
+
|
|
95
|
+
I also try to update recursive-open-struct to support new features in
|
|
96
|
+
OpenStruct itself as new versions of Ruby are released. However, I don't
|
|
97
|
+
actively monitor the status of this, so a newer feature might not work. If you
|
|
98
|
+
encounter such a feature, please file a bug or a PR to fix it, and I will try
|
|
99
|
+
to cut a new release of recursive-open-struct quickly.
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
## SemVer Compliance
|
|
103
|
+
|
|
104
|
+
Rescursive-open-struct follows [SemVer
|
|
105
|
+
2.0](https://semver.org/spec/v2.0.0.html) for its versioning.
|
|
106
|
+
|
|
56
107
|
|
|
57
108
|
## Copyright
|
|
58
109
|
|
|
59
|
-
Copyright (c) 2009-
|
|
110
|
+
Copyright (c) 2009-2018, The Recursive-open-struct developers (given in the
|
|
60
111
|
file AUTHORS.txt). See LICENSE.txt for details.
|
|
@@ -3,56 +3,81 @@ require 'recursive_open_struct/version'
|
|
|
3
3
|
|
|
4
4
|
require 'recursive_open_struct/debug_inspect'
|
|
5
5
|
require 'recursive_open_struct/deep_dup'
|
|
6
|
-
require 'recursive_open_struct/
|
|
6
|
+
require 'recursive_open_struct/dig'
|
|
7
7
|
|
|
8
8
|
# TODO: When we care less about Rubies before 2.4.0, match OpenStruct's method
|
|
9
9
|
# names instead of doing things like aliasing `new_ostruct_member` to
|
|
10
10
|
# `new_ostruct_member!`
|
|
11
|
+
#
|
|
12
|
+
# TODO: `#*_as_a_hash` deprecated. Nested hashes can be referenced using
|
|
13
|
+
# `#to_h`.
|
|
11
14
|
|
|
12
15
|
class RecursiveOpenStruct < OpenStruct
|
|
13
|
-
include
|
|
16
|
+
include Dig if OpenStruct.public_instance_methods.include? :dig
|
|
17
|
+
|
|
18
|
+
# TODO: deprecated, possibly remove or make optional an runtime so that it
|
|
19
|
+
# doesn't normally pollute the public method namespace
|
|
14
20
|
include DebugInspect
|
|
15
21
|
|
|
16
|
-
def
|
|
22
|
+
def self.default_options
|
|
23
|
+
{
|
|
24
|
+
mutate_input_hash: false,
|
|
25
|
+
recurse_over_arrays: false,
|
|
26
|
+
preserve_original_keys: false
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize(hash=nil, passed_options={})
|
|
17
31
|
hash ||= {}
|
|
18
|
-
@recurse_over_arrays = args.fetch(:recurse_over_arrays, false)
|
|
19
|
-
@preserve_original_keys = args.fetch(:preserve_original_keys, false)
|
|
20
|
-
@deep_dup = DeepDup.new(
|
|
21
|
-
recurse_over_arrays: @recurse_over_arrays,
|
|
22
|
-
preserve_original_keys: @preserve_original_keys
|
|
23
|
-
)
|
|
24
32
|
|
|
25
|
-
@
|
|
33
|
+
@options = self.class.default_options.merge!(passed_options).freeze
|
|
34
|
+
|
|
35
|
+
@deep_dup = DeepDup.new(@options)
|
|
36
|
+
|
|
37
|
+
@table = @options[:mutate_input_hash] ? hash : @deep_dup.call(hash)
|
|
26
38
|
|
|
27
39
|
@sub_elements = {}
|
|
28
40
|
end
|
|
29
41
|
|
|
30
|
-
def initialize_copy(orig)
|
|
31
|
-
super
|
|
32
42
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
if OpenStruct.public_instance_methods.include?(:initialize_copy)
|
|
44
|
+
def initialize_copy(orig)
|
|
45
|
+
super
|
|
46
|
+
|
|
47
|
+
# deep copy the table to separate the two objects
|
|
48
|
+
@table = @deep_dup.call(@table)
|
|
49
|
+
# Forget any memoized sub-elements
|
|
50
|
+
@sub_elements = {}
|
|
51
|
+
end
|
|
37
52
|
end
|
|
38
53
|
|
|
39
54
|
def to_h
|
|
40
55
|
@deep_dup.call(@table)
|
|
41
56
|
end
|
|
42
57
|
|
|
58
|
+
# TODO: deprecated, unsupported by OpenStruct. OpenStruct does not consider
|
|
59
|
+
# itself to be a "kind of" Hash.
|
|
43
60
|
alias_method :to_hash, :to_h
|
|
44
61
|
|
|
62
|
+
# Continue supporting older rubies -- JRuby 9.1.x.x is still considered
|
|
63
|
+
# stable, but is based on Ruby
|
|
64
|
+
# 2.3.x and so uses :modifiable instead of :modifiable?. Furthermore, if
|
|
65
|
+
# :modifiable is private, then make :modifiable? private too.
|
|
66
|
+
if !OpenStruct.private_instance_methods.include?(:modifiable?)
|
|
67
|
+
if OpenStruct.private_instance_methods.include?(:modifiable)
|
|
68
|
+
alias_method :modifiable?, :modifiable
|
|
69
|
+
elsif OpenStruct.public_instance_methods.include?(:modifiable)
|
|
70
|
+
alias_method :modifiable?, :modifiable
|
|
71
|
+
private :modifiable?
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
45
75
|
def [](name)
|
|
46
76
|
key_name = _get_key_from_table_(name)
|
|
47
77
|
v = @table[key_name]
|
|
48
78
|
if v.is_a?(Hash)
|
|
49
|
-
@sub_elements[key_name] ||=
|
|
50
|
-
|
|
51
|
-
recurse_over_arrays: @recurse_over_arrays,
|
|
52
|
-
preserve_original_keys: @preserve_original_keys,
|
|
53
|
-
mutate_input_hash: true
|
|
54
|
-
)
|
|
55
|
-
elsif v.is_a?(Array) and @recurse_over_arrays
|
|
79
|
+
@sub_elements[key_name] ||= _create_sub_element_(v, mutate_input_hash: true)
|
|
80
|
+
elsif v.is_a?(Array) and @options[:recurse_over_arrays]
|
|
56
81
|
@sub_elements[key_name] ||= recurse_over_array(v)
|
|
57
82
|
@sub_elements[key_name] = recurse_over_array(@sub_elements[key_name])
|
|
58
83
|
else
|
|
@@ -60,29 +85,45 @@ class RecursiveOpenStruct < OpenStruct
|
|
|
60
85
|
end
|
|
61
86
|
end
|
|
62
87
|
|
|
88
|
+
if private_instance_methods.include?(:modifiable?) || public_instance_methods.include?(:modifiable?)
|
|
89
|
+
def []=(name, value)
|
|
90
|
+
key_name = _get_key_from_table_(name)
|
|
91
|
+
tbl = modifiable? # Ensure we are modifiable
|
|
92
|
+
@sub_elements.delete(key_name)
|
|
93
|
+
tbl[key_name] = value
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
def []=(name, value)
|
|
97
|
+
key_name = _get_key_from_table_(name)
|
|
98
|
+
@table[key_name] = value # raises if self is frozen in Ruby 3.0
|
|
99
|
+
@sub_elements.delete(key_name)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
63
103
|
# Makes sure ROS responds as expected on #respond_to? and #method requests
|
|
64
104
|
def respond_to_missing?(mid, include_private = false)
|
|
65
105
|
mname = _get_key_from_table_(mid.to_s.chomp('=').chomp('_as_a_hash'))
|
|
66
106
|
@table.key?(mname) || super
|
|
67
107
|
end
|
|
68
108
|
|
|
69
|
-
# Adapted implementation of method_missing to
|
|
70
|
-
#
|
|
71
|
-
# TODO: Use modifiable? instead of modifiable, and new_ostruct_member!
|
|
72
|
-
# instead of new_ostruct_member once we care less about Rubies before 2.4.0.
|
|
109
|
+
# Adapted implementation of method_missing to accommodate the differences
|
|
110
|
+
# between ROS and OS.
|
|
73
111
|
def method_missing(mid, *args)
|
|
74
112
|
len = args.length
|
|
75
113
|
if mid =~ /^(.*)=$/
|
|
76
114
|
if len != 1
|
|
77
115
|
raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
|
|
78
116
|
end
|
|
79
|
-
|
|
117
|
+
# self[$1.to_sym] = args[0]
|
|
118
|
+
# modifiable?[new_ostruct_member!($1.to_sym)] = args[0]
|
|
119
|
+
new_ostruct_member!($1.to_sym)
|
|
120
|
+
public_send(mid, args[0])
|
|
80
121
|
elsif len == 0
|
|
81
122
|
key = mid
|
|
82
123
|
key = $1 if key =~ /^(.*)_as_a_hash$/
|
|
83
124
|
if @table.key?(_get_key_from_table_(key))
|
|
84
125
|
new_ostruct_member!(key)
|
|
85
|
-
|
|
126
|
+
public_send(mid)
|
|
86
127
|
end
|
|
87
128
|
else
|
|
88
129
|
err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
|
|
@@ -95,14 +136,13 @@ class RecursiveOpenStruct < OpenStruct
|
|
|
95
136
|
# 2.4.0.
|
|
96
137
|
def new_ostruct_member(name)
|
|
97
138
|
key_name = _get_key_from_table_(name)
|
|
98
|
-
unless self.
|
|
139
|
+
unless self.singleton_class.method_defined?(name.to_sym)
|
|
99
140
|
class << self; self; end.class_eval do
|
|
100
141
|
define_method(name) do
|
|
101
142
|
self[key_name]
|
|
102
143
|
end
|
|
103
144
|
define_method("#{name}=") do |x|
|
|
104
|
-
|
|
105
|
-
modifiable[key_name] = x
|
|
145
|
+
self[key_name] = x
|
|
106
146
|
end
|
|
107
147
|
define_method("#{name}_as_a_hash") { @table[key_name] }
|
|
108
148
|
end
|
|
@@ -113,7 +153,7 @@ class RecursiveOpenStruct < OpenStruct
|
|
|
113
153
|
# Support Ruby 2.4.0+'s changes in a way that doesn't require dynamically
|
|
114
154
|
# modifying ROS.
|
|
115
155
|
#
|
|
116
|
-
# TODO: Once we care less about Rubies before 2.4.0, reverse this
|
|
156
|
+
# TODO: Once we care less about Rubies before 2.4.0, reverse this so that
|
|
117
157
|
# new_ostruct_member points to our version and not OpenStruct's.
|
|
118
158
|
alias new_ostruct_member! new_ostruct_member
|
|
119
159
|
# new_ostruct_member! is private, but new_ostruct_member is not on OpenStruct in 2.4.0-rc1?!
|
|
@@ -122,23 +162,36 @@ class RecursiveOpenStruct < OpenStruct
|
|
|
122
162
|
def delete_field(name)
|
|
123
163
|
sym = _get_key_from_table_(name)
|
|
124
164
|
singleton_class.__send__(:remove_method, sym, "#{sym}=") rescue NoMethodError # ignore if methods not yet generated.
|
|
125
|
-
@sub_elements.delete
|
|
126
|
-
@table.delete
|
|
165
|
+
@sub_elements.delete(sym)
|
|
166
|
+
@table.delete(sym)
|
|
127
167
|
end
|
|
128
168
|
|
|
129
169
|
private
|
|
130
170
|
|
|
171
|
+
unless OpenStruct.public_instance_methods.include?(:initialize_copy)
|
|
172
|
+
def initialize_dup(orig)
|
|
173
|
+
super
|
|
174
|
+
# deep copy the table to separate the two objects
|
|
175
|
+
@table = @deep_dup.call(@table)
|
|
176
|
+
# Forget any memoized sub-elements
|
|
177
|
+
@sub_elements = {}
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
131
181
|
def _get_key_from_table_(name)
|
|
132
182
|
return name.to_s if @table.has_key?(name.to_s)
|
|
133
183
|
return name.to_sym if @table.has_key?(name.to_sym)
|
|
134
184
|
name
|
|
135
185
|
end
|
|
136
186
|
|
|
187
|
+
def _create_sub_element_(hash, **overrides)
|
|
188
|
+
self.class.new(hash, @options.merge(overrides))
|
|
189
|
+
end
|
|
190
|
+
|
|
137
191
|
def recurse_over_array(array)
|
|
138
192
|
array.each_with_index do |a, i|
|
|
139
193
|
if a.is_a? Hash
|
|
140
|
-
array[i] =
|
|
141
|
-
:mutate_input_hash => true, :preserve_original_keys => @preserve_original_keys)
|
|
194
|
+
array[i] = _create_sub_element_(a, mutate_input_hash: true, recurse_over_arrays: true)
|
|
142
195
|
elsif a.is_a? Array
|
|
143
196
|
array[i] = recurse_over_array a
|
|
144
197
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class RecursiveOpenStruct < OpenStruct
|
|
2
|
+
module Dig
|
|
3
|
+
|
|
4
|
+
# Replaces +OpenStruct#dig+ to properly support treating nested values as
|
|
5
|
+
# RecursiveOpenStructs instead of returning the nested Hashes.
|
|
6
|
+
def dig(name, *names)
|
|
7
|
+
begin
|
|
8
|
+
name = name.to_sym
|
|
9
|
+
rescue NoMethodError
|
|
10
|
+
raise TypeError, "#{name} is not a symbol nor a string"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
name_val = self[name]
|
|
14
|
+
|
|
15
|
+
if names.length > 0 && name_val.respond_to?(:dig)
|
|
16
|
+
name_val.dig(*names)
|
|
17
|
+
else
|
|
18
|
+
name_val
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
|
|
|
8
8
|
s.authors = ["William (B.J.) Snow Orvis"]
|
|
9
9
|
s.email = "aetherknight@gmail.com"
|
|
10
10
|
s.date = Time.now.utc.strftime("%Y-%m-%d")
|
|
11
|
-
s.homepage = "
|
|
11
|
+
s.homepage = "https://github.com/aetherknight/recursive-open-struct"
|
|
12
12
|
s.licenses = ["MIT"]
|
|
13
13
|
|
|
14
14
|
s.summary = "OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs"
|
|
@@ -6,7 +6,7 @@ describe RecursiveOpenStruct do
|
|
|
6
6
|
let(:hash) { {:foo => 'foo', 'bar' => :bar} }
|
|
7
7
|
subject(:ros) { RecursiveOpenStruct.new(hash) }
|
|
8
8
|
|
|
9
|
-
describe "OpenStruct 2.0 methods" do
|
|
9
|
+
describe "OpenStruct 2.0+ methods" do
|
|
10
10
|
|
|
11
11
|
context "Hash style setter" do
|
|
12
12
|
|
|
@@ -96,10 +96,16 @@ describe RecursiveOpenStruct do
|
|
|
96
96
|
|
|
97
97
|
context "each_pair" do
|
|
98
98
|
it "iterates over hash keys, with keys as symbol" do
|
|
99
|
-
|
|
99
|
+
ros_pairs = []
|
|
100
|
+
ros.each_pair {|k,v| ros_pairs << [k,v]}
|
|
101
|
+
|
|
102
|
+
hash_pairs = []
|
|
103
|
+
{:foo => 'foo', :bar => :bar}.each_pair {|k,v| hash_pairs << [k,v]}
|
|
104
|
+
|
|
105
|
+
expect(ros_pairs).to match (hash_pairs)
|
|
100
106
|
end
|
|
101
107
|
end
|
|
102
108
|
|
|
103
|
-
end
|
|
109
|
+
end # describe OpenStruct 2.0+ methods
|
|
104
110
|
|
|
105
111
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require_relative '../spec_helper'
|
|
2
|
+
require 'recursive_open_struct'
|
|
3
|
+
|
|
4
|
+
describe RecursiveOpenStruct do
|
|
5
|
+
describe "OpenStruct 2.3.0+ methods" do
|
|
6
|
+
describe "#dig" do
|
|
7
|
+
# We only care when Ruby supports `#dig`.
|
|
8
|
+
if OpenStruct.public_instance_methods.include? :dig
|
|
9
|
+
context "recurse_over_arrays: false" do
|
|
10
|
+
subject { RecursiveOpenStruct.new(a: { b: 2, c: ["doo", "bee", { inner: "one"}]}) }
|
|
11
|
+
|
|
12
|
+
describe "OpenStruct-like behavior" do
|
|
13
|
+
it { expect(subject.dig(:a, :b)).to eq 2 }
|
|
14
|
+
it { expect(subject.dig(:a, :c, 0)).to eq "doo" }
|
|
15
|
+
it { expect(subject.dig(:a, :c, 2, :inner)).to eq "one" }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "recursive behavior" do
|
|
19
|
+
it {
|
|
20
|
+
expect(subject.dig(:a)).to eq RecursiveOpenStruct.new(
|
|
21
|
+
{ b: 2, c: ["doo", "bee", { inner: "one"}]}
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
it { expect(subject.dig(:a, :c, 2)).to eq({inner: "one"}) }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context "recurse_over_arrays: true" do
|
|
29
|
+
subject { RecursiveOpenStruct.new({a: { b: 2, c: ["doo", "bee", { inner: "one"}]}}, recurse_over_arrays: true) }
|
|
30
|
+
|
|
31
|
+
describe "OpenStruct-like behavior" do
|
|
32
|
+
it { expect(subject.dig(:a, :b)).to eq 2 }
|
|
33
|
+
it { expect(subject.dig(:a, :c, 0)).to eq "doo" }
|
|
34
|
+
it { expect(subject.dig(:a, :c, 2, :inner)).to eq "one" }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe "recursive behavior" do
|
|
38
|
+
it {
|
|
39
|
+
expect(subject.dig(:a)).to eq RecursiveOpenStruct.new(
|
|
40
|
+
{ b: 2, c: ["doo", "bee", { inner: "one"}]}
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
it { expect(subject.dig(:a, :c, 2)).to eq RecursiveOpenStruct.new(inner: "one") }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end # describe #dig
|
|
48
|
+
end # describe OpenStruct 2.3+ methods
|
|
49
|
+
end
|
|
@@ -28,6 +28,15 @@ describe RecursiveOpenStruct do
|
|
|
28
28
|
expect(subject.blah_as_a_hash).to eq({ :another => 'value' })
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
it "handles sub-element replacement with dotted notation before member setup" do
|
|
32
|
+
expect(ros[:blah][:another]).to eql 'value'
|
|
33
|
+
expect(ros.methods).not_to include(:blah)
|
|
34
|
+
|
|
35
|
+
ros.blah = { changed: 'backing' }
|
|
36
|
+
|
|
37
|
+
expect(ros.blah.changed).to eql 'backing'
|
|
38
|
+
end
|
|
39
|
+
|
|
31
40
|
describe "handling loops in the original Hashes" do
|
|
32
41
|
let(:h1) { { :a => 'a'} }
|
|
33
42
|
let(:h2) { { :a => 'b', :h1 => h1 } }
|
|
@@ -55,6 +64,34 @@ describe RecursiveOpenStruct do
|
|
|
55
64
|
expect(ros.blah.blargh).to eq "Janet"
|
|
56
65
|
end
|
|
57
66
|
|
|
67
|
+
describe 'subscript mutation notation' do
|
|
68
|
+
it 'handles the basic case' do
|
|
69
|
+
subject[:blah] = 12345
|
|
70
|
+
expect(subject.blah).to eql 12345
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'recurses properly' do
|
|
74
|
+
subject[:blah][:another] = 'abc'
|
|
75
|
+
expect(subject.blah.another).to eql 'abc'
|
|
76
|
+
expect(subject.blah_as_a_hash).to eql({ :another => 'abc' })
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
let(:diff){ { :different => 'thing' } }
|
|
80
|
+
|
|
81
|
+
it 'can replace the entire hash' do
|
|
82
|
+
expect(subject.to_h).to eql(h)
|
|
83
|
+
subject[:blah] = diff
|
|
84
|
+
expect(subject.to_h).to eql({ :blah => diff })
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'updates sub-element cache' do
|
|
88
|
+
expect(subject.blah.different).to be_nil
|
|
89
|
+
subject[:blah] = diff
|
|
90
|
+
expect(subject.blah.different).to eql 'thing'
|
|
91
|
+
expect(subject.blah_as_a_hash).to eql(diff)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
58
95
|
context "after a sub-element has been modified" do
|
|
59
96
|
let(:hash) do
|
|
60
97
|
{ :blah => { :blargh => "Brad" }, :some_array => [ 1, 2, 3] }
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: recursive-open-struct
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- William (B.J.) Snow Orvis
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2020-10-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -119,6 +119,7 @@ files:
|
|
|
119
119
|
- ".travis.yml"
|
|
120
120
|
- AUTHORS.txt
|
|
121
121
|
- CHANGELOG.md
|
|
122
|
+
- CONTRIBUTING.md
|
|
122
123
|
- Gemfile
|
|
123
124
|
- LICENSE.txt
|
|
124
125
|
- README.md
|
|
@@ -127,17 +128,18 @@ files:
|
|
|
127
128
|
- lib/recursive_open_struct.rb
|
|
128
129
|
- lib/recursive_open_struct/debug_inspect.rb
|
|
129
130
|
- lib/recursive_open_struct/deep_dup.rb
|
|
130
|
-
- lib/recursive_open_struct/
|
|
131
|
+
- lib/recursive_open_struct/dig.rb
|
|
131
132
|
- lib/recursive_open_struct/version.rb
|
|
132
133
|
- recursive-open-struct.gemspec
|
|
133
134
|
- spec/recursive_open_struct/debug_inspect_spec.rb
|
|
134
135
|
- spec/recursive_open_struct/indifferent_access_spec.rb
|
|
135
136
|
- spec/recursive_open_struct/open_struct_behavior_spec.rb
|
|
136
137
|
- spec/recursive_open_struct/ostruct_2_0_0_spec.rb
|
|
138
|
+
- spec/recursive_open_struct/ostruct_2_3_0_spec.rb
|
|
137
139
|
- spec/recursive_open_struct/recursion_and_subclassing_spec.rb
|
|
138
140
|
- spec/recursive_open_struct/recursion_spec.rb
|
|
139
141
|
- spec/spec_helper.rb
|
|
140
|
-
homepage:
|
|
142
|
+
homepage: https://github.com/aetherknight/recursive-open-struct
|
|
141
143
|
licenses:
|
|
142
144
|
- MIT
|
|
143
145
|
metadata: {}
|
|
@@ -156,8 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
156
158
|
- !ruby/object:Gem::Version
|
|
157
159
|
version: '0'
|
|
158
160
|
requirements: []
|
|
159
|
-
|
|
160
|
-
rubygems_version: 2.6.8
|
|
161
|
+
rubygems_version: 3.1.2
|
|
161
162
|
signing_key:
|
|
162
163
|
specification_version: 4
|
|
163
164
|
summary: OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs
|
|
@@ -166,6 +167,7 @@ test_files:
|
|
|
166
167
|
- spec/recursive_open_struct/indifferent_access_spec.rb
|
|
167
168
|
- spec/recursive_open_struct/open_struct_behavior_spec.rb
|
|
168
169
|
- spec/recursive_open_struct/ostruct_2_0_0_spec.rb
|
|
170
|
+
- spec/recursive_open_struct/ostruct_2_3_0_spec.rb
|
|
169
171
|
- spec/recursive_open_struct/recursion_and_subclassing_spec.rb
|
|
170
172
|
- spec/recursive_open_struct/recursion_spec.rb
|
|
171
173
|
- spec/spec_helper.rb
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
module RecursiveOpenStruct::Ruby19Backport
|
|
2
|
-
# Apply fix if necessary:
|
|
3
|
-
# https://github.com/ruby/ruby/commit/2d952c6d16ffe06a28bb1007e2cd1410c3db2d58
|
|
4
|
-
def initialize_copy(orig)
|
|
5
|
-
super
|
|
6
|
-
@table.each_key{|key| new_ostruct_member(key)}
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def []=(name, value)
|
|
10
|
-
modifiable[new_ostruct_member(name)] = value
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def eql?(other)
|
|
14
|
-
return false unless other.kind_of?(OpenStruct)
|
|
15
|
-
@table.eql?(other.table)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def hash
|
|
19
|
-
@table.hash
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def each_pair
|
|
23
|
-
return to_enum(:each_pair) { @table.size } unless block_given?
|
|
24
|
-
@table.each_pair{|p| yield p}
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|