recursive-open-struct 0.6.3 → 0.6.4
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 +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/recursive_open_struct.rb +30 -14
- data/lib/recursive_open_struct/ruby_19_backport.rb +27 -0
- data/lib/recursive_open_struct/version.rb +1 -1
- data/spec/recursive_open_struct/debug_inspect_spec.rb +70 -0
- data/spec/recursive_open_struct/indifferent_access_spec.rb +150 -0
- data/spec/recursive_open_struct/open_struct_behavior_spec.rb +62 -0
- data/spec/recursive_open_struct/ostruct_2_0_0_spec.rb +105 -0
- data/spec/recursive_open_struct/recursion_and_subclassing_spec.rb +14 -0
- data/spec/recursive_open_struct/recursion_spec.rb +284 -0
- data/spec/spec_helper.rb +1 -1
- metadata +15 -4
- data/spec/recursive_open_struct_spec.rb +0 -366
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7067232359a27f9453674a17396798cb42b085d4
|
4
|
+
data.tar.gz: 88fbfa54a7bff4548ab752ae681309afda312923
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50a3ea326db6026dfa417ecb81919ecda0e1b997753ea908a28c22281a9586d8b081b33dee0bacafb12ab65f568f91646237386d40d46a0bd070159a4bd3c594
|
7
|
+
data.tar.gz: bec50eceabca004115b14b176caf67253fa7b06414552d2051cc4d3502508c5a3fbab27f08027907aba1e7571391a03e92ac4c04a3260aa870b651f9eb8a785b
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
0.6.4 / 2015-05-20
|
2
|
+
==================
|
3
|
+
|
4
|
+
* FIX: Kris Dekeyser: Fix indifferent subscript access (string or symbol). Also
|
5
|
+
backported several ostruct methods for Ruby 1.9.x.
|
6
|
+
* FIX: Partial fix for allowing an array in a RecursiveOpenStruct tree to be
|
7
|
+
modified. However, methods such as to_hash are still broken.
|
8
|
+
|
1
9
|
0.6.3 / 2015-04-11
|
2
10
|
==================
|
3
11
|
|
@@ -3,8 +3,10 @@ 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
7
|
|
7
8
|
class RecursiveOpenStruct < OpenStruct
|
9
|
+
include Ruby19Backport if RUBY_VERSION =~ /\A1.9/
|
8
10
|
include DebugInspect
|
9
11
|
|
10
12
|
def initialize(hash={}, args={})
|
@@ -20,10 +22,6 @@ class RecursiveOpenStruct < OpenStruct
|
|
20
22
|
def initialize_copy(orig)
|
21
23
|
super
|
22
24
|
|
23
|
-
# Apply fix if necessary:
|
24
|
-
# https://github.com/ruby/ruby/commit/2d952c6d16ffe06a28bb1007e2cd1410c3db2d58
|
25
|
-
@table.each_key{|key| new_ostruct_member(key)} if RUBY_VERSION =~ /^1.9/
|
26
|
-
|
27
25
|
# deep copy the table to separate the two objects
|
28
26
|
@table = @deep_dup.call(orig.instance_variable_get(:@table))
|
29
27
|
# Forget any memoized sub-elements
|
@@ -41,28 +39,32 @@ class RecursiveOpenStruct < OpenStruct
|
|
41
39
|
end
|
42
40
|
|
43
41
|
def new_ostruct_member(name)
|
42
|
+
key_name = _get_key_from_table_ name
|
44
43
|
unless self.respond_to?(name)
|
45
44
|
class << self; self; end.class_eval do
|
46
45
|
define_method(name) do
|
47
|
-
v = @table[
|
46
|
+
v = @table[key_name]
|
48
47
|
if v.is_a?(Hash)
|
49
|
-
@sub_elements[
|
50
|
-
|
51
|
-
|
48
|
+
@sub_elements[key_name] ||= self.class.new(
|
49
|
+
v,
|
50
|
+
:recurse_over_arrays => @recurse_over_arrays,
|
51
|
+
:mutate_input_hash => true
|
52
|
+
)
|
52
53
|
elsif v.is_a?(Array) and @recurse_over_arrays
|
53
|
-
@sub_elements[
|
54
|
+
@sub_elements[key_name] ||= recurse_over_array(v)
|
55
|
+
@sub_elements[key_name] = recurse_over_array(@sub_elements[key_name])
|
54
56
|
else
|
55
57
|
v
|
56
58
|
end
|
57
59
|
end
|
58
60
|
define_method("#{name}=") do |x|
|
59
|
-
@sub_elements.delete(
|
60
|
-
modifiable[
|
61
|
+
@sub_elements.delete(key_name)
|
62
|
+
modifiable[key_name] = x
|
61
63
|
end
|
62
|
-
define_method("#{name}_as_a_hash") { @table[
|
64
|
+
define_method("#{name}_as_a_hash") { @table[key_name] }
|
63
65
|
end
|
64
66
|
end
|
65
|
-
|
67
|
+
key_name
|
66
68
|
end
|
67
69
|
|
68
70
|
# TODO: Make me private if/when we do an API-breaking change release
|
@@ -77,5 +79,19 @@ class RecursiveOpenStruct < OpenStruct
|
|
77
79
|
end
|
78
80
|
end
|
79
81
|
end
|
80
|
-
end
|
81
82
|
|
83
|
+
def delete_field(name)
|
84
|
+
sym = _get_key_from_table_(name)
|
85
|
+
singleton_class.__send__(:remove_method, sym, "#{sym}=")
|
86
|
+
@sub_elements.delete sym
|
87
|
+
@table.delete sym
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def _get_key_from_table_(name)
|
93
|
+
return name.to_s if @table.has_key?(name.to_s)
|
94
|
+
return name.to_sym if @table.has_key?(name.to_sym)
|
95
|
+
name
|
96
|
+
end
|
97
|
+
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,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,150 @@
|
|
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
|
+
subject(:hash_ros) { RecursiveOpenStruct.new(hash) }
|
13
|
+
context 'setting value with method' do
|
14
|
+
|
15
|
+
before(:each) do
|
16
|
+
subject.foo = value
|
17
|
+
end
|
18
|
+
|
19
|
+
it('allows getting with method') { expect(subject.foo).to be value }
|
20
|
+
it('allows getting with symbol') { expect(subject[:foo]).to be value }
|
21
|
+
it('allows getting with string') { expect(subject['foo']).to be value }
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'setting value with symbol' do
|
26
|
+
|
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
|
+
|
39
|
+
before(:each) do
|
40
|
+
subject['foo'] = value
|
41
|
+
end
|
42
|
+
|
43
|
+
it('allows getting with method') { expect(subject.foo).to be value }
|
44
|
+
it('allows getting with symbol') { expect(subject[:foo]).to be value }
|
45
|
+
it('allows getting with string') { expect(subject['foo']).to be value }
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'overwriting values' do
|
50
|
+
|
51
|
+
context 'set with method' do
|
52
|
+
|
53
|
+
before(:each) do
|
54
|
+
subject.foo = value
|
55
|
+
end
|
56
|
+
|
57
|
+
it('overrides with symbol') do
|
58
|
+
subject[:foo] = new_value
|
59
|
+
expect(subject.foo).to be new_value
|
60
|
+
end
|
61
|
+
|
62
|
+
it('overrides with string') do
|
63
|
+
subject['foo'] = new_value
|
64
|
+
expect(subject.foo).to be new_value
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'set with symbol' do
|
70
|
+
|
71
|
+
before(:each) do
|
72
|
+
subject[:foo] = value
|
73
|
+
end
|
74
|
+
|
75
|
+
it('overrides with method') do
|
76
|
+
subject.foo = new_value
|
77
|
+
expect(subject[:foo]).to be new_value
|
78
|
+
end
|
79
|
+
|
80
|
+
it('overrides with string') do
|
81
|
+
subject['foo'] = new_value
|
82
|
+
expect(subject[:foo]).to be new_value
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'set with string' do
|
88
|
+
|
89
|
+
before(:each) do
|
90
|
+
subject['foo'] = value
|
91
|
+
end
|
92
|
+
|
93
|
+
it('overrides with method') do
|
94
|
+
subject.foo = new_value
|
95
|
+
expect(subject['foo']).to be new_value
|
96
|
+
end
|
97
|
+
|
98
|
+
it('overrides with symbol') do
|
99
|
+
subject[:foo] = new_value
|
100
|
+
expect(subject['foo']).to be new_value
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'set with hash' do
|
106
|
+
|
107
|
+
it('overrides with method') do
|
108
|
+
hash_ros.foo = new_value
|
109
|
+
expect(hash_ros[:foo]).to be new_value
|
110
|
+
|
111
|
+
hash_ros.bar = new_symbol
|
112
|
+
expect(hash_ros['bar']).to be new_symbol
|
113
|
+
end
|
114
|
+
|
115
|
+
it('overrides with symbol') do
|
116
|
+
hash_ros[:bar] = new_symbol
|
117
|
+
expect(hash_ros['bar']).to be new_symbol
|
118
|
+
end
|
119
|
+
|
120
|
+
it('overrides with string') do
|
121
|
+
hash_ros['foo'] = new_value
|
122
|
+
expect(hash_ros[:foo]).to be new_value
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'keeps original keys' do
|
128
|
+
subject(:recursive) { RecursiveOpenStruct.new(recursive_hash, recurse_over_arrays: true) }
|
129
|
+
let(:recursive_hash) { {:foo => [ {'bar' => [ { 'foo' => :bar} ] } ] } }
|
130
|
+
let(:modified_hash) { {:foo => [ {'bar' => [ { 'foo' => :foo} ] } ] } }
|
131
|
+
|
132
|
+
it 'after initialization' do
|
133
|
+
expect(hash_ros.to_h).to eq hash
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'in recursive hashes' do
|
137
|
+
expect(recursive.to_h).to eq recursive_hash
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'after resetting value' do
|
141
|
+
recursive.foo.first[:bar].first[:foo] = :foo
|
142
|
+
expect(recursive.to_h).to eq modified_hash
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,62 @@
|
|
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
|
+
end
|
16
|
+
|
17
|
+
context "when initialized from a hash" do
|
18
|
+
let(:hash) { { :asdf => 'John Smith' } }
|
19
|
+
|
20
|
+
context 'that contains symbol keys' do
|
21
|
+
it "turns those symbol keys into method names" do
|
22
|
+
expect(ros.asdf).to eq "John Smith"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "can modify an existing key" do
|
27
|
+
ros.asdf = "George Washington"
|
28
|
+
expect(ros.asdf).to eq "George Washington"
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'that contains string keys' do
|
32
|
+
let(:hash) { { 'asdf' => 'John Smith' } }
|
33
|
+
it "turns those string keys into method names" do
|
34
|
+
expect(ros.asdf).to eq "John Smith"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
describe "handling of arbitrary attributes" do
|
42
|
+
subject { RecursiveOpenStruct.new }
|
43
|
+
before(:each) do
|
44
|
+
subject.blah = "John Smith"
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#respond?" do
|
48
|
+
it { expect(subject).to respond_to :blah }
|
49
|
+
it { expect(subject).to respond_to :blah= }
|
50
|
+
it { expect(subject).to_not respond_to :asdf }
|
51
|
+
it { expect(subject).to_not respond_to :asdf= }
|
52
|
+
end # describe #respond?
|
53
|
+
|
54
|
+
describe "#methods" do
|
55
|
+
it { expect(subject.methods.map(&:to_sym)).to include :blah }
|
56
|
+
it { expect(subject.methods.map(&:to_sym)).to include :blah= }
|
57
|
+
it { expect(subject.methods.map(&:to_sym)).to_not include :asdf }
|
58
|
+
it { expect(subject.methods.map(&:to_sym)).to_not include :asdf= }
|
59
|
+
end # describe #methods
|
60
|
+
end # describe handling of arbitrary attributes
|
61
|
+
end # describe behavior it inherits from OpenStruct
|
62
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'recursive_open_struct'
|
3
|
+
|
4
|
+
describe RecursiveOpenStruct do
|
5
|
+
|
6
|
+
let(:hash) { {:foo => 'foo', 'bar' => :bar} }
|
7
|
+
subject(:ros) { RecursiveOpenStruct.new(hash) }
|
8
|
+
|
9
|
+
describe "OpenStruct 2.0 methods" do
|
10
|
+
|
11
|
+
context "Hash style setter" do
|
12
|
+
|
13
|
+
it "method exists" do
|
14
|
+
expect(ros.respond_to?('[]=')).to be_truthy
|
15
|
+
end
|
16
|
+
|
17
|
+
it "changes the value" do
|
18
|
+
ros[:foo] = :foo
|
19
|
+
ros.foo = :foo
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
context "delete_field" do
|
25
|
+
|
26
|
+
before(:each) { ros.delete_field :foo }
|
27
|
+
|
28
|
+
it "removes the value" do
|
29
|
+
expect(ros.foo).to be_nil
|
30
|
+
expect(ros.to_h).to_not include(:foo)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "removes the getter method" do
|
34
|
+
is_expected.to_not respond_to :foo
|
35
|
+
end
|
36
|
+
|
37
|
+
it "removes the setter method" do
|
38
|
+
expect(ros.respond_to? 'foo=').to be_falsey
|
39
|
+
end
|
40
|
+
|
41
|
+
it "works with indifferent access" do
|
42
|
+
expect(ros.delete_field :bar).to eq :bar
|
43
|
+
is_expected.to_not respond_to :bar
|
44
|
+
is_expected.to_not respond_to 'bar='
|
45
|
+
expect(ros.to_h).to be_empty
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
context "eql?" do
|
51
|
+
subject(:new_ros) { ros.dup }
|
52
|
+
|
53
|
+
context "with identical ROS" do
|
54
|
+
subject { ros }
|
55
|
+
it { is_expected.to be_eql ros }
|
56
|
+
end
|
57
|
+
|
58
|
+
context "with similar ROS" do
|
59
|
+
subject { RecursiveOpenStruct.new(hash) }
|
60
|
+
it { is_expected.to be_eql ros }
|
61
|
+
end
|
62
|
+
|
63
|
+
context "with same Hash" do
|
64
|
+
subject { RecursiveOpenStruct.new(hash, recurse_over_arrays: true) }
|
65
|
+
it { is_expected.to be_eql ros }
|
66
|
+
end
|
67
|
+
|
68
|
+
context "with duplicated ROS" do
|
69
|
+
subject { ros.dup }
|
70
|
+
|
71
|
+
it "fails on different value" do
|
72
|
+
subject.foo = 'bar'
|
73
|
+
is_expected.not_to be_eql ros
|
74
|
+
end
|
75
|
+
|
76
|
+
it "fails on missing field" do
|
77
|
+
subject.delete_field :bar
|
78
|
+
is_expected.not_to be_eql ros
|
79
|
+
end
|
80
|
+
|
81
|
+
it "fails on added field" do
|
82
|
+
subject.baz = :baz
|
83
|
+
is_expected.not_to be_eql ros
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
context "hash" do
|
91
|
+
it "calculates table hash" do
|
92
|
+
expect(ros.hash).to be ros.instance_variable_get('@table').hash
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
context "each_pair" do
|
98
|
+
it "iterates over hash keys" do
|
99
|
+
expect(ros.each_pair).to match hash.each_pair
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'recursive_open_struct'
|
3
|
+
|
4
|
+
describe RecursiveOpenStruct do
|
5
|
+
describe "subclassing RecursiveOpenStruct" do
|
6
|
+
let(:subclass) { Class.new(RecursiveOpenStruct) }
|
7
|
+
|
8
|
+
subject(:rossc) { subclass.new({ :one => [{:two => :three}] }, recurse_over_arrays: true) }
|
9
|
+
|
10
|
+
specify "nested objects use the subclass of the parent" do
|
11
|
+
expect(rossc.one.first.class).to eq subclass
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,284 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'recursive_open_struct'
|
3
|
+
|
4
|
+
describe RecursiveOpenStruct do
|
5
|
+
|
6
|
+
describe "recursive behavior" do
|
7
|
+
let(:h) { { :blah => { :another => 'value' } } }
|
8
|
+
subject { RecursiveOpenStruct.new(h) }
|
9
|
+
|
10
|
+
it "can convert the entire hash tree back into a hash" do
|
11
|
+
blank_obj = Object.new
|
12
|
+
h = {:asdf => 'John Smith', :foo => [{:bar => blank_obj}, {:baz => nil}]}
|
13
|
+
ros = RecursiveOpenStruct.new(h)
|
14
|
+
|
15
|
+
expect(ros.to_h).to eq h
|
16
|
+
expect(ros.to_hash).to eq h
|
17
|
+
end
|
18
|
+
|
19
|
+
it "returns accessed hashes as RecursiveOpenStructs instead of hashes" do
|
20
|
+
expect(subject.blah.another).to eq 'value'
|
21
|
+
end
|
22
|
+
|
23
|
+
it "handles subscript notation the same way as dotted notation" do
|
24
|
+
expect(subject.blah.another).to eq subject[:blah].another
|
25
|
+
end
|
26
|
+
|
27
|
+
it "uses #key_as_a_hash to return key as a Hash" do
|
28
|
+
expect(subject.blah_as_a_hash).to eq({ :another => 'value' })
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "handling loops in the original Hashes" do
|
32
|
+
let(:h1) { { :a => 'a'} }
|
33
|
+
let(:h2) { { :a => 'b', :h1 => h1 } }
|
34
|
+
before(:each) { h1[:h2] = h2 }
|
35
|
+
|
36
|
+
subject { RecursiveOpenStruct.new(h2) }
|
37
|
+
|
38
|
+
it { expect(subject.h1.a).to eq 'a' }
|
39
|
+
it { expect(subject.h1.h2.a).to eq 'b' }
|
40
|
+
it { expect(subject.h1.h2.h1.a).to eq 'a' }
|
41
|
+
it { expect(subject.h1.h2.h1.h2.a).to eq 'b' }
|
42
|
+
it { expect(subject.h1).to eq subject.h1.h2.h1 }
|
43
|
+
it { expect(subject.h1).to_not eq subject.h1.h2 }
|
44
|
+
end # describe handling loops in the origin Hashes
|
45
|
+
|
46
|
+
it "can modify a key of a sub-element" do
|
47
|
+
h = {
|
48
|
+
:blah => {
|
49
|
+
:blargh => 'Brad'
|
50
|
+
}
|
51
|
+
}
|
52
|
+
ros = RecursiveOpenStruct.new(h)
|
53
|
+
ros.blah.blargh = "Janet"
|
54
|
+
|
55
|
+
expect(ros.blah.blargh).to eq "Janet"
|
56
|
+
end
|
57
|
+
|
58
|
+
context "after a sub-element has been modified" do
|
59
|
+
let(:hash) do
|
60
|
+
{ :blah => { :blargh => "Brad" }, :some_array => [ 1, 2, 3] }
|
61
|
+
end
|
62
|
+
let(:updated_hash) do
|
63
|
+
{ :blah => { :blargh => "Janet" }, :some_array => [ 1, 2, 3] }
|
64
|
+
end
|
65
|
+
|
66
|
+
subject { RecursiveOpenStruct.new(hash) }
|
67
|
+
|
68
|
+
before(:each) { subject.blah.blargh = "Janet" }
|
69
|
+
|
70
|
+
describe ".to_h" do
|
71
|
+
it "returns a hash tree that contains those modifications" do
|
72
|
+
expect(subject.to_h).to eq updated_hash
|
73
|
+
end
|
74
|
+
|
75
|
+
specify "modifying the returned hash tree does not modify the ROS" do
|
76
|
+
subject.to_h[:blah][:blargh] = "Dr Scott"
|
77
|
+
|
78
|
+
expect(subject.blah.blargh).to eq "Janet"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it "does not mutate the original hash tree passed to the constructor" do
|
83
|
+
expect(hash[:blah][:blargh]).to eq 'Brad'
|
84
|
+
end
|
85
|
+
|
86
|
+
it "limits the deep-copy to the initial hash tree" do
|
87
|
+
subject.some_array[0] = 4
|
88
|
+
|
89
|
+
expect(hash[:some_array][0]).to eq 4
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#dup" do
|
93
|
+
let(:duped_subject) { subject.dup }
|
94
|
+
|
95
|
+
it "preserves sub-element modifications" do
|
96
|
+
expect(duped_subject.blah.blargh).to eq subject.blah.blargh
|
97
|
+
end
|
98
|
+
|
99
|
+
it "allows the copy's sub-elements to be modified independently from the original's" do
|
100
|
+
expect(subject.blah.blargh).to eq "Janet"
|
101
|
+
|
102
|
+
duped_subject.blah.blargh = "Dr. Scott"
|
103
|
+
|
104
|
+
expect(subject.blah.blargh).to eq "Janet"
|
105
|
+
expect(duped_subject.blah.blargh).to eq "Dr. Scott"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "when memoizing and then modifying entire recursive structures" do
|
111
|
+
subject do
|
112
|
+
RecursiveOpenStruct.new(
|
113
|
+
{ :blah => original_blah }, :recurse_over_arrays => true)
|
114
|
+
end
|
115
|
+
|
116
|
+
before(:each) { subject.blah } # enforce memoization
|
117
|
+
|
118
|
+
context "when modifying an entire Hash" do
|
119
|
+
let(:original_blah) { { :a => 'A', :b => 'B' } }
|
120
|
+
let(:new_blah) { { :something_new => "C" } }
|
121
|
+
|
122
|
+
before(:each) { subject.blah = new_blah }
|
123
|
+
|
124
|
+
it "returns the modified value instead of the memoized one" do
|
125
|
+
expect(subject.blah.something_new).to eq "C"
|
126
|
+
end
|
127
|
+
|
128
|
+
specify "the old value no longer exists" do
|
129
|
+
expect(subject.blah.a).to be_nil
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when modifying an entire Array" do
|
134
|
+
let(:original_blah) { [1, 2, 3] }
|
135
|
+
|
136
|
+
it "returns the modified value instead of the memoized one" do
|
137
|
+
new_blah = [4, 5, 6]
|
138
|
+
subject.blah = new_blah
|
139
|
+
expect(subject.blah).to eq new_blah
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'recursing over arrays' do
|
145
|
+
let(:blah_list) { [ { :foo => '1' }, { :foo => '2' }, 'baz' ] }
|
146
|
+
let(:h) { { :blah => blah_list } }
|
147
|
+
|
148
|
+
context "when recursing over arrays is enabled" do
|
149
|
+
subject { RecursiveOpenStruct.new(h, :recurse_over_arrays => true) }
|
150
|
+
|
151
|
+
it { expect(subject.blah.length).to eq 3 }
|
152
|
+
it { expect(subject.blah[0].foo).to eq '1' }
|
153
|
+
it { expect(subject.blah[1].foo).to eq '2' }
|
154
|
+
it { expect(subject.blah_as_a_hash).to eq blah_list }
|
155
|
+
it { expect(subject.blah[2]).to eq 'baz' }
|
156
|
+
|
157
|
+
context "when an inner value changes" do
|
158
|
+
let(:updated_blah_list) { [ { :foo => '1' }, { :foo => 'Dr Scott' }, 'baz' ] }
|
159
|
+
let(:updated_h) { { :blah => updated_blah_list } }
|
160
|
+
|
161
|
+
before(:each) { subject.blah[1].foo = "Dr Scott" }
|
162
|
+
|
163
|
+
it "Retains changes across Array lookups" do
|
164
|
+
expect(subject.blah[1].foo).to eq "Dr Scott"
|
165
|
+
end
|
166
|
+
|
167
|
+
it "propagates the changes through to .to_h across Array lookups" do
|
168
|
+
expect(subject.to_h).to eq({
|
169
|
+
:blah => [ { :foo => '1' }, { :foo => "Dr Scott" }, 'baz' ]
|
170
|
+
})
|
171
|
+
end
|
172
|
+
|
173
|
+
it "deep-copies hashes within Arrays" do
|
174
|
+
subject.to_h[:blah][1][:foo] = "Rocky"
|
175
|
+
|
176
|
+
expect(subject.blah[1].foo).to eq "Dr Scott"
|
177
|
+
end
|
178
|
+
|
179
|
+
it "does not mutate the input hash passed to the constructor" do
|
180
|
+
expect(h[:blah][1][:foo]).to eq '2'
|
181
|
+
end
|
182
|
+
|
183
|
+
it "the deep copy recurses over Arrays as well" do
|
184
|
+
expect(h[:blah][1][:foo]).to eq '2'
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "#dup" do
|
188
|
+
let(:duped_subject) { subject.dup }
|
189
|
+
|
190
|
+
it "preserves sub-element modifications" do
|
191
|
+
expect(duped_subject.blah[1].foo).to eq subject.blah[1].foo
|
192
|
+
end
|
193
|
+
|
194
|
+
it "allows the copy's sub-elements to be modified independently from the original's" do
|
195
|
+
duped_subject.blah[1].foo = "Rocky"
|
196
|
+
|
197
|
+
expect(duped_subject.blah[1].foo).to eq "Rocky"
|
198
|
+
expect(subject.blah[1].foo).to eq "Dr Scott"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context "when array is nested deeper" do
|
204
|
+
let(:deep_hash) { { :foo => { :blah => blah_list } } }
|
205
|
+
subject { RecursiveOpenStruct.new(deep_hash, :recurse_over_arrays => true) }
|
206
|
+
|
207
|
+
it { expect(subject.foo.blah.length).to eq 3 }
|
208
|
+
it "Retains changes across Array lookups" do
|
209
|
+
subject.foo.blah[1].foo = "Dr Scott"
|
210
|
+
expect(subject.foo.blah[1].foo).to eq "Dr Scott"
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
context "when array is in an array" do
|
216
|
+
let(:haah) { { :blah => [ blah_list ] } }
|
217
|
+
subject { RecursiveOpenStruct.new(haah, :recurse_over_arrays => true) }
|
218
|
+
|
219
|
+
it { expect(subject.blah.length).to eq 1 }
|
220
|
+
it { expect(subject.blah[0].length).to eq 3 }
|
221
|
+
it "Retains changes across Array lookups" do
|
222
|
+
subject.blah[0][1].foo = "Dr Scott"
|
223
|
+
|
224
|
+
expect(subject.blah[0][1].foo).to eq "Dr Scott"
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
end # when recursing over arrays is enabled
|
230
|
+
|
231
|
+
context "when recursing over arrays is disabled" do
|
232
|
+
subject { RecursiveOpenStruct.new(h) }
|
233
|
+
|
234
|
+
it { expect(subject.blah.length).to eq 3 }
|
235
|
+
it { expect(subject.blah[0]).to eq({ :foo => '1' }) }
|
236
|
+
it { expect(subject.blah[0][:foo]).to eq '1' }
|
237
|
+
end # when recursing over arrays is disabled
|
238
|
+
|
239
|
+
describe 'modifying an array and recursing over it' do
|
240
|
+
let(:h) { {} }
|
241
|
+
subject { RecursiveOpenStruct.new(h, recurse_over_arrays: true) }
|
242
|
+
|
243
|
+
context 'when adding an array with hashes into the tree' do
|
244
|
+
before(:each) do
|
245
|
+
subject.mystery = {}
|
246
|
+
subject.mystery.science = [{ theatre: 9000 }]
|
247
|
+
end
|
248
|
+
|
249
|
+
it "ROS's it" do
|
250
|
+
expect(subject.mystery.science[0].theatre).to eq 9000
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'when appending a hash to an array' do
|
255
|
+
before(:each) do
|
256
|
+
subject.mystery = {}
|
257
|
+
subject.mystery.science = []
|
258
|
+
subject.mystery.science << { theatre: 9000 }
|
259
|
+
end
|
260
|
+
|
261
|
+
it "ROS's it" do
|
262
|
+
expect(subject.mystery.science[0].theatre).to eq 9000
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context 'after appending a hash to an array' do
|
267
|
+
before(:each) do
|
268
|
+
subject.mystery = {}
|
269
|
+
subject.mystery.science = []
|
270
|
+
subject.mystery.science[0] = {}
|
271
|
+
end
|
272
|
+
|
273
|
+
it "can have new values be set" do
|
274
|
+
expect do
|
275
|
+
subject.mystery.science[0].theatre = 9000
|
276
|
+
end.to_not raise_error
|
277
|
+
|
278
|
+
expect(subject.mystery.science[0].theatre).to eq 9000
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end # modifying an array and then recursing
|
282
|
+
end # recursing over arrays
|
283
|
+
end # recursive behavior
|
284
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -15,5 +15,5 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
|
15
15
|
RSpec.configure do |config|
|
16
16
|
config.run_all_when_everything_filtered = true
|
17
17
|
config.filter_run :focus
|
18
|
-
config.expect_with(:rspec) { |c| c.syntax = :should }
|
18
|
+
# config.expect_with(:rspec) { |c| c.syntax = :should }
|
19
19
|
end
|
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: 0.6.
|
4
|
+
version: 0.6.4
|
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: 2015-
|
11
|
+
date: 2015-05-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -126,9 +126,15 @@ files:
|
|
126
126
|
- lib/recursive_open_struct.rb
|
127
127
|
- lib/recursive_open_struct/debug_inspect.rb
|
128
128
|
- lib/recursive_open_struct/deep_dup.rb
|
129
|
+
- lib/recursive_open_struct/ruby_19_backport.rb
|
129
130
|
- lib/recursive_open_struct/version.rb
|
130
131
|
- recursive-open-struct.gemspec
|
131
|
-
- spec/
|
132
|
+
- spec/recursive_open_struct/debug_inspect_spec.rb
|
133
|
+
- spec/recursive_open_struct/indifferent_access_spec.rb
|
134
|
+
- spec/recursive_open_struct/open_struct_behavior_spec.rb
|
135
|
+
- spec/recursive_open_struct/ostruct_2_0_0_spec.rb
|
136
|
+
- spec/recursive_open_struct/recursion_and_subclassing_spec.rb
|
137
|
+
- spec/recursive_open_struct/recursion_spec.rb
|
132
138
|
- spec/spec_helper.rb
|
133
139
|
homepage: http://github.com/aetherknight/recursive-open-struct
|
134
140
|
licenses:
|
@@ -155,5 +161,10 @@ signing_key:
|
|
155
161
|
specification_version: 4
|
156
162
|
summary: OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs
|
157
163
|
test_files:
|
158
|
-
- spec/
|
164
|
+
- spec/recursive_open_struct/debug_inspect_spec.rb
|
165
|
+
- spec/recursive_open_struct/indifferent_access_spec.rb
|
166
|
+
- spec/recursive_open_struct/open_struct_behavior_spec.rb
|
167
|
+
- spec/recursive_open_struct/ostruct_2_0_0_spec.rb
|
168
|
+
- spec/recursive_open_struct/recursion_and_subclassing_spec.rb
|
169
|
+
- spec/recursive_open_struct/recursion_spec.rb
|
159
170
|
- spec/spec_helper.rb
|
@@ -1,366 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
-
require 'recursive_open_struct'
|
3
|
-
|
4
|
-
describe RecursiveOpenStruct do
|
5
|
-
|
6
|
-
describe "behavior it inherits from OpenStruct" do
|
7
|
-
|
8
|
-
it "can represent arbitrary data objects" do
|
9
|
-
ros = RecursiveOpenStruct.new
|
10
|
-
ros.blah = "John Smith"
|
11
|
-
ros.blah.should == "John Smith"
|
12
|
-
end
|
13
|
-
|
14
|
-
context "can be created from a hash" do
|
15
|
-
it "and keys are instanced as symbol" do
|
16
|
-
h = { :asdf => 'John Smith' }
|
17
|
-
ros = RecursiveOpenStruct.new(h)
|
18
|
-
ros.asdf.should == "John Smith"
|
19
|
-
end
|
20
|
-
|
21
|
-
it "and keys are instanced as string" do
|
22
|
-
h = { "asdf" => 'John Smith' }
|
23
|
-
ros = RecursiveOpenStruct.new(h)
|
24
|
-
ros.asdf.should == "John Smith"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
it "can modify an existing key" do
|
29
|
-
h = { :blah => 'John Smith' }
|
30
|
-
ros = RecursiveOpenStruct.new(h)
|
31
|
-
ros.blah = "George Washington"
|
32
|
-
ros.blah.should == "George Washington"
|
33
|
-
end
|
34
|
-
|
35
|
-
describe "handling of arbitrary attributes" do
|
36
|
-
subject { RecursiveOpenStruct.new }
|
37
|
-
before(:each) do
|
38
|
-
subject.blah = "John Smith"
|
39
|
-
end
|
40
|
-
|
41
|
-
describe "#respond?" do
|
42
|
-
it { subject.should respond_to :blah }
|
43
|
-
it { subject.should respond_to :blah= }
|
44
|
-
it { subject.should_not respond_to :asdf }
|
45
|
-
it { subject.should_not respond_to :asdf= }
|
46
|
-
end # describe #respond?
|
47
|
-
|
48
|
-
describe "#methods" do
|
49
|
-
it { subject.methods.map(&:to_sym).should include :blah }
|
50
|
-
it { subject.methods.map(&:to_sym).should include :blah= }
|
51
|
-
it { subject.methods.map(&:to_sym).should_not include :asdf }
|
52
|
-
it { subject.methods.map(&:to_sym).should_not include :asdf= }
|
53
|
-
end # describe #methods
|
54
|
-
end # describe handling of arbitrary attributes
|
55
|
-
end # describe behavior it inherits from OpenStruct
|
56
|
-
|
57
|
-
describe "improvements on OpenStruct" do
|
58
|
-
it "can be converted back to a hash" do
|
59
|
-
blank_obj = Object.new
|
60
|
-
h = {:asdf => 'John Smith', :foo => [{:bar => blank_obj}, {:baz => nil}]}
|
61
|
-
ros = RecursiveOpenStruct.new(h)
|
62
|
-
ros.to_h.should == h
|
63
|
-
ros.to_hash.should == h
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
describe "recursive behavior" do
|
68
|
-
let(:h) { { :blah => { :another => 'value' } } }
|
69
|
-
subject { RecursiveOpenStruct.new(h) }
|
70
|
-
|
71
|
-
it "returns accessed hashes as RecursiveOpenStructs instead of hashes" do
|
72
|
-
subject.blah.another.should == 'value'
|
73
|
-
end
|
74
|
-
|
75
|
-
it "handles subscript notation the same way as dotted notation" do
|
76
|
-
subject.blah.another.should == subject[:blah].another
|
77
|
-
end
|
78
|
-
|
79
|
-
it "uses #key_as_a_hash to return key as a Hash" do
|
80
|
-
subject.blah_as_a_hash.should == { :another => 'value' }
|
81
|
-
end
|
82
|
-
|
83
|
-
describe "handling loops in the origin Hashes" do
|
84
|
-
let(:h1) { { :a => 'a'} }
|
85
|
-
let(:h2) { { :a => 'b', :h1 => h1 } }
|
86
|
-
before(:each) { h1[:h2] = h2 }
|
87
|
-
|
88
|
-
subject { RecursiveOpenStruct.new(h2) }
|
89
|
-
|
90
|
-
it { subject.h1.a.should == 'a' }
|
91
|
-
it { subject.h1.h2.a.should == 'b' }
|
92
|
-
it { subject.h1.h2.h1.a.should == 'a' }
|
93
|
-
it { subject.h1.h2.h1.h2.a.should == 'b' }
|
94
|
-
it { subject.h1.should == subject.h1.h2.h1 }
|
95
|
-
it { subject.h1.should_not == subject.h1.h2 }
|
96
|
-
end # describe handling loops in the origin Hashes
|
97
|
-
|
98
|
-
it "can modify a key of a sub-element" do
|
99
|
-
h = {
|
100
|
-
:blah => {
|
101
|
-
:blargh => 'Brad'
|
102
|
-
}
|
103
|
-
}
|
104
|
-
ros = RecursiveOpenStruct.new(h)
|
105
|
-
ros.blah.blargh = "Janet"
|
106
|
-
ros.blah.blargh.should == "Janet"
|
107
|
-
end
|
108
|
-
|
109
|
-
context "after a sub-element has been modified" do
|
110
|
-
let(:hash) do
|
111
|
-
{ :blah => { :blargh => "Brad" }, :some_array => [ 1, 2, 3] }
|
112
|
-
end
|
113
|
-
let(:updated_hash) do
|
114
|
-
{ :blah => { :blargh => "Janet" }, :some_array => [ 1, 2, 3] }
|
115
|
-
end
|
116
|
-
|
117
|
-
subject { RecursiveOpenStruct.new(hash) }
|
118
|
-
|
119
|
-
before(:each) { subject.blah.blargh = "Janet" }
|
120
|
-
|
121
|
-
describe ".to_h" do
|
122
|
-
it "returns a hash tree that contains those modifications" do
|
123
|
-
subject.to_h.should == updated_hash
|
124
|
-
end
|
125
|
-
|
126
|
-
specify "modifying the returned hash tree does not modify the ROS" do
|
127
|
-
subject.to_h[:blah][:blargh] = "Dr Scott"
|
128
|
-
|
129
|
-
subject.blah.blargh.should == "Janet"
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
it "does not mutate the original hash tree passed to the constructor" do
|
134
|
-
hash[:blah][:blargh].should == 'Brad'
|
135
|
-
end
|
136
|
-
|
137
|
-
it "limits the deep-copy to the initial hash tree" do
|
138
|
-
subject.some_array[0] = 4
|
139
|
-
|
140
|
-
hash[:some_array][0].should == 4
|
141
|
-
end
|
142
|
-
|
143
|
-
describe "#dup" do
|
144
|
-
let(:duped_subject) { subject.dup }
|
145
|
-
|
146
|
-
it "preserves sub-element modifications" do
|
147
|
-
duped_subject.blah.blargh.should == subject.blah.blargh
|
148
|
-
end
|
149
|
-
|
150
|
-
it "allows the copy's sub-elements to be modified independently from the original's" do
|
151
|
-
subject.blah.blargh.should == "Janet"
|
152
|
-
duped_subject.blah.blargh = "Dr. Scott"
|
153
|
-
|
154
|
-
duped_subject.blah.blargh.should == "Dr. Scott"
|
155
|
-
subject.blah.blargh.should == "Janet"
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
context "when memoizing and then modifying entire recursive structures" do
|
161
|
-
subject do
|
162
|
-
RecursiveOpenStruct.new(
|
163
|
-
{ :blah => original_blah }, :recurse_over_arrays => true)
|
164
|
-
end
|
165
|
-
|
166
|
-
before(:each) { subject.blah } # enforce memoization
|
167
|
-
|
168
|
-
context "when modifying an entire Hash" do
|
169
|
-
let(:original_blah) { { :a => 'A', :b => 'B' } }
|
170
|
-
let(:new_blah) { { :something_new => "C" } }
|
171
|
-
|
172
|
-
before(:each) { subject.blah = new_blah }
|
173
|
-
|
174
|
-
it "returns the modified value instead of the memoized one" do
|
175
|
-
subject.blah.something_new.should == "C"
|
176
|
-
end
|
177
|
-
|
178
|
-
specify "the old value no longer exists" do
|
179
|
-
subject.blah.a.should be_nil
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
context "when modifying an entire Array" do
|
184
|
-
let(:original_blah) { [1, 2, 3] }
|
185
|
-
|
186
|
-
it "returns the modified value instead of the memoized one" do
|
187
|
-
new_blah = [4, 5, 6]
|
188
|
-
subject.blah = new_blah
|
189
|
-
subject.blah.should == new_blah
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
describe 'recursing over arrays' do
|
195
|
-
let(:blah_list) { [ { :foo => '1' }, { :foo => '2' }, 'baz' ] }
|
196
|
-
let(:h) { { :blah => blah_list } }
|
197
|
-
|
198
|
-
context "when recursing over arrays is enabled" do
|
199
|
-
subject { RecursiveOpenStruct.new(h, :recurse_over_arrays => true) }
|
200
|
-
|
201
|
-
it { subject.blah.length.should == 3 }
|
202
|
-
it { subject.blah[0].foo.should == '1' }
|
203
|
-
it { subject.blah[1].foo.should == '2' }
|
204
|
-
it { subject.blah_as_a_hash.should == blah_list }
|
205
|
-
it { subject.blah[2].should == 'baz' }
|
206
|
-
|
207
|
-
context "when an inner value changes" do
|
208
|
-
let(:updated_blah_list) { [ { :foo => '1' }, { :foo => 'Dr Scott' }, 'baz' ] }
|
209
|
-
let(:updated_h) { { :blah => updated_blah_list } }
|
210
|
-
|
211
|
-
before(:each) { subject.blah[1].foo = "Dr Scott" }
|
212
|
-
|
213
|
-
it "Retains changes across Array lookups" do
|
214
|
-
subject.blah[1].foo.should == "Dr Scott"
|
215
|
-
end
|
216
|
-
|
217
|
-
it "propagates the changes through to .to_h across Array lookups" do
|
218
|
-
subject.to_h.should == {
|
219
|
-
:blah => [ { :foo => '1' }, { :foo => "Dr Scott" }, 'baz' ]
|
220
|
-
}
|
221
|
-
end
|
222
|
-
|
223
|
-
it "deep-copies hashes within Arrays" do
|
224
|
-
subject.to_h[:blah][1][:foo] = "Rocky"
|
225
|
-
|
226
|
-
subject.blah[1].foo.should == "Dr Scott"
|
227
|
-
end
|
228
|
-
|
229
|
-
it "does not mutate the input hash passed to the constructor" do
|
230
|
-
h[:blah][1][:foo].should == '2'
|
231
|
-
end
|
232
|
-
|
233
|
-
it "the deep copy recurses over Arrays as well" do
|
234
|
-
h[:blah][1][:foo].should == '2'
|
235
|
-
end
|
236
|
-
|
237
|
-
describe "#dup" do
|
238
|
-
let(:duped_subject) { subject.dup }
|
239
|
-
|
240
|
-
it "preserves sub-element modifications" do
|
241
|
-
duped_subject.blah[1].foo.should == subject.blah[1].foo
|
242
|
-
end
|
243
|
-
|
244
|
-
it "allows the copy's sub-elements to be modified independently from the original's" do
|
245
|
-
duped_subject.blah[1].foo = "Rocky"
|
246
|
-
|
247
|
-
duped_subject.blah[1].foo.should == "Rocky"
|
248
|
-
subject.blah[1].foo.should == "Dr Scott"
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
context "when array is nested deeper" do
|
254
|
-
let(:deep_hash) { { :foo => { :blah => blah_list } } }
|
255
|
-
subject { RecursiveOpenStruct.new(deep_hash, :recurse_over_arrays => true) }
|
256
|
-
|
257
|
-
it { subject.foo.blah.length.should == 3 }
|
258
|
-
it "Retains changes across Array lookups" do
|
259
|
-
subject.foo.blah[1].foo = "Dr Scott"
|
260
|
-
subject.foo.blah[1].foo.should == "Dr Scott"
|
261
|
-
end
|
262
|
-
|
263
|
-
end
|
264
|
-
|
265
|
-
context "when array is in an array" do
|
266
|
-
let(:haah) { { :blah => [ blah_list ] } }
|
267
|
-
subject { RecursiveOpenStruct.new(haah, :recurse_over_arrays => true) }
|
268
|
-
|
269
|
-
it { subject.blah.length.should == 1 }
|
270
|
-
it { subject.blah[0].length.should == 3 }
|
271
|
-
it "Retains changes across Array lookups" do
|
272
|
-
subject.blah[0][1].foo = "Dr Scott"
|
273
|
-
subject.blah[0][1].foo.should == "Dr Scott"
|
274
|
-
end
|
275
|
-
|
276
|
-
end
|
277
|
-
|
278
|
-
end # when recursing over arrays is enabled
|
279
|
-
|
280
|
-
context "when recursing over arrays is disabled" do
|
281
|
-
subject { RecursiveOpenStruct.new(h) }
|
282
|
-
|
283
|
-
it { subject.blah.length.should == 3 }
|
284
|
-
it { subject.blah[0].should == { :foo => '1' } }
|
285
|
-
it { subject.blah[0][:foo].should == '1' }
|
286
|
-
end # when recursing over arrays is disabled
|
287
|
-
|
288
|
-
end # recursing over arrays
|
289
|
-
end # recursive behavior
|
290
|
-
|
291
|
-
describe "additionnel features" do
|
292
|
-
|
293
|
-
before(:each) do
|
294
|
-
h1 = { :a => 'a'}
|
295
|
-
h2 = { :a => 'b', :h1 => h1 }
|
296
|
-
h1[:h2] = h2
|
297
|
-
@ros = RecursiveOpenStruct.new(h2)
|
298
|
-
end
|
299
|
-
|
300
|
-
it "should have a simple way of display" do
|
301
|
-
@output = <<-QUOTE
|
302
|
-
a = "b"
|
303
|
-
h1.
|
304
|
-
a = "a"
|
305
|
-
h2.
|
306
|
-
a = "b"
|
307
|
-
h1.
|
308
|
-
a = "a"
|
309
|
-
h2.
|
310
|
-
a = "b"
|
311
|
-
h1.
|
312
|
-
a = "a"
|
313
|
-
h2.
|
314
|
-
a = "b"
|
315
|
-
h1.
|
316
|
-
a = "a"
|
317
|
-
h2.
|
318
|
-
a = "b"
|
319
|
-
h1.
|
320
|
-
a = "a"
|
321
|
-
h2.
|
322
|
-
a = "b"
|
323
|
-
h1.
|
324
|
-
a = "a"
|
325
|
-
h2.
|
326
|
-
(recursion limit reached)
|
327
|
-
QUOTE
|
328
|
-
@io = StringIO.new
|
329
|
-
@ros.debug_inspect(@io)
|
330
|
-
@io.string.should match /^a = "b"$/
|
331
|
-
@io.string.should match /^h1\.$/
|
332
|
-
@io.string.should match /^ a = "a"$/
|
333
|
-
@io.string.should match /^ h2\.$/
|
334
|
-
@io.string.should match /^ a = "b"$/
|
335
|
-
@io.string.should match /^ h1\.$/
|
336
|
-
@io.string.should match /^ a = "a"$/
|
337
|
-
@io.string.should match /^ h2\.$/
|
338
|
-
@io.string.should match /^ a = "b"$/
|
339
|
-
@io.string.should match /^ h1\.$/
|
340
|
-
@io.string.should match /^ a = "a"$/
|
341
|
-
@io.string.should match /^ h2\.$/
|
342
|
-
@io.string.should match /^ a = "b"$/
|
343
|
-
@io.string.should match /^ h1\.$/
|
344
|
-
@io.string.should match /^ a = "a"$/
|
345
|
-
@io.string.should match /^ h2\.$/
|
346
|
-
@io.string.should match /^ a = "b"$/
|
347
|
-
@io.string.should match /^ h1\.$/
|
348
|
-
@io.string.should match /^ a = "a"$/
|
349
|
-
@io.string.should match /^ h2\.$/
|
350
|
-
@io.string.should match /^ a = "b"$/
|
351
|
-
@io.string.should match /^ h1\.$/
|
352
|
-
@io.string.should match /^ a = "a"$/
|
353
|
-
@io.string.should match /^ h2\.$/
|
354
|
-
@io.string.should match /^ \(recursion limit reached\)$/
|
355
|
-
end
|
356
|
-
|
357
|
-
it "creates nested objects via subclass" do
|
358
|
-
RecursiveOpenStructSubClass = Class.new(RecursiveOpenStruct)
|
359
|
-
|
360
|
-
rossc = RecursiveOpenStructSubClass.new({ :one => [{:two => :three}] }, recurse_over_arrays: true)
|
361
|
-
|
362
|
-
rossc.one.first.class.should == RecursiveOpenStructSubClass
|
363
|
-
end
|
364
|
-
end # additionnel features
|
365
|
-
|
366
|
-
end # describe RecursiveOpenStruct
|