recursive-open-struct-sd 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ module RecursiveOpenStruct::DebugInspect
2
+ def debug_inspect(io = STDOUT, indent_level = 0, recursion_limit = 12)
3
+ display_recursive_open_struct(io, @table, indent_level, recursion_limit)
4
+ end
5
+
6
+ def display_recursive_open_struct(io, ostrct_or_hash, indent_level, recursion_limit)
7
+
8
+ if recursion_limit <= 0 then
9
+ # protection against recursive structure (like in the tests)
10
+ io.puts ' '*indent_level + '(recursion limit reached)'
11
+ else
12
+ #puts ostrct_or_hash.inspect
13
+ if ostrct_or_hash.is_a?(self.class) then
14
+ ostrct_or_hash = ostrct_or_hash.marshal_dump
15
+ end
16
+
17
+ # We'll display the key values like this : key = value
18
+ # to align display, we look for the maximum key length of the data that will be displayed
19
+ # (everything except hashes)
20
+ data_indent = ostrct_or_hash \
21
+ .reject { |k, v| v.is_a?(self.class) || v.is_a?(Hash) } \
22
+ .max {|a,b| a[0].to_s.length <=> b[0].to_s.length}[0].to_s.length
23
+ # puts "max length = #{data_indent}"
24
+
25
+ ostrct_or_hash.each do |key, value|
26
+ if (value.is_a?(self.class) || value.is_a?(Hash)) then
27
+ io.puts ' '*indent_level + key.to_s + '.'
28
+ display_recursive_open_struct(io, value, indent_level + 1, recursion_limit - 1)
29
+ else
30
+ io.puts ' '*indent_level + key.to_s + ' '*(data_indent - key.to_s.length) + ' = ' + value.inspect
31
+ end
32
+ end
33
+ end
34
+
35
+ true
36
+ end
37
+
38
+ end
@@ -0,0 +1,32 @@
1
+ class RecursiveOpenStruct::DeepDup
2
+ def initialize(opts={})
3
+ @recurse_over_arrays = opts.fetch(:recurse_over_arrays, false)
4
+ @preserve_original_keys = opts.fetch(:preserve_original_keys, false)
5
+ end
6
+
7
+ def call(obj)
8
+ deep_dup(obj)
9
+ end
10
+
11
+ private
12
+
13
+ def deep_dup(obj, visited=[])
14
+ if obj.is_a?(Hash)
15
+ obj.each_with_object({}) do |(key, value), h|
16
+ h[@preserve_original_keys ? key : key.to_sym] = value_or_deep_dup(value, visited)
17
+ end
18
+ elsif obj.is_a?(Array) && @recurse_over_arrays
19
+ obj.each_with_object([]) do |value, arr|
20
+ value = value.is_a?(RecursiveOpenStruct) ? value.to_h : value
21
+ arr << value_or_deep_dup(value, visited)
22
+ end
23
+ else
24
+ obj
25
+ end
26
+ end
27
+
28
+ def value_or_deep_dup(value, visited)
29
+ obj_id = value.object_id
30
+ visited.include?(obj_id) ? value : deep_dup(value, visited << obj_id)
31
+ end
32
+ end
@@ -0,0 +1,27 @@
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
+
@@ -0,0 +1,7 @@
1
+ # Necessary since the top-level class/module is a class that inherits from
2
+ # OpenStruct.
3
+ require 'ostruct'
4
+
5
+ class RecursiveOpenStruct < OpenStruct
6
+ VERSION = "1.0.2"
7
+ end
@@ -0,0 +1,46 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require './lib/recursive_open_struct/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "recursive-open-struct-sd"
7
+ s.version = RecursiveOpenStruct::VERSION
8
+ s.authors = ["William (B.J.) Snow Orvis"]
9
+ s.email = "aetherknight@gmail.com"
10
+ s.date = Time.now.utc.strftime("%Y-%m-%d")
11
+ s.homepage = "http://github.com/aetherknight/recursive-open-struct"
12
+ s.licenses = ["MIT"]
13
+
14
+ s.summary = "OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs"
15
+ s.description = <<-QUOTE .gsub(/^ /,'')
16
+ RecursiveOpenStruct is a subclass of OpenStruct. It differs from
17
+ OpenStruct in that it allows nested hashes to be treated in a recursive
18
+ fashion. For example:
19
+
20
+ ros = RecursiveOpenStruct.new({ :a => { :b => 'c' } })
21
+ ros.a.b # 'c'
22
+
23
+ Also, nested hashes can still be accessed as hashes:
24
+
25
+ ros.a_as_a_hash # { :b => 'c' }
26
+
27
+ > This is a fork of the original recursive-open-struct
28
+ > to include a fix for https://github.com/aetherknight/recursive-open-struct/issues/46
29
+ QUOTE
30
+
31
+ s.files = `git ls-files`.split("\n")
32
+ s.test_files = `git ls-files spec`.split("\n")
33
+ s.require_paths = ["lib"]
34
+ s.extra_rdoc_files = [
35
+ "CHANGELOG.md",
36
+ "LICENSE.txt",
37
+ "README.md"
38
+ ]
39
+
40
+ s.add_development_dependency('bundler', [">= 0"])
41
+ s.add_development_dependency('pry', [">= 0"])
42
+ s.add_development_dependency('rake', [">= 0"])
43
+ s.add_development_dependency('rdoc', [">= 0"])
44
+ s.add_development_dependency('rspec', "~> 3.2")
45
+ s.add_development_dependency('simplecov', [">= 0"])
46
+ end
@@ -0,0 +1,70 @@
1
+ require_relative '../spec_helper'
2
+ require 'recursive_open_struct'
3
+
4
+ describe RecursiveOpenStruct do
5
+ describe "#debug_inspect" do
6
+ before(:each) do
7
+ h1 = { :a => 'a'}
8
+ h2 = { :a => 'b', :h1 => h1 }
9
+ h1[:h2] = h2
10
+ @ros = RecursiveOpenStruct.new(h2)
11
+ end
12
+
13
+ it "should have a simple way of display" do
14
+ @output = <<-QUOTE
15
+ a = "b"
16
+ h1.
17
+ a = "a"
18
+ h2.
19
+ a = "b"
20
+ h1.
21
+ a = "a"
22
+ h2.
23
+ a = "b"
24
+ h1.
25
+ a = "a"
26
+ h2.
27
+ a = "b"
28
+ h1.
29
+ a = "a"
30
+ h2.
31
+ a = "b"
32
+ h1.
33
+ a = "a"
34
+ h2.
35
+ a = "b"
36
+ h1.
37
+ a = "a"
38
+ h2.
39
+ (recursion limit reached)
40
+ QUOTE
41
+ @io = StringIO.new
42
+ @ros.debug_inspect(@io)
43
+ expect(@io.string).to match /^a = "b"$/
44
+ expect(@io.string).to match /^h1\.$/
45
+ expect(@io.string).to match /^ a = "a"$/
46
+ expect(@io.string).to match /^ h2\.$/
47
+ expect(@io.string).to match /^ a = "b"$/
48
+ expect(@io.string).to match /^ h1\.$/
49
+ expect(@io.string).to match /^ a = "a"$/
50
+ expect(@io.string).to match /^ h2\.$/
51
+ expect(@io.string).to match /^ a = "b"$/
52
+ expect(@io.string).to match /^ h1\.$/
53
+ expect(@io.string).to match /^ a = "a"$/
54
+ expect(@io.string).to match /^ h2\.$/
55
+ expect(@io.string).to match /^ a = "b"$/
56
+ expect(@io.string).to match /^ h1\.$/
57
+ expect(@io.string).to match /^ a = "a"$/
58
+ expect(@io.string).to match /^ h2\.$/
59
+ expect(@io.string).to match /^ a = "b"$/
60
+ expect(@io.string).to match /^ h1\.$/
61
+ expect(@io.string).to match /^ a = "a"$/
62
+ expect(@io.string).to match /^ h2\.$/
63
+ expect(@io.string).to match /^ a = "b"$/
64
+ expect(@io.string).to match /^ h1\.$/
65
+ expect(@io.string).to match /^ a = "a"$/
66
+ expect(@io.string).to match /^ h2\.$/
67
+ expect(@io.string).to match /^ \(recursion limit reached\)$/
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,166 @@
1
+ require_relative '../spec_helper'
2
+ require 'recursive_open_struct'
3
+
4
+ describe RecursiveOpenStruct do
5
+ let(:value) { 'foo' }
6
+ let(:symbol) { :bar }
7
+ let(:new_value) { 'bar' }
8
+ let(:new_symbol) { :foo }
9
+
10
+ describe 'indifferent access' do
11
+ let(:hash) { {:foo => value, 'bar' => symbol} }
12
+ let(:hash_ros_opts) { {} }
13
+ subject(:hash_ros) { RecursiveOpenStruct.new(hash, hash_ros_opts) }
14
+
15
+ context 'setting value with method' do
16
+ before(:each) do
17
+ subject.foo = value
18
+ end
19
+
20
+ it('allows getting with method') { expect(subject.foo).to be value }
21
+ it('allows getting with symbol') { expect(subject[:foo]).to be value }
22
+ it('allows getting with string') { expect(subject['foo']).to be value }
23
+
24
+ end
25
+
26
+ context 'setting value with symbol' do
27
+ before(:each) do
28
+ subject[:foo] = value
29
+ end
30
+
31
+ it('allows getting with method') { expect(subject.foo).to be value }
32
+ it('allows getting with symbol') { expect(subject[:foo]).to be value }
33
+ it('allows getting with string') { expect(subject['foo']).to be value }
34
+
35
+ end
36
+
37
+ context 'setting value with string' do
38
+ before(:each) do
39
+ subject['foo'] = value
40
+ end
41
+
42
+ it('allows getting with method') { expect(subject.foo).to be value }
43
+ it('allows getting with symbol') { expect(subject[:foo]).to be value }
44
+ it('allows getting with string') { expect(subject['foo']).to be value }
45
+
46
+ end
47
+
48
+ context 'overwriting values' do
49
+ context 'set with method' do
50
+ before(:each) do
51
+ subject.foo = value
52
+ end
53
+
54
+ it('overrides with symbol') do
55
+ subject[:foo] = new_value
56
+ expect(subject.foo).to be new_value
57
+ end
58
+
59
+ it('overrides with string') do
60
+ subject['foo'] = new_value
61
+ expect(subject.foo).to be new_value
62
+ end
63
+ end
64
+
65
+ context 'set with symbol' do
66
+ before(:each) do
67
+ subject[:foo] = value
68
+ end
69
+
70
+ it('overrides with method') do
71
+ subject.foo = new_value
72
+ expect(subject[:foo]).to be new_value
73
+ end
74
+
75
+ it('overrides with string') do
76
+ subject['foo'] = new_value
77
+ expect(subject[:foo]).to be new_value
78
+ end
79
+ end
80
+
81
+ context 'set with string' do
82
+ before(:each) do
83
+ subject['foo'] = value
84
+ end
85
+
86
+ it('overrides with method') do
87
+ subject.foo = new_value
88
+ expect(subject['foo']).to be new_value
89
+ end
90
+
91
+ it('overrides with symbol') do
92
+ subject[:foo] = new_value
93
+ expect(subject['foo']).to be new_value
94
+ end
95
+ end
96
+
97
+ context 'set with hash' do
98
+ it('overrides with method') do
99
+ hash_ros.foo = new_value
100
+ expect(hash_ros[:foo]).to be new_value
101
+
102
+ hash_ros.bar = new_symbol
103
+ expect(hash_ros['bar']).to be new_symbol
104
+ end
105
+
106
+ it('overrides with symbol') do
107
+ hash_ros[:bar] = new_symbol
108
+ expect(hash_ros['bar']).to be new_symbol
109
+ end
110
+
111
+ it('overrides with string') do
112
+ hash_ros['foo'] = new_value
113
+ expect(hash_ros[:foo]).to be new_value
114
+ end
115
+ end
116
+
117
+ context 'when preserve_original_keys is not enabled' do
118
+ context 'transforms original keys to symbols' do
119
+ subject(:recursive) { RecursiveOpenStruct.new(recursive_hash, recurse_over_arrays: true) }
120
+ let(:recursive_hash) { {:foo => [ {'bar' => [ { 'foo' => :bar} ] } ] } }
121
+ let(:symbolized_recursive_hash) { {:foo => [ {:bar => [ { :foo => :bar} ] } ] } }
122
+ let(:symbolized_modified_hash) { {:foo => [ {:bar => [ { :foo => :foo} ] } ] } }
123
+ let(:symbolized_hash) { Hash[hash.map{|(k,v)| [k.to_sym,v]}] }
124
+
125
+ specify 'after initialization' do
126
+ expect(hash_ros.to_h).to eq symbolized_hash
127
+ end
128
+
129
+ specify 'in recursive hashes' do
130
+ expect(recursive.to_h).to eq symbolized_recursive_hash
131
+ end
132
+
133
+ specify 'after resetting value' do
134
+ recursive.foo.first[:bar].first[:foo] = :foo
135
+ expect(recursive.to_h).to eq symbolized_modified_hash
136
+ end
137
+ end
138
+ end
139
+
140
+ context 'when preserve_original_keys is enabled' do
141
+ context 'preserves the original keys' do
142
+ subject(:recursive) { RecursiveOpenStruct.new(recursive_hash, recurse_over_arrays: true, preserve_original_keys: true) }
143
+ let(:recursive_hash) { {:foo => [ {'bar' => [ { 'foo' => :bar} ] } ] } }
144
+ let(:modified_hash) { {:foo => [ {'bar' => [ { 'foo' => :foo} ] } ] } }
145
+
146
+ let(:hash_ros_opts) { { preserve_original_keys: true }}
147
+
148
+ specify 'after initialization' do
149
+ expect(hash_ros.to_h).to eq hash
150
+ end
151
+
152
+ specify 'in recursive hashes' do
153
+ expect(recursive.to_h).to eq recursive_hash
154
+ end
155
+
156
+ specify 'after resetting value' do
157
+ recursive.foo.first[:bar].first[:foo] = :foo
158
+ expect(recursive.to_h).to eq modified_hash
159
+ end
160
+ end
161
+ end
162
+
163
+ end
164
+
165
+ end
166
+ end
@@ -0,0 +1,92 @@
1
+ require_relative '../spec_helper'
2
+ require 'recursive_open_struct'
3
+
4
+ describe RecursiveOpenStruct do
5
+ let(:hash) { {} }
6
+ subject(:ros) { RecursiveOpenStruct.new(hash) }
7
+
8
+ describe "behavior it inherits from OpenStruct" do
9
+ context 'when not initialized from anything' do
10
+ subject(:ros) { RecursiveOpenStruct.new }
11
+ it "can represent arbitrary data objects" do
12
+ ros.blah = "John Smith"
13
+ expect(ros.blah).to eq "John Smith"
14
+ end
15
+
16
+ it 'returns nil for missing attributes' do
17
+ expect(ros.foo).to be_nil
18
+ end
19
+ end
20
+
21
+ context 'when initialized with nil' do
22
+ let(:hash) { nil }
23
+ it 'returns nil for missing attributes' do
24
+ expect(ros.foo).to be_nil
25
+ end
26
+ end
27
+
28
+ context 'when initialized with an empty hash' do
29
+ it 'returns nil for missing attributes' do
30
+ expect(ros.foo).to be_nil
31
+ end
32
+ end
33
+
34
+ context "when initialized from a hash" do
35
+ let(:hash) { { :asdf => 'John Smith' } }
36
+
37
+ context 'that contains symbol keys' do
38
+ it "turns those symbol keys into method names" do
39
+ expect(ros.asdf).to eq "John Smith"
40
+ end
41
+ end
42
+
43
+ it "can modify an existing key" do
44
+ ros.asdf = "George Washington"
45
+ expect(ros.asdf).to eq "George Washington"
46
+ end
47
+
48
+ context 'that contains string keys' do
49
+ let(:hash) { { 'asdf' => 'John Smith' } }
50
+ it "turns those string keys into method names" do
51
+ expect(ros.asdf).to eq "John Smith"
52
+ end
53
+ end
54
+
55
+ context 'that contains keys that mirror existing private methods' do
56
+ let(:hash) { { test: :foo, rand: 'not a number' } }
57
+
58
+ # https://github.com/aetherknight/recursive-open-struct/issues/42
59
+ it 'handles subscript notation without calling the method name first (#42)' do
60
+ expect(ros['test']).to eq :foo
61
+ expect(ros['rand']).to eq 'not a number'
62
+
63
+ expect(ros.test).to eq :foo
64
+ expect(ros.rand).to eq 'not a number'
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+
71
+ describe "handling of arbitrary attributes" do
72
+ subject { RecursiveOpenStruct.new }
73
+ before(:each) do
74
+ subject.blah = "John Smith"
75
+ end
76
+
77
+ describe "#respond?" do
78
+ it { expect(subject).to respond_to :blah }
79
+ it { expect(subject).to respond_to :blah= }
80
+ it { expect(subject).to_not respond_to :asdf }
81
+ it { expect(subject).to_not respond_to :asdf= }
82
+ end # describe #respond?
83
+
84
+ describe "#methods" do
85
+ it { expect(subject.methods.map(&:to_sym)).to include :blah }
86
+ it { expect(subject.methods.map(&:to_sym)).to include :blah= }
87
+ it { expect(subject.methods.map(&:to_sym)).to_not include :asdf }
88
+ it { expect(subject.methods.map(&:to_sym)).to_not include :asdf= }
89
+ end # describe #methods
90
+ end # describe handling of arbitrary attributes
91
+ end # describe behavior it inherits from OpenStruct
92
+ end