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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1a818135247a6ccee4084c02a5504a41213ed7ef
4
- data.tar.gz: 83e7f427a585d6b32f0d041d0b237e37f16fc7d1
2
+ SHA256:
3
+ metadata.gz: f4474b5a862c6ea4da4f2c930664880ca70a01f75c077a2e9952fd80ed2d26da
4
+ data.tar.gz: dcea4c80ebe98a5f91f9ea40b2b7e82cc219b259eae83b62a00329f8075cc85a
5
5
  SHA512:
6
- metadata.gz: 786b9589759c155996647a4a5cf35ae3d3bb28515c9d6cc9555ca152c4ff34578abb8b6e3357b5711340f22702e482517ad8d60d5fd53c0f418f99caa95d819a
7
- data.tar.gz: e76b6ab3dc483c8eecbcc40cfae8f66da581533d45826d7e215baaf763f6f6176bb040502c96f2858c4492ca93784d1de302ed78eb9417abf3efb357129970dc
6
+ metadata.gz: e8698402e67013a209a3434235e26201550c2249db8ae82480ec2208bdd0c83fb6524be046ecb4af3ed8cfe09adebd513a383d56b8b40d91f2c973fcd1f18196
7
+ data.tar.gz: a15827f31947ca85607086b9cda61ab18b4421bb310dd3034ca4dd39ef43ecf5215d599103cb897152924d6faa1ae94e0bc800cf197c5e96ab50fc794c547a2c
@@ -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.7
8
- - 2.3.4
9
- - 2.4.1
10
- - ruby-head
8
+ - 2.2.10
11
9
  - jruby-19mode
12
- - jruby-9.0.5.0
13
- - jruby-9.1.5.0
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
@@ -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>
@@ -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
 
@@ -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.
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009-2016, The Recursive-open-struct developers (given in the
1
+ Copyright (c) 2009-2018, The Recursive-open-struct developers (given in the
2
2
  file AUTHORS.txt).
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining
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
- ros = RecursiveOpenStruct.new( { fooa: { foob: 'fooc' } } )
11
+ ```ruby
12
+ ros = RecursiveOpenStruct.new( { wha: { tagoo: 'siam' } } )
9
13
 
10
- ros.fooa.foob # => 'fooc'
14
+ ros.wha.tagoo # => 'siam'
15
+ ```
11
16
 
12
17
  Also, if needed, nested hashes can still be accessed as hashes:
13
18
 
14
- ros.fooa_as_a_hash # { foob: 'fooc' }
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
- h = { :somearr => [ { name: 'a'}, { name: 'b' } ] }
20
- ros = RecursiveOpenStruct.new(h, recurse_over_arrays: true )
33
+ ros = RecursiveOpenStruct.new(h)
34
+ ros.somearr # => [ { name: 'a'}, { name: 'b' } ]
35
+ ```
21
36
 
22
- ros.somearr[0].name # => 'a'
23
- ros.somearr[1].name # => 'b'
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
- h = { 'fear' => 'is', 'the' => 'mindkiller' } }
28
- ros = RecursiveOpenStruct.new(h)
29
- ros.to_h # => { fear: 'is', the: 'mindkiller' }
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
- h = { 'fear' => 'is', 'the' => 'mindkiller' } }
34
- ros = RecursiveOpenStruct.new(h, preserve_original_keys: true)
35
- ros.to_h # => { 'fear' => 'is', 'the' => 'mindkiller' }
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 throw that in your gemfile :
70
+ If you use bundler, just add recursive-open-struct to your gemfile :
42
71
 
43
- gem 'recursive-open-struct'
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
- * Fork the project.
52
- * Make your feature addition or bug fix.
53
- * Add tests for your new or changed functionality. Make sure the tests you add
54
- provide clean and clear explanation of the feature.
55
- * Send me a pull request. Bonus points for topic branches.
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-2016, The Recursive-open-struct developers (given in the
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/ruby_19_backport'
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 Ruby19Backport if RUBY_VERSION =~ /\A1.9/
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 initialize(hash=nil, args={})
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
- @table = args.fetch(:mutate_input_hash, false) ? hash : @deep_dup.call(hash)
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] ||= self.class.new(
50
- v,
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
- # Adapted implementation of method_missing to accomodate the differences between ROS and OS.
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.
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
- modifiable[new_ostruct_member!($1.to_sym)] = args[0]
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
- send(mid)
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.methods.include?(name.to_sym)
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
- @sub_elements.delete(key_name)
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 sot hat
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 sym
126
- @table.delete sym
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] = self.class.new(a, :recurse_over_arrays => true,
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
@@ -3,5 +3,5 @@
3
3
  require 'ostruct'
4
4
 
5
5
  class RecursiveOpenStruct < OpenStruct
6
- VERSION = "1.0.3"
6
+ VERSION = "1.1.2"
7
7
  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 = "http://github.com/aetherknight/recursive-open-struct"
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
- expect(ros.each_pair).to match ({:foo => 'foo', :bar => :bar}.each_pair)
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.0.3
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: 2017-04-10 00:00:00.000000000 Z
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/ruby_19_backport.rb
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: http://github.com/aetherknight/recursive-open-struct
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
- rubyforge_project:
160
- rubygems_version: 2.6.6
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
-