recursive-open-struct 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
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