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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a1f1a8f65c6217fb268d9cb42d7a862c17331c9a
4
- data.tar.gz: 0ac4e5cd8da82078b32e3c782668e1446f2383bb
3
+ metadata.gz: 7067232359a27f9453674a17396798cb42b085d4
4
+ data.tar.gz: 88fbfa54a7bff4548ab752ae681309afda312923
5
5
  SHA512:
6
- metadata.gz: 3ccb7649bea9011276fa62f1f5e1d22956446d846d41320616e1a1b83738bf1ede1baa128cc9fa2d283e1b591dbc8f765dcbde1f64ffd7a367535edda95114e1
7
- data.tar.gz: 5c762d9925b274435e1753b1c24b4b4e84ddc49f84cc2780c40e39fd01aa803eb071b8f9afc1484aa974500fa08a34ccaf5c12602bafeb55131a22483b413ec3
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[name]
46
+ v = @table[key_name]
48
47
  if v.is_a?(Hash)
49
- @sub_elements[name] ||= self.class.new(v,
50
- :recurse_over_arrays => @recurse_over_arrays,
51
- :mutate_input_hash => true)
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[name] ||= recurse_over_array(v)
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(name)
60
- modifiable[name] = x
61
+ @sub_elements.delete(key_name)
62
+ modifiable[key_name] = x
61
63
  end
62
- define_method("#{name}_as_a_hash") { @table[name] }
64
+ define_method("#{name}_as_a_hash") { @table[key_name] }
63
65
  end
64
66
  end
65
- name
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
+
@@ -3,5 +3,5 @@
3
3
  require 'ostruct'
4
4
 
5
5
  class RecursiveOpenStruct < OpenStruct
6
- VERSION = "0.6.3"
6
+ VERSION = "0.6.4"
7
7
  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,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.3
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-04-11 00:00:00.000000000 Z
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/recursive_open_struct_spec.rb
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/recursive_open_struct_spec.rb
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