From 47b6e64e85959ae380b91a2a3d2ab29cb3e0a8e8 Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Thu, 10 Nov 2011 14:01:22 -0800 Subject: [PATCH] Use ordered hashes internally when available --- .gitignore | 1 + README.md | 13 ++++++- lib/confstruct/hash_with_struct_access.rb | 37 ++++++++++++++----- .../hash_with_struct_access_spec.rb | 5 +++ 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 1f296e2..e8c3985 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Gemfile.lock pkg/* .yardoc coverage +rdoc diff --git a/README.md b/README.md index f14b102..fb7341a 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,18 @@ define read-only, dynamic configuration attributes => {:project=>"confstruct", :github=>{:branch=>"master", :url=>"http://www.github.com/mbklein/confstruct"}} config.github.url => "http://www.github.com/mbklein/confstruct" - + +### Notes + +* Confstruct will attempt to use ordered hashes internally when available. + * In Ruby 1.9 and above, this is automatic. + * In Rubies earlier than 1.9, Confstruct will try to require and use ActiveSupport::OrderedHash, + falling back to a regular Hash if necessary. The class/instance method `ordered?` can be used + to determine if the hash ordered or not. +* In order to support struct access, all hash keys are converted to symbols, and are accessible + both as strings and symbols (like a `HashWithIndifferentAccess`). In other words, config['foo'] + and config[:foo] refer to the same value. + ## Release History ## Contributing to confstruct diff --git a/lib/confstruct/hash_with_struct_access.rb b/lib/confstruct/hash_with_struct_access.rb index fb78d1f..ecd1903 100644 --- a/lib/confstruct/hash_with_struct_access.rb +++ b/lib/confstruct/hash_with_struct_access.rb @@ -2,16 +2,30 @@ require 'confstruct/utils' module Confstruct - class HashWithStructAccess < DelegateClass(Hash) - + if ::RUBY_VERSION < '1.9' + begin + require 'active_support/ordered_hash' + class HashWithStructAccess < DelegateClass(ActiveSupport::OrderedHash); @@ordered = true; @@hash_class = ActiveSupport::OrderedHash; end + rescue LoadError, NameError + class HashWithStructAccess < DelegateClass(Hash); @@ordered = false; @@hash_class = Hash; end + end + else + class HashWithStructAccess < DelegateClass(Hash); @@ordered = true; @@hash_class = Hash; end + end + + class HashWithStructAccess class << self def from_hash hash symbolized_hash = symbolize_hash hash self.new(symbolized_hash) end + def ordered? + @@ordered + end + def symbolize_hash hash - hash.inject({}) do |h,(k,v)| + hash.inject(@@hash_class.new) do |h,(k,v)| h[symbolize k] = v.is_a?(Hash) ? symbolize_hash(v) : v h end @@ -22,7 +36,7 @@ def symbolize key end end - def initialize hash = {} + def initialize hash = @@hash_class.new super(hash) end @@ -36,15 +50,16 @@ def [] key def []= key,value k = symbolize(key) - if value.is_a?(Hash) and self[k].is_a?(Hash) - self[k].replace(value) + v = (value.is_a?(Hash) and not value.is_a?(HashWithStructAccess)) ? HashWithStructAccess.new(value) : value + if v.is_a?(Hash) and self[k].is_a?(Hash) + self[k].replace(v) else - result = super(k, value) + super(k, v) end end def deep_copy - result = self.class.new({}) + result = self.class.new(@@hash_class.new) self.each_pair do |k,v| if v.respond_to?(:deep_copy) result[k] = v.deep_copy @@ -93,7 +108,7 @@ def method_missing sym, *args, &block else result = self[name] if result.nil? and block_given? - result = self[name] = HashWithStructAccess.new({}) + result = self[name] = HashWithStructAccess.new(@@hash_class.new) end if result.is_a?(HashWithStructAccess) and block_given? @@ -116,6 +131,10 @@ def methods super + key_methods.compact.flatten end + def ordered? + self.class.ordered? + end + def respond_to? arg super(arg) || keys.include?(symbolize(arg.to_s.sub(/=$/,''))) end diff --git a/spec/confstruct/hash_with_struct_access_spec.rb b/spec/confstruct/hash_with_struct_access_spec.rb index 50bf1fc..a030b5d 100644 --- a/spec/confstruct/hash_with_struct_access_spec.rb +++ b/spec/confstruct/hash_with_struct_access_spec.rb @@ -10,6 +10,11 @@ hwsa.should == {} end + it "should respond to #ordered?" do + hwsa = Confstruct::HashWithStructAccess.new + [true,false].should include(hwsa.ordered?) + end + context "data manipulation" do before :all do @hash = {