fancy-open-struct 0.1
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.
- data/.document +5 -0
- data/.gitignore +52 -0
- data/.rspec +1 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +63 -0
- data/Rakefile +45 -0
- data/fancy-open-struct.gemspec +48 -0
- data/lib/fancy-open-struct.rb +1 -0
- data/lib/fancy_open_struct.rb +126 -0
- data/spec/fancy_open_struct_spec.rb +308 -0
- data/spec/spec_helper.rb +17 -0
- metadata +114 -0
data/.document
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# rcov generated
|
2
|
+
coverage
|
3
|
+
|
4
|
+
# rdoc generated
|
5
|
+
rdoc
|
6
|
+
|
7
|
+
# yard generated
|
8
|
+
doc
|
9
|
+
.yardoc
|
10
|
+
|
11
|
+
# bundler
|
12
|
+
.bundle
|
13
|
+
|
14
|
+
# jeweler generated
|
15
|
+
pkg
|
16
|
+
|
17
|
+
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
18
|
+
#
|
19
|
+
# * Create a file at ~/.gitignore
|
20
|
+
# * Include files you want ignored
|
21
|
+
# * Run: git config --global core.excludesfile ~/.gitignore
|
22
|
+
#
|
23
|
+
# After doing this, these files will be ignored in all your git projects,
|
24
|
+
# saving you from having to 'pollute' every project you touch with them
|
25
|
+
#
|
26
|
+
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
27
|
+
#
|
28
|
+
# For MacOS:
|
29
|
+
#
|
30
|
+
.DS_Store
|
31
|
+
|
32
|
+
# For TextMate
|
33
|
+
*.tmproj
|
34
|
+
tmtags
|
35
|
+
|
36
|
+
# For emacs:
|
37
|
+
*~
|
38
|
+
\#*
|
39
|
+
.\#*
|
40
|
+
|
41
|
+
# For vim:
|
42
|
+
*.swp
|
43
|
+
|
44
|
+
# For redcar:
|
45
|
+
#.redcar
|
46
|
+
|
47
|
+
# For rubinius:
|
48
|
+
#*.rbc
|
49
|
+
|
50
|
+
Gemfile.lock
|
51
|
+
|
52
|
+
*.gem
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2014 Thomas H. Chapin
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
= fancy-open-struct
|
2
|
+
|
3
|
+
OpenStruct subclass that returns nested (recursive) hash attributes as FancyOpenStructs,
|
4
|
+
and also allows usage of Hash methods for getting and setting values.
|
5
|
+
|
6
|
+
It allows for hashes within hashes to be called in a chain of methods:
|
7
|
+
|
8
|
+
fos = FancyOpenStruct.new( { :fooa => { :foob => 'fooc' } } )
|
9
|
+
|
10
|
+
fos.fooa.foob # => 'fooc'
|
11
|
+
|
12
|
+
Also, if needed, nested hashes can still be accessed as hashes:
|
13
|
+
|
14
|
+
fos.fooa_as_a_hash # { :foob => 'fooc' }
|
15
|
+
|
16
|
+
Get and set values either via dot syntax, or hash syntax (Hash keys are handled as Symbols):
|
17
|
+
|
18
|
+
fos.foo = 'bar'
|
19
|
+
fos[:foo] # 'bar'
|
20
|
+
|
21
|
+
fos[:baz] = 'qux'
|
22
|
+
fos.baz # 'qux'
|
23
|
+
|
24
|
+
fos.length # 2
|
25
|
+
|
26
|
+
FancyOpenStruct can also optionally recurse across arrays, although you
|
27
|
+
have to explicitly enable it:
|
28
|
+
|
29
|
+
h = { :somearr => [ { :name => 'a'}, { :name => 'b' } ] }
|
30
|
+
|
31
|
+
fos = FancyOpenStruct.new(h, :recurse_over_arrays => true )
|
32
|
+
|
33
|
+
fos.somarr[0].name # => 'a'
|
34
|
+
fos.somarr[1].name # => 'b'
|
35
|
+
|
36
|
+
== Installation
|
37
|
+
|
38
|
+
Available as a gem in rubygems, the default gem repository.
|
39
|
+
|
40
|
+
If you use bundler, just throw that in your gemfile :
|
41
|
+
|
42
|
+
gem 'fancy-open-struct'
|
43
|
+
|
44
|
+
You may also install the gem manually :
|
45
|
+
|
46
|
+
gem install fancy-open-struct
|
47
|
+
|
48
|
+
== Note on Patches/Pull Requests
|
49
|
+
|
50
|
+
* Fork the project.
|
51
|
+
* Make your feature addition or bug fix.
|
52
|
+
* Add tests for it. This is important so I don't break it in a
|
53
|
+
future version unintentionally.
|
54
|
+
* Commit, do not mess with rakefile, version, or history.
|
55
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
56
|
+
* Send me a pull request. Bonus points for topic branches.
|
57
|
+
|
58
|
+
== Copyright
|
59
|
+
|
60
|
+
Copyright (c) 2014 Thomas H. Chapin. See LICENSE for details.
|
61
|
+
|
62
|
+
This gem is based on the recursive-open-struct gem by William (B.J.) Snow Orvis, which can be found here:
|
63
|
+
https://github.com/aetherknight/recursive-open-struct
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
8
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
9
|
+
end
|
10
|
+
namespace :spec do
|
11
|
+
if RUBY_VERSION =~ /^1\.8/
|
12
|
+
desc "Rspec code coverage (1.8.7)"
|
13
|
+
RSpec::Core::RakeTask.new(:coverage) do |spec|
|
14
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
15
|
+
spec.rcov = true
|
16
|
+
end
|
17
|
+
else
|
18
|
+
desc "Rspec code coverage (1.9+)"
|
19
|
+
task :coverage do
|
20
|
+
ENV['COVERAGE'] = 'true'
|
21
|
+
Rake::Task["spec"].execute
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rdoc/task'
|
27
|
+
Rake::RDocTask.new do |rdoc|
|
28
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
29
|
+
|
30
|
+
rdoc.rdoc_dir = 'rdoc'
|
31
|
+
rdoc.title = "fancy-open-struct #{version}"
|
32
|
+
rdoc.rdoc_files.include('README*')
|
33
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
34
|
+
end
|
35
|
+
|
36
|
+
task :default => :spec
|
37
|
+
|
38
|
+
task :fix_permissions do
|
39
|
+
File.umask 0022
|
40
|
+
filelist = `git ls-files`.split("\n")
|
41
|
+
FileUtils.chmod 0644, filelist, :verbose => true
|
42
|
+
FileUtils.chmod 0755, ['lib','spec'], :verbose => true
|
43
|
+
end
|
44
|
+
|
45
|
+
task :build => :fix_permissions
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require './lib/fancy_open_struct'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "fancy-open-struct"
|
7
|
+
s.version = FancyOpenStruct::VERSION
|
8
|
+
s.authors = ["Thomas H. Chapin"]
|
9
|
+
s.email = "tchapin@gmail.com"
|
10
|
+
s.date = Time.now.utc.strftime("%Y-%m-%d")
|
11
|
+
s.homepage = "http://github.com/tomchapin/fancy-open-struct"
|
12
|
+
s.licenses = ["MIT"]
|
13
|
+
|
14
|
+
s.summary = "OpenStruct subclass that returns nested hash attributes as FancyOpenStructs"
|
15
|
+
s.description = <<-QUOTE .gsub(/^ /,'')
|
16
|
+
FancyOpenStruct is a subclass of OpenStruct, and is a variant of RecursiveOpenStruct.
|
17
|
+
It differs from OpenStruct in that it allows nested hashes to be treated in a recursive
|
18
|
+
fashion, and it also provides Hash methods for getting and setting values.
|
19
|
+
|
20
|
+
For example:
|
21
|
+
|
22
|
+
fos = FancyOpenStruct.new({ :a => { :b => 'c' } })
|
23
|
+
fos.a.b # 'c'
|
24
|
+
|
25
|
+
fos.foo = 'bar'
|
26
|
+
fos[:foo] # 'bar'
|
27
|
+
|
28
|
+
fos.length # 2
|
29
|
+
|
30
|
+
Also, nested hashes can still be accessed as hashes:
|
31
|
+
|
32
|
+
fos.a_as_a_hash # { :b => 'c' }
|
33
|
+
|
34
|
+
QUOTE
|
35
|
+
|
36
|
+
s.files = `git ls-files`.split("\n")
|
37
|
+
s.test_files = `git ls-files spec`.split("\n")
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.extra_rdoc_files = [
|
40
|
+
"LICENSE.txt",
|
41
|
+
"README.rdoc"
|
42
|
+
]
|
43
|
+
|
44
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
45
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
46
|
+
s.add_development_dependency(%q<rdoc>, [">= 0"])
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'fancy_open_struct'
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'pry'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'delegate'
|
4
|
+
|
5
|
+
class FancyOpenStruct < OpenStruct
|
6
|
+
VERSION = "0.1"
|
7
|
+
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
hash_methods = Hash.instance_methods(false) - (Hash.instance_methods(false) & OpenStruct.instance_methods(false))
|
11
|
+
def_delegators :@table, *hash_methods
|
12
|
+
|
13
|
+
def initialize(hash=nil, args={})
|
14
|
+
@recurse_over_arrays = args.fetch(:recurse_over_arrays, false)
|
15
|
+
@table = {}
|
16
|
+
if hash
|
17
|
+
for k, v in hash
|
18
|
+
@table[k.to_sym] = v
|
19
|
+
new_ostruct_member(k)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
@sub_elements = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_h
|
26
|
+
@table.dup.update(@sub_elements) do |k, oldval, newval|
|
27
|
+
if newval.kind_of?(self.class)
|
28
|
+
newval.to_h
|
29
|
+
elsif newval.kind_of?(Array)
|
30
|
+
newval.map { |a| a.kind_of?(self.class) ? a.to_h : a }
|
31
|
+
else
|
32
|
+
raise "Cached value of unsupported type: #{newval.inspect}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def new_ostruct_member(name)
|
38
|
+
name = name.to_sym
|
39
|
+
unless self.respond_to?(name)
|
40
|
+
define_singleton_method name do
|
41
|
+
v = @table[name]
|
42
|
+
if v.is_a?(Hash)
|
43
|
+
@sub_elements[name] ||= self.class.new(v, :recurse_over_arrays => @recurse_over_arrays)
|
44
|
+
elsif v.is_a?(Array) and @recurse_over_arrays
|
45
|
+
@sub_elements[name] ||= recurse_over_array v
|
46
|
+
else
|
47
|
+
v
|
48
|
+
end
|
49
|
+
end
|
50
|
+
define_singleton_method("#{name}=") { |x| modifiable[name] = x }
|
51
|
+
define_singleton_method("#{name}_as_a_hash") { @table[name] }
|
52
|
+
end
|
53
|
+
name
|
54
|
+
end
|
55
|
+
|
56
|
+
def recurse_over_array array
|
57
|
+
array.map do |a|
|
58
|
+
if a.is_a? Hash
|
59
|
+
self.class.new(a, :recurse_over_arrays => true)
|
60
|
+
elsif a.is_a? Array
|
61
|
+
recurse_over_array a
|
62
|
+
else
|
63
|
+
a
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def debug_inspect(io = STDOUT, indent_level = 0, recursion_limit = 12)
|
69
|
+
display_recursive_open_hash(io, @table, indent_level, recursion_limit)
|
70
|
+
end
|
71
|
+
|
72
|
+
def display_recursive_open_hash(io, ostrct_or_hash, indent_level, recursion_limit)
|
73
|
+
|
74
|
+
if recursion_limit <= 0
|
75
|
+
# protection against recursive structure (like in the tests)
|
76
|
+
io.puts ' '*indent_level + '(recursion limit reached)'
|
77
|
+
else
|
78
|
+
#puts ostrct_or_hash.inspect
|
79
|
+
if ostrct_or_hash.is_a?(self.class)
|
80
|
+
ostrct_or_hash = ostrct_or_hash.marshal_dump
|
81
|
+
end
|
82
|
+
|
83
|
+
# We'll display the key values like this : key = value
|
84
|
+
# to align display, we look for the maximum key length of the data that will be displayed
|
85
|
+
# (everything except hashes)
|
86
|
+
data_indent = ostrct_or_hash \
|
87
|
+
.reject { |k, v| v.is_a?(self.class) || v.is_a?(Hash) } \
|
88
|
+
.max { |a, b| a[0].to_s.length <=> b[0].to_s.length }[0].to_s.length
|
89
|
+
# puts "max length = #{data_indent}"
|
90
|
+
|
91
|
+
ostrct_or_hash.each do |key, value|
|
92
|
+
if value.is_a?(self.class) || value.is_a?(Hash)
|
93
|
+
io.puts ' '*indent_level + key.to_s + '.'
|
94
|
+
display_recursive_open_hash(io, value, indent_level + 1, recursion_limit - 1)
|
95
|
+
else
|
96
|
+
io.puts ' '*indent_level + key.to_s + ' '*(data_indent - key.to_s.length) + ' = ' + value.inspect
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
true
|
102
|
+
end
|
103
|
+
|
104
|
+
def []=(*args)
|
105
|
+
len = args.length
|
106
|
+
raise ArgumentError, "wrong number of arguments (#{len} for 2)", caller(1) if len != 2
|
107
|
+
modifiable[new_ostruct_member(args[0].to_sym)] = args[1]
|
108
|
+
end
|
109
|
+
|
110
|
+
def method_missing(mid, *args) # :nodoc:
|
111
|
+
mname = mid.id2name
|
112
|
+
len = args.length
|
113
|
+
if mname.chomp!('=')
|
114
|
+
raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1) if len != 1
|
115
|
+
# Set up an instance method to point to the key/value in the table and set the value
|
116
|
+
modifiable[new_ostruct_member(mname.to_sym)] = args[0]
|
117
|
+
elsif @table.has_key?(mid)
|
118
|
+
# The table has apparently been modified externally, so we need to set up
|
119
|
+
# an instance method to point to the key/value in the table.
|
120
|
+
new_ostruct_member(mname.to_sym)
|
121
|
+
self.send(mid)
|
122
|
+
else
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,308 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'fancy_open_struct'
|
3
|
+
|
4
|
+
describe FancyOpenStruct do
|
5
|
+
|
6
|
+
describe "behavior it inherits from OpenStruct" do
|
7
|
+
|
8
|
+
it "can represent arbitrary data objects" do
|
9
|
+
fos = FancyOpenStruct.new
|
10
|
+
fos.blah = "John Smith"
|
11
|
+
fos.blah.should == "John Smith"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "can be created from a hash" do
|
15
|
+
h = {:asdf => 'John Smith'}
|
16
|
+
fos = FancyOpenStruct.new(h)
|
17
|
+
fos.asdf.should == "John Smith"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "can modify an existing key" do
|
21
|
+
h = {:blah => 'John Smith'}
|
22
|
+
fos = FancyOpenStruct.new(h)
|
23
|
+
fos.blah = "George Washington"
|
24
|
+
fos.blah.should == "George Washington"
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "handling of arbitrary attributes" do
|
28
|
+
subject { FancyOpenStruct.new }
|
29
|
+
before(:each) do
|
30
|
+
subject.blah = "John Smith"
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#respond?" do
|
34
|
+
it { subject.should respond_to :blah }
|
35
|
+
it { subject.should respond_to :blah= }
|
36
|
+
it { subject.should_not respond_to :asdf }
|
37
|
+
it { subject.should_not respond_to :asdf= }
|
38
|
+
end # describe #respond?
|
39
|
+
|
40
|
+
describe "#methods" do
|
41
|
+
it { subject.methods.map(&:to_sym).should include :blah }
|
42
|
+
it { subject.methods.map(&:to_sym).should include :blah= }
|
43
|
+
it { subject.methods.map(&:to_sym).should_not include :asdf }
|
44
|
+
it { subject.methods.map(&:to_sym).should_not include :asdf= }
|
45
|
+
end # describe #methods
|
46
|
+
end # describe handling of arbitrary attributes
|
47
|
+
end # describe behavior it inherits from OpenStruct
|
48
|
+
|
49
|
+
describe "improvements on OpenStruct" do
|
50
|
+
it "can be converted back to a hash" do
|
51
|
+
h = {:asdf => 'John Smith'}
|
52
|
+
fos = FancyOpenStruct.new(h)
|
53
|
+
fos.to_h.should == h
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'hash methods' do
|
57
|
+
it "handles hash methods for setting values" do
|
58
|
+
fos = FancyOpenStruct.new
|
59
|
+
fos['blah'] = "John Smith"
|
60
|
+
fos[:foo] = "George Washington"
|
61
|
+
fos.blah.should == "John Smith"
|
62
|
+
fos.foo.should == "George Washington"
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'converts string hash keys to symbols' do
|
66
|
+
fos = FancyOpenStruct.new
|
67
|
+
fos['blah'] = "John Smith"
|
68
|
+
fos['blah'].should == nil
|
69
|
+
fos[:blah].should == "John Smith"
|
70
|
+
fos.blah.should == "John Smith"
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'forwards all of the basic Hash methods directly to the @table instance variable' do
|
74
|
+
fos = FancyOpenStruct.new
|
75
|
+
Hash.instance_methods(false).each do |method_name|
|
76
|
+
fos.respond_to?(method_name).should be_true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'recovers gracefully even when the internal hash @table is directly modified via hash methods' do
|
81
|
+
fos = FancyOpenStruct.new
|
82
|
+
fos.foo = 'bar'
|
83
|
+
fos.to_h.should == {:foo => "bar"}
|
84
|
+
other_hash = {:baz => :qux}
|
85
|
+
fos.merge! other_hash
|
86
|
+
fos.to_h.should == {:foo => "bar", :baz => :qux}
|
87
|
+
fos.foo.should == 'bar'
|
88
|
+
fos.baz.should == :qux
|
89
|
+
fos.instance_variable_set :@table, {}
|
90
|
+
fos.foo.should == nil
|
91
|
+
fos.baz.should == nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'using strings instead of symbols as hash keys' do
|
96
|
+
it "can be created from a hash" do
|
97
|
+
h = {'asdf' => 'John Smith'}
|
98
|
+
fos = FancyOpenStruct.new(h)
|
99
|
+
fos.asdf.should == "John Smith"
|
100
|
+
end
|
101
|
+
|
102
|
+
it "can modify an existing key" do
|
103
|
+
h = {'blah' => 'John Smith'}
|
104
|
+
fos = FancyOpenStruct.new(h)
|
105
|
+
fos.blah = "George Washington"
|
106
|
+
fos.blah.should == "George Washington"
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'saves string hash keys as symbols' do
|
110
|
+
h = {'blah' => 'John Smith'}
|
111
|
+
fos = FancyOpenStruct.new(h)
|
112
|
+
fos.to_h.should == {:blah => "John Smith"}
|
113
|
+
fos[:blah].should == "John Smith"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "recursive behavior" do
|
119
|
+
let(:h) { {:blah => {:another => 'value'}} }
|
120
|
+
subject { FancyOpenStruct.new(h) }
|
121
|
+
|
122
|
+
it "returns accessed hashes as FancyOpenStructs instead of hashes" do
|
123
|
+
subject.blah.another.should == 'value'
|
124
|
+
end
|
125
|
+
|
126
|
+
it "uses #key_as_a_hash to return key as a Hash" do
|
127
|
+
subject.blah_as_a_hash.should == {:another => 'value'}
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "handling loops in the origin Hashes" do
|
131
|
+
let(:h1) { {:a => 'a'} }
|
132
|
+
let(:h2) { {:a => 'b', :h1 => h1} }
|
133
|
+
before(:each) { h1[:h2] = h2 }
|
134
|
+
|
135
|
+
subject { FancyOpenStruct.new(h2) }
|
136
|
+
|
137
|
+
it { subject.h1.a.should == 'a' }
|
138
|
+
it { subject.h1.h2.a.should == 'b' }
|
139
|
+
it { subject.h1.h2.h1.a.should == 'a' }
|
140
|
+
it { subject.h1.h2.h1.h2.a.should == 'b' }
|
141
|
+
it { subject.h1.should == subject.h1.h2.h1 }
|
142
|
+
it { subject.h1.should_not == subject.h1.h2 }
|
143
|
+
end # describe handling loops in the origin Hashes
|
144
|
+
|
145
|
+
it "can modify a key of a sub-element" do
|
146
|
+
h = {
|
147
|
+
:blah => {
|
148
|
+
:blargh => 'Brad'
|
149
|
+
}
|
150
|
+
}
|
151
|
+
fos = FancyOpenStruct.new(h)
|
152
|
+
fos.blah.blargh = "Janet"
|
153
|
+
fos.blah.blargh.should == "Janet"
|
154
|
+
end
|
155
|
+
|
156
|
+
context "after a sub-element has been modified" do
|
157
|
+
let(:hash) do
|
158
|
+
{
|
159
|
+
:blah => {
|
160
|
+
:blargh => 'Brad'
|
161
|
+
}
|
162
|
+
}
|
163
|
+
end
|
164
|
+
subject { FancyOpenStruct.new(hash) }
|
165
|
+
before(:each) { subject.blah.blargh = "Janet" }
|
166
|
+
it "returns a hash that contains those modifications" do
|
167
|
+
subject.to_h.should == {:blah => {:blargh => "Janet"}}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
describe 'recursing over arrays' do
|
173
|
+
let(:blah_list) { [{:foo => '1'}, {:foo => '2'}, 'baz'] }
|
174
|
+
let(:h) { {:blah => blah_list} }
|
175
|
+
|
176
|
+
context "when recursing over arrays is enabled" do
|
177
|
+
subject { FancyOpenStruct.new(h, :recurse_over_arrays => true) }
|
178
|
+
|
179
|
+
it { subject.blah.length.should == 3 }
|
180
|
+
it { subject.blah[0].foo.should == '1' }
|
181
|
+
it { subject.blah[1].foo.should == '2' }
|
182
|
+
it { subject.blah_as_a_hash.should == blah_list }
|
183
|
+
it { subject.blah[2].should == 'baz' }
|
184
|
+
it "Retains changes acfoss Array lookups" do
|
185
|
+
subject.blah[1].foo = "Dr Scott"
|
186
|
+
subject.blah[1].foo.should == "Dr Scott"
|
187
|
+
end
|
188
|
+
it "propagates the changes through to .to_h acfoss Array lookups" do
|
189
|
+
subject.blah[1].foo = "Dr Scott"
|
190
|
+
subject.to_h.should == {
|
191
|
+
:blah => [{:foo => '1'}, {:foo => "Dr Scott"}, 'baz']
|
192
|
+
}
|
193
|
+
end
|
194
|
+
|
195
|
+
context "when array is nested deeper" do
|
196
|
+
let(:deep_hash) { {:foo => {:blah => blah_list}} }
|
197
|
+
subject { FancyOpenStruct.new(deep_hash, :recurse_over_arrays => true) }
|
198
|
+
|
199
|
+
it { subject.foo.blah.length.should == 3 }
|
200
|
+
it "Retains changes acfoss Array lookups" do
|
201
|
+
subject.foo.blah[1].foo = "Dr Scott"
|
202
|
+
subject.foo.blah[1].foo.should == "Dr Scott"
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
context "when array is in an array" do
|
208
|
+
let(:haah) { {:blah => [blah_list]} }
|
209
|
+
subject { FancyOpenStruct.new(haah, :recurse_over_arrays => true) }
|
210
|
+
|
211
|
+
it { subject.blah.length.should == 1 }
|
212
|
+
it { subject.blah[0].length.should == 3 }
|
213
|
+
it "Retains changes acfoss Array lookups" do
|
214
|
+
subject.blah[0][1].foo = "Dr Scott"
|
215
|
+
subject.blah[0][1].foo.should == "Dr Scott"
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
end # when recursing over arrays is enabled
|
221
|
+
|
222
|
+
context "when recursing over arrays is disabled" do
|
223
|
+
subject { FancyOpenStruct.new(h) }
|
224
|
+
|
225
|
+
it { subject.blah.length.should == 3 }
|
226
|
+
it { subject.blah[0].should == {:foo => '1'} }
|
227
|
+
it { subject.blah[0][:foo].should == '1' }
|
228
|
+
end # when recursing over arrays is disabled
|
229
|
+
|
230
|
+
end # recursing over arrays
|
231
|
+
end # recursive behavior
|
232
|
+
|
233
|
+
describe "additional features" do
|
234
|
+
|
235
|
+
before(:each) do
|
236
|
+
h1 = {:a => 'a'}
|
237
|
+
h2 = {:a => 'b', :h1 => h1}
|
238
|
+
h1[:h2] = h2
|
239
|
+
@fos = FancyOpenStruct.new(h2)
|
240
|
+
end
|
241
|
+
|
242
|
+
it "should have a simple way of display" do
|
243
|
+
@output = <<-QUOTE
|
244
|
+
a = "b"
|
245
|
+
h1.
|
246
|
+
a = "a"
|
247
|
+
h2.
|
248
|
+
a = "b"
|
249
|
+
h1.
|
250
|
+
a = "a"
|
251
|
+
h2.
|
252
|
+
a = "b"
|
253
|
+
h1.
|
254
|
+
a = "a"
|
255
|
+
h2.
|
256
|
+
a = "b"
|
257
|
+
h1.
|
258
|
+
a = "a"
|
259
|
+
h2.
|
260
|
+
a = "b"
|
261
|
+
h1.
|
262
|
+
a = "a"
|
263
|
+
h2.
|
264
|
+
a = "b"
|
265
|
+
h1.
|
266
|
+
a = "a"
|
267
|
+
h2.
|
268
|
+
(recursion limit reached)
|
269
|
+
QUOTE
|
270
|
+
@io = StringIO.new
|
271
|
+
@fos.debug_inspect(@io)
|
272
|
+
@io.string.should match /^a = "b"$/
|
273
|
+
@io.string.should match /^h1\.$/
|
274
|
+
@io.string.should match /^ a = "a"$/
|
275
|
+
@io.string.should match /^ h2\.$/
|
276
|
+
@io.string.should match /^ a = "b"$/
|
277
|
+
@io.string.should match /^ h1\.$/
|
278
|
+
@io.string.should match /^ a = "a"$/
|
279
|
+
@io.string.should match /^ h2\.$/
|
280
|
+
@io.string.should match /^ a = "b"$/
|
281
|
+
@io.string.should match /^ h1\.$/
|
282
|
+
@io.string.should match /^ a = "a"$/
|
283
|
+
@io.string.should match /^ h2\.$/
|
284
|
+
@io.string.should match /^ a = "b"$/
|
285
|
+
@io.string.should match /^ h1\.$/
|
286
|
+
@io.string.should match /^ a = "a"$/
|
287
|
+
@io.string.should match /^ h2\.$/
|
288
|
+
@io.string.should match /^ a = "b"$/
|
289
|
+
@io.string.should match /^ h1\.$/
|
290
|
+
@io.string.should match /^ a = "a"$/
|
291
|
+
@io.string.should match /^ h2\.$/
|
292
|
+
@io.string.should match /^ a = "b"$/
|
293
|
+
@io.string.should match /^ h1\.$/
|
294
|
+
@io.string.should match /^ a = "a"$/
|
295
|
+
@io.string.should match /^ h2\.$/
|
296
|
+
@io.string.should match /^ \(recursion limit reached\)$/
|
297
|
+
end
|
298
|
+
|
299
|
+
it "creates nested objects via subclass" do
|
300
|
+
FancyOpenStructSubClass = Class.new(FancyOpenStruct)
|
301
|
+
|
302
|
+
fossc = FancyOpenStructSubClass.new({:one => [{:two => :three}]}, recurse_over_arrays: true)
|
303
|
+
|
304
|
+
fossc.one.first.class.should == FancyOpenStructSubClass
|
305
|
+
end
|
306
|
+
end # additionnel features
|
307
|
+
|
308
|
+
end # describe FancyOpenStruct
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
|
5
|
+
if ENV['COVERAGE'] == 'true'
|
6
|
+
require 'simplecov'
|
7
|
+
SimpleCov.start
|
8
|
+
end
|
9
|
+
|
10
|
+
# Requires supporting files with custom matchers and macfos, etc,
|
11
|
+
# in ./support/ and its subdirectories.
|
12
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
13
|
+
|
14
|
+
RSpec.configure do |config|
|
15
|
+
config.filter_run focus: true
|
16
|
+
config.run_all_when_everything_filtered = true
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fancy-open-struct
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Thomas H. Chapin
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-05-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rdoc
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: ! "FancyOpenStruct is a subclass of OpenStruct, and is a variant of RecursiveOpenStruct.\nIt
|
63
|
+
differs from OpenStruct in that it allows nested hashes to be treated in a recursive\nfashion,
|
64
|
+
and it also provides Hash methods for getting and setting values.\n\nFor example:\n\n
|
65
|
+
\ fos = FancyOpenStruct.new({ :a => { :b => 'c' } })\n fos.a.b # 'c'\n\n fos.foo
|
66
|
+
= 'bar'\n fos[:foo] # 'bar'\n\n fos.length # 2\n\nAlso, nested hashes can
|
67
|
+
still be accessed as hashes:\n\n fos.a_as_a_hash # { :b => 'c' }\n\n"
|
68
|
+
email: tchapin@gmail.com
|
69
|
+
executables: []
|
70
|
+
extensions: []
|
71
|
+
extra_rdoc_files:
|
72
|
+
- LICENSE.txt
|
73
|
+
- README.rdoc
|
74
|
+
files:
|
75
|
+
- .document
|
76
|
+
- .gitignore
|
77
|
+
- .rspec
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.rdoc
|
81
|
+
- Rakefile
|
82
|
+
- fancy-open-struct.gemspec
|
83
|
+
- lib/fancy-open-struct.rb
|
84
|
+
- lib/fancy_open_struct.rb
|
85
|
+
- spec/fancy_open_struct_spec.rb
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
homepage: http://github.com/tomchapin/fancy-open-struct
|
88
|
+
licenses:
|
89
|
+
- MIT
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.8.23.2
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: OpenStruct subclass that returns nested hash attributes as FancyOpenStructs
|
112
|
+
test_files:
|
113
|
+
- spec/fancy_open_struct_spec.rb
|
114
|
+
- spec/spec_helper.rb
|