recursive-open-struct-sd 1.0.2

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