recursive-open-struct 1.0.4 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c20dbcb5ce4e4805d18e6c7ea454ef2357dbc224
4
- data.tar.gz: d9a112d4e90c9355c3f7356e88b16f81fe8da7f7
2
+ SHA256:
3
+ metadata.gz: 3cc972f20fd1aba4f8eea6fe1766c17c7d9e663a3ed999e8375549ac37ae4b08
4
+ data.tar.gz: 9c0d0230d431aedeae47f0f2026d77196dfb230c3ebe9359763b4265c9a4c4d6
5
5
  SHA512:
6
- metadata.gz: 7379ae006edd0cea0f1b052098042a6333cc8ed3509fa1742f77b54aa73d135eb483e41bfa7eb00e39b29fb2c7c5cc808cd788f4af0cb3ff0fdb11a77a4d2f77
7
- data.tar.gz: ac6f2be126563fa292e523f2e463fa1a8f520f8d5e6b45c134e6d196a4bc50de4d1bcf144448cf8da7b5b862615cf400efaf094efd1c11c9b55e7f5e96fa7175
6
+ metadata.gz: dcdc725048e24182c562551875b2fa90d100248008af5dd4a974bb8df82f48ec5f618b3d76458d16f868b76b55263e96d3576a6d92f5ace69979beb29ae3213f
7
+ data.tar.gz: d67c50798fcaa7ca49003a924e57df0a22e83a5d688ef6ff90b61f462855f2b6ef8882e5873c0994558416030a9b265fe60cccfe586fd5bf9688e463d92279fe
@@ -1,24 +1,33 @@
1
1
  ---
2
2
  language: ruby
3
3
  rvm:
4
- # - 1.9.3 # json gem now requires Ruby ~> 2.0
4
+ # No longer supported (but test anyways)
5
5
  - 2.0.0
6
6
  - 2.1.10
7
- - 2.2.7
8
- - 2.3.4
9
- - 2.4.1
10
- - ruby-head
7
+ - 2.2.10
11
8
  - jruby-19mode
12
- - jruby-9.0.5.0
13
- - jruby-9.1.5.0
9
+ - 2.3.8
10
+ - 2.4.10
11
+ # Current stable supported by Travis
12
+ - 2.5.8
13
+ - 2.6.6
14
+ - 2.7.1
15
+ - jruby-9.1.9.0
16
+ # Future
17
+ - ruby-head
14
18
  - jruby-head
19
+ - truffleruby-head
15
20
  sudo: false
16
21
  matrix:
17
22
  allow_failures:
18
23
  # No longer supported
19
24
  - rvm: 2.0.0
20
25
  - rvm: 2.1.10
26
+ - rvm: 2.2.10
27
+ - rvm: 2.3.8
28
+ - rvm: 2.4.10
21
29
  - rvm: jruby-19mode
22
30
  # Future
23
31
  - rvm: ruby-head
24
32
  - rvm: jruby-head
33
+ - rvm: truffleruby-head
@@ -1,8 +1,15 @@
1
1
  Recursive-open-struct was written by these fine people:
2
2
 
3
+ * Ben Langfeld <ben@langfeld.me>
4
+ * Beni Cherniavsky-Paskin <cben@redhat.com>
3
5
  * Cédric Felizard <cedric@felizard.fr>
6
+ * David Feldman <dbfeldman@gmail.com>
7
+ * Edward Betts <edward@4angle.com>
8
+ * Ewoud Kohl van Wijngaarden <ewoud@kohlvanwijngaarden.nl>
4
9
  * Federico Aloi <federico.aloi@gmail.com>
5
10
  * fervic <roberto@runawaybit.com>
11
+ * Igor Victor <gogainda@yandex.ru>
12
+ * Jean Boussier <jean.boussier@gmail.com>
6
13
  * Joe Rafaniello <jrafanie@redhat.com>
7
14
  * Kris Dekeyser <kris.dekeyser@libis.be>
8
15
  * Matt Culpepper <matt@culpepper.co>
@@ -10,6 +17,7 @@ Recursive-open-struct was written by these fine people:
10
17
  * Offirmo <offirmo.net@gmail.com>
11
18
  * Pedro Sena <sena.pedro@gmail.com>
12
19
  * Peter Yeremenko <peter.yeremenko@gmail.com>
20
+ * Pirate Praveen <praveen@debian.org>
13
21
  * Sebastian Gaul <sebastian@mgvmedia.com>
14
22
  * Thiago Guimaraes <thiagogsr@gmail.com>
15
23
  * Tom Chapin <tchapin@gmail.com>
@@ -1,8 +1,55 @@
1
+ 1.1.3 / 2020/10/15
2
+ ==================
3
+
4
+ * No longer officially supporting Ruby 2.4.x, but compatiblity continues.
5
+ * [#68](https://github.com/aetherknight/recursive-open-struct/pull/68): Igor
6
+ Victor: Add truffleruby-head to travis
7
+ * FIX [#67](https://github.com/aetherknight/recursive-open-struct/pull/67):
8
+ Jean Boussier: Support upstream changes to OpenStruct in ruby-head (Ruby
9
+ 3.0.0-dev)
10
+
11
+ 1.1.2 / 2020/06/20
12
+ ==================
13
+
14
+ * FIX [#58](https://github.com/aetherknight/recursive-open-struct/pull/58):
15
+ David Feldman: Fix `[]=` so that it properly updates sub-elements
16
+ * [#58](https://github.com/aetherknight/recursive-open-struct/pull/58):
17
+ David Feldman: Make the default options configurable at the class level to
18
+ simplify adding additional options in subclasses
19
+
20
+ 1.1.1 / 2020/03/10
21
+ ==================
22
+
23
+ * FIX [#64](https://github.com/aetherknight/recursive-open-struct/pull/64):
24
+ Pirate Praveen: Support Ruby 2.7.0. `OpenStruct#modifiable` support was
25
+ finally dropped, and has to be replaced with `OpenStruct#modifiable?`.
26
+ * Made some additional changes to continue supporting pre-2.4.x Rubies,
27
+ including the current stable JRuby (9.1.x.x, which tracks Ruby 2.3.x for
28
+ features)
29
+
30
+ 1.1.0 / 2018-02-03
31
+ ==================
32
+
33
+ * NEW/FIX [#56](https://github.com/aetherknight/recursive-open-struct/issues/56):
34
+ Add better support for Ruby 2.3+'s `#dig` method (when it exists for the
35
+ current version of Ruby), so that nested Hashes are properly converted to
36
+ RecursiveOpenStructs. `OpenStruct#dig`'s implementation was returning Hashes
37
+ and does not handle `recurse_over_arrays` so ROS needs special support.
38
+ Thanks to maxp-edcast for reporting the issue.
39
+ * FIX [#55](https://github.com/aetherknight/recursive-open-struct/pull/55):
40
+ EdwardBetts: Fixed a typo in the documentation/comment for `#method_missing`
41
+
42
+ 1.0.5 / 2017-06-21
43
+ ==================
44
+
45
+ * FIX [#54](https://github.com/aetherknight/recursive-open-struct/pull/54):
46
+ Beni Cherniavsky-Paskin: Improve performance of `new_ostruct_member` by using
47
+ `self.singleton_class.method_defined?` instead of `self.methods.include?`
48
+
1
49
  1.0.4 / 2017-04-29
2
50
  ==================
3
51
 
4
- * FIX
5
- [#52](https://github.com/aetherknight/recursive-open-struct/pull/52): Joe
52
+ * FIX [#52](https://github.com/aetherknight/recursive-open-struct/pull/52): Joe
6
53
  Rafaniello: Improve performance of DeepDup by using Set instead of an Array
7
54
  to track visited nodes.
8
55
 
@@ -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,56 +3,81 @@ require 'recursive_open_struct/version'
3
3
 
4
4
  require 'recursive_open_struct/debug_inspect'
5
5
  require 'recursive_open_struct/deep_dup'
6
- require 'recursive_open_struct/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
29
41
 
30
- def initialize_copy(orig)
31
- super
32
42
 
33
- # deep copy the table to separate the two objects
34
- @table = @deep_dup.call(orig.instance_variable_get(:@table))
35
- # Forget any memoized sub-elements
36
- @sub_elements = {}
43
+ if OpenStruct.public_instance_methods.include?(:initialize_copy)
44
+ def initialize_copy(orig)
45
+ super
46
+
47
+ # deep copy the table to separate the two objects
48
+ @table = @deep_dup.call(@table)
49
+ # Forget any memoized sub-elements
50
+ @sub_elements = {}
51
+ end
37
52
  end
38
53
 
39
54
  def to_h
40
55
  @deep_dup.call(@table)
41
56
  end
42
57
 
58
+ # TODO: deprecated, unsupported by OpenStruct. OpenStruct does not consider
59
+ # itself to be a "kind of" Hash.
43
60
  alias_method :to_hash, :to_h
44
61
 
62
+ # Continue supporting older rubies -- JRuby 9.1.x.x is still considered
63
+ # stable, but is based on Ruby
64
+ # 2.3.x and so uses :modifiable instead of :modifiable?. Furthermore, if
65
+ # :modifiable is private, then make :modifiable? private too.
66
+ if !OpenStruct.private_instance_methods.include?(:modifiable?)
67
+ if OpenStruct.private_instance_methods.include?(:modifiable)
68
+ alias_method :modifiable?, :modifiable
69
+ elsif OpenStruct.public_instance_methods.include?(:modifiable)
70
+ alias_method :modifiable?, :modifiable
71
+ private :modifiable?
72
+ end
73
+ end
74
+
45
75
  def [](name)
46
76
  key_name = _get_key_from_table_(name)
47
77
  v = @table[key_name]
48
78
  if v.is_a?(Hash)
49
- @sub_elements[key_name] ||= 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
79
+ @sub_elements[key_name] ||= _create_sub_element_(v, mutate_input_hash: true)
80
+ elsif v.is_a?(Array) and @options[:recurse_over_arrays]
56
81
  @sub_elements[key_name] ||= recurse_over_array(v)
57
82
  @sub_elements[key_name] = recurse_over_array(@sub_elements[key_name])
58
83
  else
@@ -60,29 +85,45 @@ class RecursiveOpenStruct < OpenStruct
60
85
  end
61
86
  end
62
87
 
88
+ if private_instance_methods.include?(:modifiable?) || public_instance_methods.include?(:modifiable?)
89
+ def []=(name, value)
90
+ key_name = _get_key_from_table_(name)
91
+ tbl = modifiable? # Ensure we are modifiable
92
+ @sub_elements.delete(key_name)
93
+ tbl[key_name] = value
94
+ end
95
+ else
96
+ def []=(name, value)
97
+ key_name = _get_key_from_table_(name)
98
+ @table[key_name] = value # raises if self is frozen in Ruby 3.0
99
+ @sub_elements.delete(key_name)
100
+ end
101
+ end
102
+
63
103
  # Makes sure ROS responds as expected on #respond_to? and #method requests
64
104
  def respond_to_missing?(mid, include_private = false)
65
105
  mname = _get_key_from_table_(mid.to_s.chomp('=').chomp('_as_a_hash'))
66
106
  @table.key?(mname) || super
67
107
  end
68
108
 
69
- # Adapted implementation of method_missing to 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.
109
+ # Adapted implementation of method_missing to accommodate the differences
110
+ # between ROS and OS.
73
111
  def method_missing(mid, *args)
74
112
  len = args.length
75
113
  if mid =~ /^(.*)=$/
76
114
  if len != 1
77
115
  raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
78
116
  end
79
- modifiable[new_ostruct_member!($1.to_sym)] = args[0]
117
+ # self[$1.to_sym] = args[0]
118
+ # modifiable?[new_ostruct_member!($1.to_sym)] = args[0]
119
+ new_ostruct_member!($1.to_sym)
120
+ public_send(mid, args[0])
80
121
  elsif len == 0
81
122
  key = mid
82
123
  key = $1 if key =~ /^(.*)_as_a_hash$/
83
124
  if @table.key?(_get_key_from_table_(key))
84
125
  new_ostruct_member!(key)
85
- send(mid)
126
+ public_send(mid)
86
127
  end
87
128
  else
88
129
  err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
@@ -95,14 +136,13 @@ class RecursiveOpenStruct < OpenStruct
95
136
  # 2.4.0.
96
137
  def new_ostruct_member(name)
97
138
  key_name = _get_key_from_table_(name)
98
- unless self.methods.include?(name.to_sym)
139
+ unless self.singleton_class.method_defined?(name.to_sym)
99
140
  class << self; self; end.class_eval do
100
141
  define_method(name) do
101
142
  self[key_name]
102
143
  end
103
144
  define_method("#{name}=") do |x|
104
- @sub_elements.delete(key_name)
105
- modifiable[key_name] = x
145
+ self[key_name] = x
106
146
  end
107
147
  define_method("#{name}_as_a_hash") { @table[key_name] }
108
148
  end
@@ -113,7 +153,7 @@ class RecursiveOpenStruct < OpenStruct
113
153
  # Support Ruby 2.4.0+'s changes in a way that doesn't require dynamically
114
154
  # modifying ROS.
115
155
  #
116
- # TODO: Once we care less about Rubies before 2.4.0, reverse this sot hat
156
+ # TODO: Once we care less about Rubies before 2.4.0, reverse this so that
117
157
  # new_ostruct_member points to our version and not OpenStruct's.
118
158
  alias new_ostruct_member! new_ostruct_member
119
159
  # new_ostruct_member! is private, but new_ostruct_member is not on OpenStruct in 2.4.0-rc1?!
@@ -122,23 +162,36 @@ class RecursiveOpenStruct < OpenStruct
122
162
  def delete_field(name)
123
163
  sym = _get_key_from_table_(name)
124
164
  singleton_class.__send__(:remove_method, sym, "#{sym}=") rescue NoMethodError # ignore if methods not yet generated.
125
- @sub_elements.delete sym
126
- @table.delete sym
165
+ @sub_elements.delete(sym)
166
+ @table.delete(sym)
127
167
  end
128
168
 
129
169
  private
130
170
 
171
+ unless OpenStruct.public_instance_methods.include?(:initialize_copy)
172
+ def initialize_dup(orig)
173
+ super
174
+ # deep copy the table to separate the two objects
175
+ @table = @deep_dup.call(@table)
176
+ # Forget any memoized sub-elements
177
+ @sub_elements = {}
178
+ end
179
+ end
180
+
131
181
  def _get_key_from_table_(name)
132
182
  return name.to_s if @table.has_key?(name.to_s)
133
183
  return name.to_sym if @table.has_key?(name.to_sym)
134
184
  name
135
185
  end
136
186
 
187
+ def _create_sub_element_(hash, **overrides)
188
+ self.class.new(hash, @options.merge(overrides))
189
+ end
190
+
137
191
  def recurse_over_array(array)
138
192
  array.each_with_index do |a, i|
139
193
  if a.is_a? Hash
140
- array[i] = self.class.new(a, :recurse_over_arrays => true,
141
- :mutate_input_hash => true, :preserve_original_keys => @preserve_original_keys)
194
+ array[i] = _create_sub_element_(a, mutate_input_hash: true, recurse_over_arrays: true)
142
195
  elsif a.is_a? Array
143
196
  array[i] = recurse_over_array a
144
197
  end
@@ -0,0 +1,22 @@
1
+ class RecursiveOpenStruct < OpenStruct
2
+ module Dig
3
+
4
+ # Replaces +OpenStruct#dig+ to properly support treating nested values as
5
+ # RecursiveOpenStructs instead of returning the nested Hashes.
6
+ def dig(name, *names)
7
+ begin
8
+ name = name.to_sym
9
+ rescue NoMethodError
10
+ raise TypeError, "#{name} is not a symbol nor a string"
11
+ end
12
+
13
+ name_val = self[name]
14
+
15
+ if names.length > 0 && name_val.respond_to?(:dig)
16
+ name_val.dig(*names)
17
+ else
18
+ name_val
19
+ end
20
+ end
21
+ end
22
+ end
@@ -3,5 +3,5 @@
3
3
  require 'ostruct'
4
4
 
5
5
  class RecursiveOpenStruct < OpenStruct
6
- VERSION = "1.0.4"
6
+ VERSION = "1.1.3"
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,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: recursive-open-struct
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - William (B.J.) Snow Orvis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-29 00:00:00.000000000 Z
11
+ date: 2020-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -119,6 +119,7 @@ files:
119
119
  - ".travis.yml"
120
120
  - AUTHORS.txt
121
121
  - CHANGELOG.md
122
+ - CONTRIBUTING.md
122
123
  - Gemfile
123
124
  - LICENSE.txt
124
125
  - README.md
@@ -127,17 +128,18 @@ files:
127
128
  - lib/recursive_open_struct.rb
128
129
  - lib/recursive_open_struct/debug_inspect.rb
129
130
  - lib/recursive_open_struct/deep_dup.rb
130
- - lib/recursive_open_struct/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: {}
@@ -156,8 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
158
  - !ruby/object:Gem::Version
157
159
  version: '0'
158
160
  requirements: []
159
- rubyforge_project:
160
- rubygems_version: 2.6.8
161
+ rubygems_version: 3.1.2
161
162
  signing_key:
162
163
  specification_version: 4
163
164
  summary: OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs
@@ -166,6 +167,7 @@ test_files:
166
167
  - spec/recursive_open_struct/indifferent_access_spec.rb
167
168
  - spec/recursive_open_struct/open_struct_behavior_spec.rb
168
169
  - spec/recursive_open_struct/ostruct_2_0_0_spec.rb
170
+ - spec/recursive_open_struct/ostruct_2_3_0_spec.rb
169
171
  - spec/recursive_open_struct/recursion_and_subclassing_spec.rb
170
172
  - spec/recursive_open_struct/recursion_spec.rb
171
173
  - spec/spec_helper.rb
@@ -1,27 +0,0 @@
1
- module RecursiveOpenStruct::Ruby19Backport
2
- # Apply fix if necessary:
3
- # https://github.com/ruby/ruby/commit/2d952c6d16ffe06a28bb1007e2cd1410c3db2d58
4
- def initialize_copy(orig)
5
- super
6
- @table.each_key{|key| new_ostruct_member(key)}
7
- end
8
-
9
- def []=(name, value)
10
- modifiable[new_ostruct_member(name)] = value
11
- end
12
-
13
- def eql?(other)
14
- return false unless other.kind_of?(OpenStruct)
15
- @table.eql?(other.table)
16
- end
17
-
18
- def hash
19
- @table.hash
20
- end
21
-
22
- def each_pair
23
- return to_enum(:each_pair) { @table.size } unless block_given?
24
- @table.each_pair{|p| yield p}
25
- end
26
- end
27
-