recursive-open-struct 1.0.3 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
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
-