recursive-open-struct 1.0.3 → 1.1.2
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 +13 -6
- data/AUTHORS.txt +7 -0
- data/CHANGELOG.md +45 -0
- data/CONTRIBUTING.md +51 -0
- data/LICENSE.txt +1 -1
- data/README.md +75 -24
- data/lib/recursive_open_struct.rb +61 -31
- data/lib/recursive_open_struct/deep_dup.rb +2 -1
- 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 +23 -21
- 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: f4474b5a862c6ea4da4f2c930664880ca70a01f75c077a2e9952fd80ed2d26da
|
4
|
+
data.tar.gz: dcea4c80ebe98a5f91f9ea40b2b7e82cc219b259eae83b62a00329f8075cc85a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8698402e67013a209a3434235e26201550c2249db8ae82480ec2208bdd0c83fb6524be046ecb4af3ed8cfe09adebd513a383d56b8b40d91f2c973fcd1f18196
|
7
|
+
data.tar.gz: a15827f31947ca85607086b9cda61ab18b4421bb310dd3034ca4dd39ef43ecf5215d599103cb897152924d6faa1ae94e0bc800cf197c5e96ab50fc794c547a2c
|
data/.travis.yml
CHANGED
@@ -1,16 +1,21 @@
|
|
1
1
|
---
|
2
2
|
language: ruby
|
3
3
|
rvm:
|
4
|
+
# No longer supported
|
4
5
|
# - 1.9.3 # json gem now requires Ruby ~> 2.0
|
5
6
|
- 2.0.0
|
6
7
|
- 2.1.10
|
7
|
-
- 2.2.
|
8
|
-
- 2.3.4
|
9
|
-
- 2.4.1
|
10
|
-
- ruby-head
|
8
|
+
- 2.2.10
|
11
9
|
- jruby-19mode
|
12
|
-
|
13
|
-
-
|
10
|
+
# Current stable supported by Travis
|
11
|
+
- 2.3.8
|
12
|
+
- 2.4.9
|
13
|
+
- 2.5.7
|
14
|
+
- 2.6.5
|
15
|
+
- 2.7.0
|
16
|
+
- jruby-9.1.9.0
|
17
|
+
# Future
|
18
|
+
- ruby-head
|
14
19
|
- jruby-head
|
15
20
|
sudo: false
|
16
21
|
matrix:
|
@@ -18,6 +23,8 @@ matrix:
|
|
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
|
21
28
|
- rvm: jruby-19mode
|
22
29
|
# Future
|
23
30
|
- rvm: ruby-head
|
data/AUTHORS.txt
CHANGED
@@ -1,14 +1,21 @@
|
|
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
|
+
* Joe Rafaniello <jrafanie@redhat.com>
|
6
12
|
* Kris Dekeyser <kris.dekeyser@libis.be>
|
7
13
|
* Matt Culpepper <matt@culpepper.co>
|
8
14
|
* Matthew O'Riordan <matthew.oriordan@gmail.com>
|
9
15
|
* Offirmo <offirmo.net@gmail.com>
|
10
16
|
* Pedro Sena <sena.pedro@gmail.com>
|
11
17
|
* Peter Yeremenko <peter.yeremenko@gmail.com>
|
18
|
+
* Pirate Praveen <praveen@debian.org>
|
12
19
|
* Sebastian Gaul <sebastian@mgvmedia.com>
|
13
20
|
* Thiago Guimaraes <thiagogsr@gmail.com>
|
14
21
|
* Tom Chapin <tchapin@gmail.com>
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,48 @@
|
|
1
|
+
1.1.2 / 2020/06/20
|
2
|
+
==================
|
3
|
+
|
4
|
+
* FIX [#58](https://github.com/aetherknight/recursive-open-struct/pull/58):
|
5
|
+
David Feldman: Fix `[]=` so that it properly updates sub-elements
|
6
|
+
* [#58](https://github.com/aetherknight/recursive-open-struct/pull/58):
|
7
|
+
David Feldman: Make the default options configurable at the class level to
|
8
|
+
simplify adding additional options in subclasses
|
9
|
+
|
10
|
+
1.1.1 / 2020/03/10
|
11
|
+
==================
|
12
|
+
|
13
|
+
* FIX [#64](https://github.com/aetherknight/recursive-open-struct/pull/64):
|
14
|
+
Pirate Praveen: Support Ruby 2.7.0. `OpenStruct#modifiable` support was
|
15
|
+
finally dropped, and has to be replaced with `OpenStruct#modifiable?`.
|
16
|
+
* Made some additional changes to continue supporting pre-2.4.x Rubies,
|
17
|
+
including the current stable JRuby (9.1.x.x, which tracks Ruby 2.3.x for
|
18
|
+
features)
|
19
|
+
|
20
|
+
1.1.0 / 2018-02-03
|
21
|
+
==================
|
22
|
+
|
23
|
+
* NEW/FIX [#56](https://github.com/aetherknight/recursive-open-struct/issues/56):
|
24
|
+
Add better support for Ruby 2.3+'s `#dig` method (when it exists for the
|
25
|
+
current version of Ruby), so that nested Hashes are properly converted to
|
26
|
+
RecursiveOpenStructs. `OpenStruct#dig`'s implementation was returning Hashes
|
27
|
+
and does not handle `recurse_over_arrays` so ROS needs special support.
|
28
|
+
Thanks to maxp-edcast for reporting the issue.
|
29
|
+
* FIX [#55](https://github.com/aetherknight/recursive-open-struct/pull/55):
|
30
|
+
EdwardBetts: Fixed a typo in the documentation/comment for `#method_missing`
|
31
|
+
|
32
|
+
1.0.5 / 2017-06-21
|
33
|
+
==================
|
34
|
+
|
35
|
+
* FIX [#54](https://github.com/aetherknight/recursive-open-struct/pull/54):
|
36
|
+
Beni Cherniavsky-Paskin: Improve performance of `new_ostruct_member` by using
|
37
|
+
`self.singleton_class.method_defined?` instead of `self.methods.include?`
|
38
|
+
|
39
|
+
1.0.4 / 2017-04-29
|
40
|
+
==================
|
41
|
+
|
42
|
+
* FIX [#52](https://github.com/aetherknight/recursive-open-struct/pull/52): Joe
|
43
|
+
Rafaniello: Improve performance of DeepDup by using Set instead of an Array
|
44
|
+
to track visited nodes.
|
45
|
+
|
1
46
|
1.0.3 / 2017-04-10
|
2
47
|
==================
|
3
48
|
|
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,26 +3,38 @@ 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
|
@@ -40,19 +52,16 @@ class RecursiveOpenStruct < OpenStruct
|
|
40
52
|
@deep_dup.call(@table)
|
41
53
|
end
|
42
54
|
|
55
|
+
# TODO: deprecated, unsupported by OpenStruct. OpenStruct does not consider
|
56
|
+
# itself to be a "kind of" Hash.
|
43
57
|
alias_method :to_hash, :to_h
|
44
58
|
|
45
59
|
def [](name)
|
46
60
|
key_name = _get_key_from_table_(name)
|
47
61
|
v = @table[key_name]
|
48
62
|
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
|
63
|
+
@sub_elements[key_name] ||= _create_sub_element_(v, mutate_input_hash: true)
|
64
|
+
elsif v.is_a?(Array) and @options[:recurse_over_arrays]
|
56
65
|
@sub_elements[key_name] ||= recurse_over_array(v)
|
57
66
|
@sub_elements[key_name] = recurse_over_array(@sub_elements[key_name])
|
58
67
|
else
|
@@ -60,29 +69,48 @@ class RecursiveOpenStruct < OpenStruct
|
|
60
69
|
end
|
61
70
|
end
|
62
71
|
|
72
|
+
def []=(name, value)
|
73
|
+
key_name = _get_key_from_table_(name)
|
74
|
+
tbl = modifiable? # Ensure we are modifiable
|
75
|
+
@sub_elements.delete(key_name)
|
76
|
+
tbl[key_name] = value
|
77
|
+
end
|
78
|
+
|
63
79
|
# Makes sure ROS responds as expected on #respond_to? and #method requests
|
64
80
|
def respond_to_missing?(mid, include_private = false)
|
65
81
|
mname = _get_key_from_table_(mid.to_s.chomp('=').chomp('_as_a_hash'))
|
66
82
|
@table.key?(mname) || super
|
67
83
|
end
|
68
84
|
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
85
|
+
# Continue supporting older rubies -- JRuby 9.1.x.x is still considered
|
86
|
+
# stable, but is based on Ruby
|
87
|
+
# 2.3.x and so uses :modifiable instead of :modifiable?. Furthermore, if
|
88
|
+
# :modifiable is private, then make :modifiable? private too.
|
89
|
+
if !OpenStruct.private_instance_methods.include?(:modifiable?)
|
90
|
+
alias_method :modifiable?, :modifiable
|
91
|
+
if OpenStruct.private_instance_methods.include?(:modifiable)
|
92
|
+
private :modifiable?
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Adapted implementation of method_missing to accommodate the differences
|
97
|
+
# between ROS and OS.
|
73
98
|
def method_missing(mid, *args)
|
74
99
|
len = args.length
|
75
100
|
if mid =~ /^(.*)=$/
|
76
101
|
if len != 1
|
77
102
|
raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
|
78
103
|
end
|
79
|
-
|
104
|
+
# self[$1.to_sym] = args[0]
|
105
|
+
# modifiable?[new_ostruct_member!($1.to_sym)] = args[0]
|
106
|
+
new_ostruct_member!($1.to_sym)
|
107
|
+
public_send(mid, args[0])
|
80
108
|
elsif len == 0
|
81
109
|
key = mid
|
82
110
|
key = $1 if key =~ /^(.*)_as_a_hash$/
|
83
111
|
if @table.key?(_get_key_from_table_(key))
|
84
112
|
new_ostruct_member!(key)
|
85
|
-
|
113
|
+
public_send(mid)
|
86
114
|
end
|
87
115
|
else
|
88
116
|
err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
|
@@ -95,14 +123,13 @@ class RecursiveOpenStruct < OpenStruct
|
|
95
123
|
# 2.4.0.
|
96
124
|
def new_ostruct_member(name)
|
97
125
|
key_name = _get_key_from_table_(name)
|
98
|
-
unless self.
|
126
|
+
unless self.singleton_class.method_defined?(name.to_sym)
|
99
127
|
class << self; self; end.class_eval do
|
100
128
|
define_method(name) do
|
101
129
|
self[key_name]
|
102
130
|
end
|
103
131
|
define_method("#{name}=") do |x|
|
104
|
-
|
105
|
-
modifiable[key_name] = x
|
132
|
+
self[key_name] = x
|
106
133
|
end
|
107
134
|
define_method("#{name}_as_a_hash") { @table[key_name] }
|
108
135
|
end
|
@@ -113,7 +140,7 @@ class RecursiveOpenStruct < OpenStruct
|
|
113
140
|
# Support Ruby 2.4.0+'s changes in a way that doesn't require dynamically
|
114
141
|
# modifying ROS.
|
115
142
|
#
|
116
|
-
# TODO: Once we care less about Rubies before 2.4.0, reverse this
|
143
|
+
# TODO: Once we care less about Rubies before 2.4.0, reverse this so that
|
117
144
|
# new_ostruct_member points to our version and not OpenStruct's.
|
118
145
|
alias new_ostruct_member! new_ostruct_member
|
119
146
|
# new_ostruct_member! is private, but new_ostruct_member is not on OpenStruct in 2.4.0-rc1?!
|
@@ -122,8 +149,8 @@ class RecursiveOpenStruct < OpenStruct
|
|
122
149
|
def delete_field(name)
|
123
150
|
sym = _get_key_from_table_(name)
|
124
151
|
singleton_class.__send__(:remove_method, sym, "#{sym}=") rescue NoMethodError # ignore if methods not yet generated.
|
125
|
-
@sub_elements.delete
|
126
|
-
@table.delete
|
152
|
+
@sub_elements.delete(sym)
|
153
|
+
@table.delete(sym)
|
127
154
|
end
|
128
155
|
|
129
156
|
private
|
@@ -134,11 +161,14 @@ class RecursiveOpenStruct < OpenStruct
|
|
134
161
|
name
|
135
162
|
end
|
136
163
|
|
164
|
+
def _create_sub_element_(hash, **overrides)
|
165
|
+
self.class.new(hash, @options.merge(overrides))
|
166
|
+
end
|
167
|
+
|
137
168
|
def recurse_over_array(array)
|
138
169
|
array.each_with_index do |a, i|
|
139
170
|
if a.is_a? Hash
|
140
|
-
array[i] =
|
141
|
-
:mutate_input_hash => true, :preserve_original_keys => @preserve_original_keys)
|
171
|
+
array[i] = _create_sub_element_(a, mutate_input_hash: true, recurse_over_arrays: true)
|
142
172
|
elsif a.is_a? Array
|
143
173
|
array[i] = recurse_over_array a
|
144
174
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'set'
|
1
2
|
class RecursiveOpenStruct::DeepDup
|
2
3
|
def initialize(opts={})
|
3
4
|
@recurse_over_arrays = opts.fetch(:recurse_over_arrays, false)
|
@@ -10,7 +11,7 @@ class RecursiveOpenStruct::DeepDup
|
|
10
11
|
|
11
12
|
private
|
12
13
|
|
13
|
-
def deep_dup(obj, visited=
|
14
|
+
def deep_dup(obj, visited=Set.new)
|
14
15
|
if obj.is_a?(Hash)
|
15
16
|
obj.each_with_object({}) do |(key, value), h|
|
16
17
|
h[@preserve_original_keys ? key : key.to_sym] = value_or_deep_dup(value, visited)
|
@@ -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,94 +1,94 @@
|
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William (B.J.) Snow Orvis
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
14
15
|
requirement: !ruby/object:Gem::Requirement
|
15
16
|
requirements:
|
16
17
|
- - ">="
|
17
18
|
- !ruby/object:Gem::Version
|
18
19
|
version: '0'
|
19
|
-
name: bundler
|
20
|
-
prerelease: false
|
21
20
|
type: :development
|
21
|
+
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
28
29
|
requirement: !ruby/object:Gem::Requirement
|
29
30
|
requirements:
|
30
31
|
- - ">="
|
31
32
|
- !ruby/object:Gem::Version
|
32
33
|
version: '0'
|
33
|
-
name: pry
|
34
|
-
prerelease: false
|
35
34
|
type: :development
|
35
|
+
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
42
43
|
requirement: !ruby/object:Gem::Requirement
|
43
44
|
requirements:
|
44
45
|
- - ">="
|
45
46
|
- !ruby/object:Gem::Version
|
46
47
|
version: '0'
|
47
|
-
name: rake
|
48
|
-
prerelease: false
|
49
48
|
type: :development
|
49
|
+
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
+
name: rdoc
|
56
57
|
requirement: !ruby/object:Gem::Requirement
|
57
58
|
requirements:
|
58
59
|
- - ">="
|
59
60
|
- !ruby/object:Gem::Version
|
60
61
|
version: '0'
|
61
|
-
name: rdoc
|
62
|
-
prerelease: false
|
63
62
|
type: :development
|
63
|
+
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
70
71
|
requirement: !ruby/object:Gem::Requirement
|
71
72
|
requirements:
|
72
73
|
- - "~>"
|
73
74
|
- !ruby/object:Gem::Version
|
74
75
|
version: '3.2'
|
75
|
-
name: rspec
|
76
|
-
prerelease: false
|
77
76
|
type: :development
|
77
|
+
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '3.2'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
84
85
|
requirement: !ruby/object:Gem::Requirement
|
85
86
|
requirements:
|
86
87
|
- - ">="
|
87
88
|
- !ruby/object:Gem::Version
|
88
89
|
version: '0'
|
89
|
-
name: simplecov
|
90
|
-
prerelease: false
|
91
90
|
type: :development
|
91
|
+
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - ">="
|
@@ -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,21 +128,22 @@ 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: {}
|
144
|
-
post_install_message:
|
146
|
+
post_install_message:
|
145
147
|
rdoc_options: []
|
146
148
|
require_paths:
|
147
149
|
- lib
|
@@ -156,9 +158,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
156
158
|
- !ruby/object:Gem::Version
|
157
159
|
version: '0'
|
158
160
|
requirements: []
|
159
|
-
|
160
|
-
|
161
|
-
signing_key:
|
161
|
+
rubygems_version: 3.1.2
|
162
|
+
signing_key:
|
162
163
|
specification_version: 4
|
163
164
|
summary: OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs
|
164
165
|
test_files:
|
@@ -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
|
-
|