recursive-open-struct-1.1.0/0000755000004100000410000000000013251424302016025 5ustar www-datawww-datarecursive-open-struct-1.1.0/Rakefile0000644000004100000410000000277613251424302017506 0ustar www-datawww-data# encoding: utf-8 require 'rubygems' require 'bundler/gem_tasks' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = FileList['spec/**/*_spec.rb'] end namespace :spec do if RUBY_VERSION =~ /^1\.8/ desc "Rspec code coverage (1.8.7)" RSpec::Core::RakeTask.new(:coverage) do |spec| spec.pattern = 'spec/**/*_spec.rb' spec.rcov = true end else desc "Rspec code coverage (1.9+)" task :coverage do ENV['COVERAGE'] = 'true' Rake::Task["spec"].execute end end end require 'rdoc/task' Rake::RDocTask.new do |rdoc| version = File.exist?('VERSION') ? File.read('VERSION') : "" rdoc.rdoc_dir = 'rdoc' rdoc.title = "recursive-open-struct #{version}" rdoc.rdoc_files.include('README*') rdoc.rdoc_files.include('lib/**/*.rb') end task :default => :spec task :fix_permissions do File.umask 0022 filelist = `git ls-files`.split("\n") FileUtils.chmod 0644, filelist, :verbose => true FileUtils.chmod 0755, ['lib','spec'], :verbose => true end desc "Update the AUTHORS.txt file" task :update_authors do authors = `git log --format="%aN <%aE>"|sort -f|uniq` File.open('AUTHORS.txt', 'w') do |f| f.write("Recursive-open-struct was written by these fine people:\n\n") f.write(authors.split("\n").map { |a| "* #{a}" }.join( "\n" )) f.write("\n") end end task :build => [:update_authors, :fix_permissions] desc "Run an interactive pry shell with ros required" task :pry do sh "pry -I lib -r recursive-open-struct" end recursive-open-struct-1.1.0/Gemfile0000644000004100000410000000004613251424302017320 0ustar www-datawww-datasource 'https://rubygems.org' gemspec recursive-open-struct-1.1.0/recursive-open-struct.gemspec0000644000004100000410000000261213251424302023663 0ustar www-datawww-data# -*- encoding: utf-8 -*- require './lib/recursive_open_struct/version' Gem::Specification.new do |s| s.name = "recursive-open-struct" s.version = RecursiveOpenStruct::VERSION s.authors = ["William (B.J.) Snow Orvis"] s.email = "aetherknight@gmail.com" s.date = Time.now.utc.strftime("%Y-%m-%d") s.homepage = "http://github.com/aetherknight/recursive-open-struct" s.licenses = ["MIT"] s.summary = "OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs" s.description = <<-QUOTE .gsub(/^ /,'') RecursiveOpenStruct is a subclass of OpenStruct. It differs from OpenStruct in that it allows nested hashes to be treated in a recursive fashion. For example: ros = RecursiveOpenStruct.new({ :a => { :b => 'c' } }) ros.a.b # 'c' Also, nested hashes can still be accessed as hashes: ros.a_as_a_hash # { :b => 'c' } QUOTE s.files = `git ls-files`.split("\n") s.test_files = `git ls-files spec`.split("\n") s.require_paths = ["lib"] s.extra_rdoc_files = [ "CHANGELOG.md", "LICENSE.txt", "README.md" ] s.add_development_dependency('bundler', [">= 0"]) s.add_development_dependency('pry', [">= 0"]) s.add_development_dependency('rake', [">= 0"]) s.add_development_dependency('rdoc', [">= 0"]) s.add_development_dependency('rspec', "~> 3.2") s.add_development_dependency('simplecov', [">= 0"]) end recursive-open-struct-1.1.0/.rspec0000644000004100000410000000001013251424302017131 0ustar www-datawww-data--color recursive-open-struct-1.1.0/LICENSE.txt0000644000004100000410000000213713251424302017653 0ustar www-datawww-dataCopyright (c) 2009-2018, The Recursive-open-struct developers (given in the file AUTHORS.txt). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. recursive-open-struct-1.1.0/spec/0000755000004100000410000000000013251424302016757 5ustar www-datawww-datarecursive-open-struct-1.1.0/spec/spec_helper.rb0000644000004100000410000000105513251424302021576 0ustar www-datawww-data$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) $LOAD_PATH.unshift(File.dirname(__FILE__)) require 'rspec' require 'pry' if ENV['COVERAGE'] == 'true' require 'simplecov' SimpleCov.start end # Requires supporting files with custom matchers and macros, etc, # in ./support/ and its subdirectories. Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} RSpec.configure do |config| config.run_all_when_everything_filtered = true config.filter_run :focus # config.expect_with(:rspec) { |c| c.syntax = :should } end recursive-open-struct-1.1.0/spec/recursive_open_struct/0000755000004100000410000000000013251424302023413 5ustar www-datawww-datarecursive-open-struct-1.1.0/spec/recursive_open_struct/open_struct_behavior_spec.rb0000644000004100000410000000720213251424302031177 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do let(:hash) { {} } subject(:ros) { RecursiveOpenStruct.new(hash) } describe "behavior it inherits from OpenStruct" do context 'when not initialized from anything' do subject(:ros) { RecursiveOpenStruct.new } it "can represent arbitrary data objects" do ros.blah = "John Smith" expect(ros.blah).to eq "John Smith" end it 'returns nil for missing attributes' do expect(ros.foo).to be_nil end end context 'when initialized with nil' do let(:hash) { nil } it 'returns nil for missing attributes' do expect(ros.foo).to be_nil end end context 'when initialized with an empty hash' do it 'returns nil for missing attributes' do expect(ros.foo).to be_nil end end context "when initialized from a hash" do let(:hash) { { :asdf => 'John Smith' } } context 'that contains symbol keys' do it "turns those symbol keys into method names" do expect(ros.asdf).to eq "John Smith" end end it "can modify an existing key" do ros.asdf = "George Washington" expect(ros.asdf).to eq "George Washington" end context 'that contains string keys' do let(:hash) { { 'asdf' => 'John Smith' } } it "turns those string keys into method names" do expect(ros.asdf).to eq "John Smith" end end context 'that contains keys that mirror existing private methods' do let(:hash) { { test: :foo, rand: 'not a number' } } # https://github.com/aetherknight/recursive-open-struct/issues/42 it 'handles subscript notation without calling the method name first (#42)' do expect(ros['test']).to eq :foo expect(ros['rand']).to eq 'not a number' expect(ros.test).to eq :foo expect(ros.rand).to eq 'not a number' end end context 'that contains keys that mirror existing public methods inherited from Object' do let(:hash) { { method: :something } } it 'handles subscript notation without calling the existing methods' do expect(ros[:method]).to eq :something expect(ros['method']).to eq :something end end if [/\A([0-9]+)\.([0-9]+)\.([0-9]+)\z/.match(RUBY_VERSION)].tap { |l| m = l[0] ; l[0] = (m[1].to_i >= 2 && m[2].to_i >= 4) }.first context 'when Ruby 2.4.0 or newer' do specify 'new_ostruct_member! is private' do expect { ros.new_ostruct_member!(:bonsoir) }.to raise_error(NoMethodError) # OpenStruct.new().new_ostruct_member!(:foo) end end end end describe "handling of arbitrary attributes" do subject { RecursiveOpenStruct.new } before(:each) do subject.blah = "John Smith" end describe "#respond?" do it { expect(subject).to respond_to :blah } it { expect(subject).to respond_to :blah= } it { expect(subject).to_not respond_to :asdf } it { expect(subject).to_not respond_to :asdf= } end # describe #respond? describe "#methods" do it { expect(subject.methods.map(&:to_sym)).to include :blah } it { expect(subject.methods.map(&:to_sym)).to include :blah= } it { expect(subject.methods.map(&:to_sym)).to_not include :asdf } it { expect(subject.methods.map(&:to_sym)).to_not include :asdf= } end # describe #methods end # describe handling of arbitrary attributes end # describe behavior it inherits from OpenStruct end recursive-open-struct-1.1.0/spec/recursive_open_struct/indifferent_access_spec.rb0000644000004100000410000001173113251424302030573 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do let(:value) { 'foo' } let(:symbol) { :bar } let(:new_value) { 'bar' } let(:new_symbol) { :foo } describe 'indifferent access' do let(:hash) { {:foo => value, 'bar' => symbol} } let(:hash_ros_opts) { {} } subject(:hash_ros) { RecursiveOpenStruct.new(hash, hash_ros_opts) } context 'setting value with method' do before(:each) do subject.foo = value end it('allows getting with method') { expect(subject.foo).to be value } it('allows getting with symbol') { expect(subject[:foo]).to be value } it('allows getting with string') { expect(subject['foo']).to be value } end context 'setting value with symbol' do before(:each) do subject[:foo] = value end it('allows getting with method') { expect(subject.foo).to be value } it('allows getting with symbol') { expect(subject[:foo]).to be value } it('allows getting with string') { expect(subject['foo']).to be value } end context 'setting value with string' do before(:each) do subject['foo'] = value end it('allows getting with method') { expect(subject.foo).to be value } it('allows getting with symbol') { expect(subject[:foo]).to be value } it('allows getting with string') { expect(subject['foo']).to be value } end context 'overwriting values' do context 'set with method' do before(:each) do subject.foo = value end it('overrides with symbol') do subject[:foo] = new_value expect(subject.foo).to be new_value end it('overrides with string') do subject['foo'] = new_value expect(subject.foo).to be new_value end end context 'set with symbol' do before(:each) do subject[:foo] = value end it('overrides with method') do subject.foo = new_value expect(subject[:foo]).to be new_value end it('overrides with string') do subject['foo'] = new_value expect(subject[:foo]).to be new_value end end context 'set with string' do before(:each) do subject['foo'] = value end it('overrides with method') do subject.foo = new_value expect(subject['foo']).to be new_value end it('overrides with symbol') do subject[:foo] = new_value expect(subject['foo']).to be new_value end end context 'set with hash' do it('overrides with method') do hash_ros.foo = new_value expect(hash_ros[:foo]).to be new_value hash_ros.bar = new_symbol expect(hash_ros['bar']).to be new_symbol end it('overrides with symbol') do hash_ros[:bar] = new_symbol expect(hash_ros['bar']).to be new_symbol end it('overrides with string') do hash_ros['foo'] = new_value expect(hash_ros[:foo]).to be new_value end end context 'when preserve_original_keys is not enabled' do context 'transforms original keys to symbols' do subject(:recursive) { RecursiveOpenStruct.new(recursive_hash, recurse_over_arrays: true) } let(:recursive_hash) { {:foo => [ {'bar' => [ { 'foo' => :bar} ] } ] } } let(:symbolized_recursive_hash) { {:foo => [ {:bar => [ { :foo => :bar} ] } ] } } let(:symbolized_modified_hash) { {:foo => [ {:bar => [ { :foo => :foo} ] } ] } } let(:symbolized_hash) { Hash[hash.map{|(k,v)| [k.to_sym,v]}] } specify 'after initialization' do expect(hash_ros.to_h).to eq symbolized_hash end specify 'in recursive hashes' do expect(recursive.to_h).to eq symbolized_recursive_hash end specify 'after resetting value' do recursive.foo.first[:bar].first[:foo] = :foo expect(recursive.to_h).to eq symbolized_modified_hash end end end context 'when preserve_original_keys is enabled' do context 'preserves the original keys' do subject(:recursive) { RecursiveOpenStruct.new(recursive_hash, recurse_over_arrays: true, preserve_original_keys: true) } let(:recursive_hash) { {:foo => [ {'bar' => [ { 'foo' => :bar} ] } ] } } let(:modified_hash) { {:foo => [ {'bar' => [ { 'foo' => :foo} ] } ] } } let(:hash_ros_opts) { { preserve_original_keys: true }} specify 'after initialization' do expect(hash_ros.to_h).to eq hash end specify 'in recursive hashes' do expect(recursive.to_h).to eq recursive_hash end specify 'after resetting value' do recursive.foo.first[:bar].first[:foo] = :foo expect(recursive.to_h).to eq modified_hash end end end end end end recursive-open-struct-1.1.0/spec/recursive_open_struct/recursion_spec.rb0000644000004100000410000002275313251424302026774 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do describe "recursive behavior" do let(:h) { { :blah => { :another => 'value' } } } subject(:ros) { RecursiveOpenStruct.new(h) } it "can convert the entire hash tree back into a hash" do blank_obj = Object.new h = {:asdf => 'John Smith', :foo => [{:bar => blank_obj}, {:baz => nil}]} ros = RecursiveOpenStruct.new(h) expect(ros.to_h).to eq h expect(ros.to_hash).to eq h end it "returns accessed hashes as RecursiveOpenStructs instead of hashes" do expect(subject.blah.another).to eq 'value' end it "handles subscript notation the same way as dotted notation" do expect(subject.blah.another).to eq subject[:blah].another end it "uses #key_as_a_hash to return key as a Hash" do expect(subject.blah_as_a_hash).to eq({ :another => 'value' }) end describe "handling loops in the original Hashes" do let(:h1) { { :a => 'a'} } let(:h2) { { :a => 'b', :h1 => h1 } } before(:each) { h1[:h2] = h2 } subject { RecursiveOpenStruct.new(h2) } it { expect(subject.h1.a).to eq 'a' } it { expect(subject.h1.h2.a).to eq 'b' } it { expect(subject.h1.h2.h1.a).to eq 'a' } it { expect(subject.h1.h2.h1.h2.a).to eq 'b' } it { expect(subject.h1).to eq subject.h1.h2.h1 } it { expect(subject.h1).to_not eq subject.h1.h2 } end # describe handling loops in the origin Hashes it "can modify a key of a sub-element" do h = { :blah => { :blargh => 'Brad' } } ros = RecursiveOpenStruct.new(h) ros.blah.blargh = "Janet" expect(ros.blah.blargh).to eq "Janet" end context "after a sub-element has been modified" do let(:hash) do { :blah => { :blargh => "Brad" }, :some_array => [ 1, 2, 3] } end let(:updated_hash) do { :blah => { :blargh => "Janet" }, :some_array => [ 1, 2, 3] } end subject { RecursiveOpenStruct.new(hash) } before(:each) { subject.blah.blargh = "Janet" } describe ".to_h" do it "returns a hash tree that contains those modifications" do expect(subject.to_h).to eq updated_hash end specify "modifying the returned hash tree does not modify the ROS" do subject.to_h[:blah][:blargh] = "Dr Scott" expect(subject.blah.blargh).to eq "Janet" end end it "does not mutate the original hash tree passed to the constructor" do expect(hash[:blah][:blargh]).to eq 'Brad' end it "limits the deep-copy to the initial hash tree" do subject.some_array[0] = 4 expect(hash[:some_array][0]).to eq 4 end describe "#dup" do let(:duped_subject) { subject.dup } it "preserves sub-element modifications" do expect(duped_subject.blah.blargh).to eq subject.blah.blargh end it "allows the copy's sub-elements to be modified independently from the original's" do expect(subject.blah.blargh).to eq "Janet" duped_subject.blah.blargh = "Dr. Scott" expect(subject.blah.blargh).to eq "Janet" expect(duped_subject.blah.blargh).to eq "Dr. Scott" end end end context "when memoizing and then modifying entire recursive structures" do subject do RecursiveOpenStruct.new( { :blah => original_blah }, :recurse_over_arrays => true) end before(:each) { subject.blah } # enforce memoization context "when modifying an entire Hash" do let(:original_blah) { { :a => 'A', :b => 'B' } } let(:new_blah) { { :something_new => "C" } } before(:each) { subject.blah = new_blah } it "returns the modified value instead of the memoized one" do expect(subject.blah.something_new).to eq "C" end specify "the old value no longer exists" do expect(subject.blah.a).to be_nil end end context "when modifying an entire Array" do let(:original_blah) { [1, 2, 3] } it "returns the modified value instead of the memoized one" do new_blah = [4, 5, 6] subject.blah = new_blah expect(subject.blah).to eq new_blah end end end describe 'recursing over arrays' do let(:blah_list) { [ { :foo => '1' }, { :foo => '2' }, 'baz' ] } let(:h) { { :blah => blah_list } } context "when recursing over arrays is enabled" do subject { RecursiveOpenStruct.new(h, :recurse_over_arrays => true) } it { expect(subject.blah.length).to eq 3 } it { expect(subject.blah[0].foo).to eq '1' } it { expect(subject.blah[1].foo).to eq '2' } it { expect(subject.blah_as_a_hash).to eq blah_list } it { expect(subject.blah[2]).to eq 'baz' } context "when an inner value changes" do let(:updated_blah_list) { [ { :foo => '1' }, { :foo => 'Dr Scott' }, 'baz' ] } let(:updated_h) { { :blah => updated_blah_list } } before(:each) { subject.blah[1].foo = "Dr Scott" } it "Retains changes across Array lookups" do expect(subject.blah[1].foo).to eq "Dr Scott" end it "propagates the changes through to .to_h across Array lookups" do expect(subject.to_h).to eq({ :blah => [ { :foo => '1' }, { :foo => "Dr Scott" }, 'baz' ] }) end it "deep-copies hashes within Arrays" do subject.to_h[:blah][1][:foo] = "Rocky" expect(subject.blah[1].foo).to eq "Dr Scott" end it "does not mutate the input hash passed to the constructor" do expect(h[:blah][1][:foo]).to eq '2' end it "the deep copy recurses over Arrays as well" do expect(h[:blah][1][:foo]).to eq '2' end describe "#dup" do let(:duped_subject) { subject.dup } it "preserves sub-element modifications" do expect(duped_subject.blah[1].foo).to eq subject.blah[1].foo end it "allows the copy's sub-elements to be modified independently from the original's" do duped_subject.blah[1].foo = "Rocky" expect(duped_subject.blah[1].foo).to eq "Rocky" expect(subject.blah[1].foo).to eq "Dr Scott" end end end context "when array is nested deeper" do let(:deep_hash) { { :foo => { :blah => blah_list } } } subject { RecursiveOpenStruct.new(deep_hash, :recurse_over_arrays => true) } it { expect(subject.foo.blah.length).to eq 3 } it "Retains changes across Array lookups" do subject.foo.blah[1].foo = "Dr Scott" expect(subject.foo.blah[1].foo).to eq "Dr Scott" end end context "when array is in an array" do let(:haah) { { :blah => [ blah_list ] } } subject { RecursiveOpenStruct.new(haah, :recurse_over_arrays => true) } it { expect(subject.blah.length).to eq 1 } it { expect(subject.blah[0].length).to eq 3 } it "Retains changes across Array lookups" do subject.blah[0][1].foo = "Dr Scott" expect(subject.blah[0][1].foo).to eq "Dr Scott" end end end # when recursing over arrays is enabled context "when recursing over arrays is disabled" do subject { RecursiveOpenStruct.new(h) } it { expect(subject.blah.length).to eq 3 } it { expect(subject.blah[0]).to eq({ :foo => '1' }) } it { expect(subject.blah[0][:foo]).to eq '1' } end # when recursing over arrays is disabled describe 'modifying an array and recursing over it' do let(:h) { {} } subject { RecursiveOpenStruct.new(h, recurse_over_arrays: true) } context 'when adding an array with hashes into the tree' do before(:each) do subject.mystery = {} subject.mystery.science = [{ theatre: 9000 }] end it "ROS's it" do expect(subject.mystery.science[0].theatre).to eq 9000 end end context 'when appending a hash to an array' do before(:each) do subject.mystery = {} subject.mystery.science = [] subject.mystery.science << { theatre: 9000 } end it "ROS's it" do expect(subject.mystery.science[0].theatre).to eq 9000 end specify "the changes show up in .to_h" do expect(subject.to_h).to eq({ mystery: { science: [{theatre: 9000}]}}) end end context 'after appending a hash to an array' do before(:each) do subject.mystery = {} subject.mystery.science = [] subject.mystery.science[0] = {} end it "can have new values be set" do expect do subject.mystery.science[0].theatre = 9000 end.to_not raise_error expect(subject.mystery.science[0].theatre).to eq 9000 end end end # modifying an array and then recursing end # recursing over arrays describe 'nested nil values' do let(:h) { { foo: { bar: nil }} } it 'returns nil' do expect(subject.foo.bar).to be_nil end it 'returns a hash with the key and a nil value' do expect(subject.to_hash).to eq({ foo: { bar: nil }}) end end # nested nil values end # recursive behavior end recursive-open-struct-1.1.0/spec/recursive_open_struct/ostruct_2_0_0_spec.rb0000644000004100000410000000477713251424302027353 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do let(:hash) { {:foo => 'foo', 'bar' => :bar} } subject(:ros) { RecursiveOpenStruct.new(hash) } describe "OpenStruct 2.0+ methods" do context "Hash style setter" do it "method exists" do expect(ros.respond_to?('[]=')).to be_truthy end it "changes the value" do ros[:foo] = :foo ros.foo = :foo end end context "delete_field" do before(:each) { ros.delete_field :foo } it "removes the value" do expect(ros.foo).to be_nil expect(ros.to_h).to_not include(:foo) end it "removes the getter method" do is_expected.to_not respond_to :foo end it "removes the setter method" do expect(ros.respond_to? 'foo=').to be_falsey end it "works with indifferent access" do expect(ros.delete_field :bar).to eq :bar is_expected.to_not respond_to :bar is_expected.to_not respond_to 'bar=' expect(ros.to_h).to be_empty end end context "eql?" do subject(:new_ros) { ros.dup } context "with identical ROS" do subject { ros } it { is_expected.to be_eql ros } end context "with similar ROS" do subject { RecursiveOpenStruct.new(hash) } it { is_expected.to be_eql ros } end context "with same Hash" do subject { RecursiveOpenStruct.new(hash, recurse_over_arrays: true) } it { is_expected.to be_eql ros } end context "with duplicated ROS" do subject { ros.dup } it "fails on different value" do subject.foo = 'bar' is_expected.not_to be_eql ros end it "fails on missing field" do subject.delete_field :bar is_expected.not_to be_eql ros end it "fails on added field" do subject.baz = :baz is_expected.not_to be_eql ros end end end context "hash" do it "calculates table hash" do expect(ros.hash).to be ros.instance_variable_get('@table').hash end end context "each_pair" do it "iterates over hash keys, with keys as symbol" do ros_pairs = [] ros.each_pair {|k,v| ros_pairs << [k,v]} hash_pairs = [] {:foo => 'foo', :bar => :bar}.each_pair {|k,v| hash_pairs << [k,v]} expect(ros_pairs).to match (hash_pairs) end end end # describe OpenStruct 2.0+ methods end recursive-open-struct-1.1.0/spec/recursive_open_struct/recursion_and_subclassing_spec.rb0000644000004100000410000000065713251424302032212 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do describe "subclassing RecursiveOpenStruct" do let(:subclass) { Class.new(RecursiveOpenStruct) } subject(:rossc) { subclass.new({ :one => [{:two => :three}] }, recurse_over_arrays: true) } specify "nested objects use the subclass of the parent" do expect(rossc.one.first.class).to eq subclass end end end recursive-open-struct-1.1.0/spec/recursive_open_struct/ostruct_2_3_0_spec.rb0000644000004100000410000000343613251424302027345 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do describe "OpenStruct 2.3.0+ methods" do describe "#dig" do # We only care when Ruby supports `#dig`. if OpenStruct.public_instance_methods.include? :dig context "recurse_over_arrays: false" do subject { RecursiveOpenStruct.new(a: { b: 2, c: ["doo", "bee", { inner: "one"}]}) } describe "OpenStruct-like behavior" do it { expect(subject.dig(:a, :b)).to eq 2 } it { expect(subject.dig(:a, :c, 0)).to eq "doo" } it { expect(subject.dig(:a, :c, 2, :inner)).to eq "one" } end describe "recursive behavior" do it { expect(subject.dig(:a)).to eq RecursiveOpenStruct.new( { b: 2, c: ["doo", "bee", { inner: "one"}]} ) } it { expect(subject.dig(:a, :c, 2)).to eq({inner: "one"}) } end end context "recurse_over_arrays: true" do subject { RecursiveOpenStruct.new({a: { b: 2, c: ["doo", "bee", { inner: "one"}]}}, recurse_over_arrays: true) } describe "OpenStruct-like behavior" do it { expect(subject.dig(:a, :b)).to eq 2 } it { expect(subject.dig(:a, :c, 0)).to eq "doo" } it { expect(subject.dig(:a, :c, 2, :inner)).to eq "one" } end describe "recursive behavior" do it { expect(subject.dig(:a)).to eq RecursiveOpenStruct.new( { b: 2, c: ["doo", "bee", { inner: "one"}]} ) } it { expect(subject.dig(:a, :c, 2)).to eq RecursiveOpenStruct.new(inner: "one") } end end end end # describe #dig end # describe OpenStruct 2.3+ methods end recursive-open-struct-1.1.0/spec/recursive_open_struct/debug_inspect_spec.rb0000644000004100000410000000441613251424302027572 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do describe "#debug_inspect" do before(:each) do h1 = { :a => 'a'} h2 = { :a => 'b', :h1 => h1 } h1[:h2] = h2 @ros = RecursiveOpenStruct.new(h2) end it "should have a simple way of display" do @output = <<-QUOTE a = "b" h1. a = "a" h2. a = "b" h1. a = "a" h2. a = "b" h1. a = "a" h2. a = "b" h1. a = "a" h2. a = "b" h1. a = "a" h2. a = "b" h1. a = "a" h2. (recursion limit reached) QUOTE @io = StringIO.new @ros.debug_inspect(@io) expect(@io.string).to match /^a = "b"$/ expect(@io.string).to match /^h1\.$/ expect(@io.string).to match /^ a = "a"$/ expect(@io.string).to match /^ h2\.$/ expect(@io.string).to match /^ a = "b"$/ expect(@io.string).to match /^ h1\.$/ expect(@io.string).to match /^ a = "a"$/ expect(@io.string).to match /^ h2\.$/ expect(@io.string).to match /^ a = "b"$/ expect(@io.string).to match /^ h1\.$/ expect(@io.string).to match /^ a = "a"$/ expect(@io.string).to match /^ h2\.$/ expect(@io.string).to match /^ a = "b"$/ expect(@io.string).to match /^ h1\.$/ expect(@io.string).to match /^ a = "a"$/ expect(@io.string).to match /^ h2\.$/ expect(@io.string).to match /^ a = "b"$/ expect(@io.string).to match /^ h1\.$/ expect(@io.string).to match /^ a = "a"$/ expect(@io.string).to match /^ h2\.$/ expect(@io.string).to match /^ a = "b"$/ expect(@io.string).to match /^ h1\.$/ expect(@io.string).to match /^ a = "a"$/ expect(@io.string).to match /^ h2\.$/ expect(@io.string).to match /^ \(recursion limit reached\)$/ end end end recursive-open-struct-1.1.0/.travis.yml0000644000004100000410000000055713251424302020145 0ustar www-datawww-data--- language: ruby rvm: # - 1.9.3 # json gem now requires Ruby ~> 2.0 - 2.0.0 - 2.1.10 - 2.2.9 - 2.3.6 - 2.4.3 - ruby-head - jruby-19mode - jruby-9.1.9.0 - jruby-head sudo: false matrix: allow_failures: # No longer supported - rvm: 2.0.0 - rvm: 2.1.10 - rvm: jruby-19mode # Future - rvm: ruby-head - rvm: jruby-head recursive-open-struct-1.1.0/lib/0000755000004100000410000000000013251424302016573 5ustar www-datawww-datarecursive-open-struct-1.1.0/lib/recursive_open_struct.rb0000644000004100000410000001113013251424302023550 0ustar www-datawww-datarequire 'ostruct' require 'recursive_open_struct/version' require 'recursive_open_struct/debug_inspect' require 'recursive_open_struct/deep_dup' require 'recursive_open_struct/ruby_19_backport' require 'recursive_open_struct/dig' # TODO: When we care less about Rubies before 2.4.0, match OpenStruct's method # names instead of doing things like aliasing `new_ostruct_member` to # `new_ostruct_member!` class RecursiveOpenStruct < OpenStruct include Ruby19Backport if RUBY_VERSION =~ /\A1.9/ include Dig if OpenStruct.public_instance_methods.include? :dig include DebugInspect def initialize(hash=nil, args={}) hash ||= {} @recurse_over_arrays = args.fetch(:recurse_over_arrays, false) @preserve_original_keys = args.fetch(:preserve_original_keys, false) @deep_dup = DeepDup.new( recurse_over_arrays: @recurse_over_arrays, preserve_original_keys: @preserve_original_keys ) @table = args.fetch(:mutate_input_hash, false) ? hash : @deep_dup.call(hash) @sub_elements = {} end def initialize_copy(orig) super # deep copy the table to separate the two objects @table = @deep_dup.call(orig.instance_variable_get(:@table)) # Forget any memoized sub-elements @sub_elements = {} end def to_h @deep_dup.call(@table) end alias_method :to_hash, :to_h def [](name) key_name = _get_key_from_table_(name) v = @table[key_name] if v.is_a?(Hash) @sub_elements[key_name] ||= self.class.new( v, recurse_over_arrays: @recurse_over_arrays, preserve_original_keys: @preserve_original_keys, mutate_input_hash: true ) elsif v.is_a?(Array) and @recurse_over_arrays @sub_elements[key_name] ||= recurse_over_array(v) @sub_elements[key_name] = recurse_over_array(@sub_elements[key_name]) else v end end # Makes sure ROS responds as expected on #respond_to? and #method requests def respond_to_missing?(mid, include_private = false) mname = _get_key_from_table_(mid.to_s.chomp('=').chomp('_as_a_hash')) @table.key?(mname) || super end # Adapted implementation of method_missing to accommodate the differences # between ROS and OS. # # TODO: Use modifiable? instead of modifiable, and new_ostruct_member! # instead of new_ostruct_member once we care less about Rubies before 2.4.0. def method_missing(mid, *args) len = args.length if mid =~ /^(.*)=$/ if len != 1 raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1) end modifiable[new_ostruct_member!($1.to_sym)] = args[0] elsif len == 0 key = mid key = $1 if key =~ /^(.*)_as_a_hash$/ if @table.key?(_get_key_from_table_(key)) new_ostruct_member!(key) send(mid) end else err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args err.set_backtrace caller(1) raise err end end # TODO: Rename to new_ostruct_member! once we care less about Rubies before # 2.4.0. def new_ostruct_member(name) key_name = _get_key_from_table_(name) unless self.singleton_class.method_defined?(name.to_sym) class << self; self; end.class_eval do define_method(name) do self[key_name] end define_method("#{name}=") do |x| @sub_elements.delete(key_name) modifiable[key_name] = x end define_method("#{name}_as_a_hash") { @table[key_name] } end end key_name end # Support Ruby 2.4.0+'s changes in a way that doesn't require dynamically # modifying ROS. # # TODO: Once we care less about Rubies before 2.4.0, reverse this so that # new_ostruct_member points to our version and not OpenStruct's. alias new_ostruct_member! new_ostruct_member # new_ostruct_member! is private, but new_ostruct_member is not on OpenStruct in 2.4.0-rc1?! private :new_ostruct_member! def delete_field(name) sym = _get_key_from_table_(name) singleton_class.__send__(:remove_method, sym, "#{sym}=") rescue NoMethodError # ignore if methods not yet generated. @sub_elements.delete sym @table.delete sym end private def _get_key_from_table_(name) return name.to_s if @table.has_key?(name.to_s) return name.to_sym if @table.has_key?(name.to_sym) name end def recurse_over_array(array) array.each_with_index do |a, i| if a.is_a? Hash array[i] = self.class.new(a, :recurse_over_arrays => true, :mutate_input_hash => true, :preserve_original_keys => @preserve_original_keys) elsif a.is_a? Array array[i] = recurse_over_array a end end array end end recursive-open-struct-1.1.0/lib/recursive_open_struct/0000755000004100000410000000000013251424302023227 5ustar www-datawww-datarecursive-open-struct-1.1.0/lib/recursive_open_struct/dig.rb0000644000004100000410000000102713251424302024317 0ustar www-datawww-dataclass RecursiveOpenStruct < OpenStruct module Dig # Replaces +OpenStruct#dig+ to properly support treating nested values as # RecursiveOpenStructs instead of returning the nested Hashes. def dig(name, *names) begin name = name.to_sym rescue NoMethodError raise TypeError, "#{name} is not a symbol nor a string" end name_val = self[name] if names.length > 0 && name_val.respond_to?(:dig) name_val.dig(*names) else name_val end end end end recursive-open-struct-1.1.0/lib/recursive_open_struct/debug_inspect.rb0000644000004100000410000000264113251424302026372 0ustar www-datawww-datamodule RecursiveOpenStruct::DebugInspect def debug_inspect(io = STDOUT, indent_level = 0, recursion_limit = 12) display_recursive_open_struct(io, @table, indent_level, recursion_limit) end def display_recursive_open_struct(io, ostrct_or_hash, indent_level, recursion_limit) if recursion_limit <= 0 then # protection against recursive structure (like in the tests) io.puts ' '*indent_level + '(recursion limit reached)' else #puts ostrct_or_hash.inspect if ostrct_or_hash.is_a?(self.class) then ostrct_or_hash = ostrct_or_hash.marshal_dump end # We'll display the key values like this : key = value # to align display, we look for the maximum key length of the data that will be displayed # (everything except hashes) data_indent = ostrct_or_hash \ .reject { |k, v| v.is_a?(self.class) || v.is_a?(Hash) } \ .max {|a,b| a[0].to_s.length <=> b[0].to_s.length}[0].to_s.length # puts "max length = #{data_indent}" ostrct_or_hash.each do |key, value| if (value.is_a?(self.class) || value.is_a?(Hash)) then io.puts ' '*indent_level + key.to_s + '.' display_recursive_open_struct(io, value, indent_level + 1, recursion_limit - 1) else io.puts ' '*indent_level + key.to_s + ' '*(data_indent - key.to_s.length) + ' = ' + value.inspect end end end true end end recursive-open-struct-1.1.0/lib/recursive_open_struct/ruby_19_backport.rb0000644000004100000410000000112413251424302026731 0ustar www-datawww-datamodule RecursiveOpenStruct::Ruby19Backport # Apply fix if necessary: # https://github.com/ruby/ruby/commit/2d952c6d16ffe06a28bb1007e2cd1410c3db2d58 def initialize_copy(orig) super @table.each_key{|key| new_ostruct_member(key)} end def []=(name, value) modifiable[new_ostruct_member(name)] = value end def eql?(other) return false unless other.kind_of?(OpenStruct) @table.eql?(other.table) end def hash @table.hash end def each_pair return to_enum(:each_pair) { @table.size } unless block_given? @table.each_pair{|p| yield p} end end recursive-open-struct-1.1.0/lib/recursive_open_struct/version.rb0000644000004100000410000000025313251424302025241 0ustar www-datawww-data# Necessary since the top-level class/module is a class that inherits from # OpenStruct. require 'ostruct' class RecursiveOpenStruct < OpenStruct VERSION = "1.1.0" end recursive-open-struct-1.1.0/lib/recursive_open_struct/deep_dup.rb0000644000004100000410000000160413251424302025342 0ustar www-datawww-datarequire 'set' class RecursiveOpenStruct::DeepDup def initialize(opts={}) @recurse_over_arrays = opts.fetch(:recurse_over_arrays, false) @preserve_original_keys = opts.fetch(:preserve_original_keys, false) end def call(obj) deep_dup(obj) end private def deep_dup(obj, visited=Set.new) if obj.is_a?(Hash) obj.each_with_object({}) do |(key, value), h| h[@preserve_original_keys ? key : key.to_sym] = value_or_deep_dup(value, visited) end elsif obj.is_a?(Array) && @recurse_over_arrays obj.each_with_object([]) do |value, arr| value = value.is_a?(RecursiveOpenStruct) ? value.to_h : value arr << value_or_deep_dup(value, visited) end else obj end end def value_or_deep_dup(value, visited) obj_id = value.object_id visited.include?(obj_id) ? value : deep_dup(value, visited << obj_id) end end recursive-open-struct-1.1.0/lib/recursive-open-struct.rb0000644000004100000410000000004013251424302023402 0ustar www-datawww-datarequire 'recursive_open_struct' recursive-open-struct-1.1.0/AUTHORS.txt0000644000004100000410000000135613251424302017720 0ustar www-datawww-dataRecursive-open-struct was written by these fine people: * Beni Cherniavsky-Paskin * Cédric Felizard * Edward Betts * Federico Aloi * fervic * Joe Rafaniello * Kris Dekeyser * Matt Culpepper * Matthew O'Riordan * Offirmo * Pedro Sena * Peter Yeremenko * Sebastian Gaul * Thiago Guimaraes * Tom Chapin * Victor Guzman * William (B.J.) Snow Orvis recursive-open-struct-1.1.0/.gitignore0000644000004100000410000000147413251424302020023 0ustar www-datawww-data# rcov generated coverage # rdoc generated rdoc # yard generated doc .yardoc # bundler .bundle # jeweler generated pkg # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: # # * Create a file at ~/.gitignore # * Include files you want ignored # * Run: git config --global core.excludesfile ~/.gitignore # # After doing this, these files will be ignored in all your git projects, # saving you from having to 'pollute' every project you touch with them # # 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) # # For MacOS: # .DS_Store # For TextMate *.tmproj tmtags # For emacs: *~ \#* .\#* # For vim: *.swp # For redcar: #.redcar # For rubinius: #*.rbc Gemfile.lock .ruby-version recursive-open-struct-1.1.0/CHANGELOG.md0000644000004100000410000001551113251424302017641 0ustar www-datawww-data1.1.0 / 2018-02-03 ================== * NEW/FIX [#56](https://github.com/aetherknight/recursive-open-struct/issues/56): Add better support for Ruby 2.3+'s `#dig` method (when it exists for the current version of Ruby), so that nested Hashes are properly converted to RecursiveOpenStructs. `OpenStruct#dig`'s implementation was returning Hashes and does not handle `recurse_over_arrays` so ROS needs special support. Thanks to maxp-edcast for reporting the issue. * FIX [#55](https://github.com/aetherknight/recursive-open-struct/pull/55): EdwardBetts: Fixed a typo in the documentation/comment for `#method_missing` 1.0.5 / 2017-06-21 ================== * FIX [#54](https://github.com/aetherknight/recursive-open-struct/pull/54): Beni Cherniavsky-Paskin: Improve performance of `new_ostruct_member` by using `self.singleton_class.method_defined?` instead of `self.methods.include?` 1.0.4 / 2017-04-29 ================== * FIX [#52](https://github.com/aetherknight/recursive-open-struct/pull/52): Joe Rafaniello: Improve performance of DeepDup by using Set instead of an Array to track visited nodes. 1.0.3 / 2017-04-10 ================== * No longer officially supporting Ruby 2.0.0 and Ruby 2.1.x. They are still tested against but are permitted to fail within the Travis configuration. * FIX: Fix subscript notation for keys that collide with existing public methods. Related to [#51](https://github.com/aetherknight/recursive-open-struct/issues/51). * FIX [#49](https://github.com/aetherknight/recursive-open-struct/issues/49): Ensure test suite passes with Ruby 2.4.0-rc1. 1.0.2 / 2016-12-20 ================== * FIX [#46](https://github.com/aetherknight/recursive-open-struct/issues/46): Pedro Sena: fix issues with mutating arrays within an ROS that has `recurse_over_arrays: true` 1.0.1 / 2016-01-18 ================== * FIX [#42](https://github.com/aetherknight/recursive-open-struct/issues/42): `[]` tried to call private methods if they existed instead of triggering the `method_missing` code path. Thanks to @SaltwaterC for reporting. 1.0.0 / 2015-12-11 ================== * API-Breaking Change: Frederico Aloi: Change `to_h` to always return symbol keys. This is more consistent with OpenStruct. * API-Breaking Change: No longer officially supporting Ruby 1.9.3. * NEW/FIX: Kris Dekeyser: Ensure that ROS continues to work with the new version of OpenStruct included in dev versions of Ruby 2.2.x and Ruby 2.3. It now implements lazy attribute creation, which broke ROS. * NEW: Added `preserve_original_keys` option to revert to the 0.x behavior. Set it to true if you want methods like `to_h` to return strings and perhaps other non-symbols. * NEW: Ensuring support for Ruby 2.0.0+ including the upcoming 2.3 release and JRuby 9000. * FIX: Peter Yeremenko: Fix a mistake in one of the examples in the README 0.6.5 / 2015-06-30 ================== * FIX: Fix ROS when initialized with nil instead of a hash. 0.6.4 / 2015-05-20 ================== * FIX: Kris Dekeyser: Fix indifferent subscript access (string or symbol). Also backported several ostruct methods for Ruby 1.9.x. * FIX: Partial fix for allowing an array in a RecursiveOpenStruct tree to be modified. However, methods such as to_hash are still broken. 0.6.3 / 2015-04-11 ================== * FIX: Thiago Guimaraes: Restore being able to create an ROS from a hash that contains strings for keys instead of symbols for keys. 0.6.2 / 2015-04-07 ================== * FIX: fervic: Address a bug in the Ruby 1.9's version of OpenStruct's `dup` * FIX: Victor Guzman: Reset memoized values on assignment in order to force the implementation to re-memoize them. * MAINT: fervic: Simplified `initialize` 0.6.1 / 2015-03-28 ================== * FIX: Actually ensure that the internal @table is properly dependent or independent of the input hash tree. I mistakenly refactored away an important piece of code that fervic added. * FIX: Actually ensure that `#dup` works. * Also refactored how `#to_h` is implemented to use newer plumbing. 0.6.0 / 2015-03-28 ================== * NEW: fervic: Make subscript notation be recursive like dot-method notation * NEW: fervic: Added a new option, `:mutate_input_hash`, that allows the caller to determine whether the original hash is mutated or not when a nested value in the ROS tree is modified. If false (the default), the ROS will not modify the original hash tree. If tree, changes within the ROS tree will also be reflected in the hash tree. * FIX: fervic: Setting/updating a value nested deep in an ROS tree is kept when the top-level ROS object is duped. * MAINT: Extracted `#deep_dup` added by fervic into its own class. This makes it possibly easier to use/copy for others, and it cleans up the main class file. * MAINT: Moved `#debug_inspect` out to its own module. This cleans up the main class file a bit. It is also something I may remove if I ever have a major version bump. * MAINT: Adding MRI 2.2 to Travis-CI 0.5.0 / 2014-06-14 ================== * NEW: Tom Chapin: Added a `#to_hash` alias for `#to_h` * MAINT: Added Travis-CI support. Testing against MRI 1.9.3, MRI 2.0, MRI 2.1, and JRuby in 1.9 mode. Not aiming to support 1.8.7 since it has been nearly a year since it has officially been retired. 0.4.5 / 2013-10-23 ================== * FIX: Matt Culpepper: Allow ROS subclasses to use their own type when creating nested objects in the tree. 0.4.4 / 2013-08-28 ================== * FIX: Ensure proper file permissions when building the gem archive 0.4.3 / 2013-05-30 ================== * FIX: Sebastian Gaul: Make `recurse_over_arrays` option work on more deeply-nested hashes. 0.4.2 / 2013-05-29 ================== * FIX: Setting a value on a nested element, then getting that value should show the updated value * FIX: Calling `#to_h` on the top-level ROS object also reflects changed nested elements. 0.4.1 / 2013-05-28 ================== * FIX: Fixing the `spec:coverage` Rake task 0.4.0 / 2013-05-26 ================== * NEW: Added `#to_h` * MAINT: Stopped using jeweler for gem development/packaging 0.3.1 / 2012-10-23 ================== * FIX: Cédric Felizard: Fix to make it work with MRI 1.8.7 again * MAINT: More spec fixups to improve spec runs on MRI 1.9.3 0.3.0 / 2013-10-23 ================== * NEW: Matthew O'Riordan: Add support for recursion working over Arrays * NEW: Made recursion over Arrays optional with `recurse_over_arrays` option. * NEW: Improving `#debug_inspect` so that it can use any IO object, not just STDOUT. * MAINT: Much cleanup of development dependencies, README file, etc. 0.2.1 / 2011-05-31 ================== * FIX: Offirmo: Slight improvement for `#debug_inspect` 0.2.0 / 2011-05-25 ================== * NEW: Offirmo: Added `debug_inspect` * MAINT: Offirmo: Worked the development files so that it can be built as a gem 0.1.0 / 2010-01-12 ================== * Initial release recursive-open-struct-1.1.0/README.md0000644000004100000410000000444013251424302017306 0ustar www-datawww-data# recursive-open-struct OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs. It allows for hashes within hashes to be called in a chain of methods: ros = RecursiveOpenStruct.new( { fooa: { foob: 'fooc' } } ) ros.fooa.foob # => 'fooc' Also, if needed, nested hashes can still be accessed as hashes: ros.fooa_as_a_hash # { foob: 'fooc' } RecursiveOpenStruct can also optionally recurse across arrays, although you have to explicitly enable it: h = { :somearr => [ { name: 'a'}, { name: 'b' } ] } ros = RecursiveOpenStruct.new(h, recurse_over_arrays: true ) ros.somearr[0].name # => 'a' ros.somearr[1].name # => 'b' Also, by default it will turn all hash keys into symbols internally: h = { 'fear' => 'is', 'the' => 'mindkiller' } } ros = RecursiveOpenStruct.new(h) ros.to_h # => { fear: 'is', the: 'mindkiller' } You can preserve the original keys by enabling `:preserve_original_keys`: h = { 'fear' => 'is', 'the' => 'mindkiller' } } ros = RecursiveOpenStruct.new(h, preserve_original_keys: true) ros.to_h # => { 'fear' => 'is', 'the' => 'mindkiller' } ## Installation Available as a gem in rubygems, the default gem repository. If you use bundler, just throw that in your gemfile : gem 'recursive-open-struct' You may also install the gem manually : gem install recursive-open-struct ## Contributing For simple bug fixes, feel free to provide a pull request. For anything else (new features, bugs that you want to report, and bugs that are difficult to fix), I recommend opening an issue first to discuss the feature or bug. I am fairly cautious about adding new features that might cause RecursiveOpenStruct's API to deviate radically from OpenStruct's (since it might introduce new reserved method names), and it is useful to discuss the best way to solve a problem when there are tradeoffs or imperfect solutions. When contributing code that changes behavior or fixes bugs, please include unit tests to cover the new behavior or to provide regression testing for bugs. Also, treat the unit tests as documentation --- make sure they are clean, clear, and concise, and well organized. ## Copyright Copyright (c) 2009-2018, The Recursive-open-struct developers (given in the file AUTHORS.txt). See LICENSE.txt for details. recursive-open-struct-1.1.0/.document0000644000004100000410000000006713251424302017647 0ustar www-datawww-datalib/**/*.rb bin/* - features/**/*.feature LICENSE.txt