recursive-open-struct 1.0.4 → 1.1.3

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: 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
-