Skip to content

Commit

Permalink
Use ordered hashes internally when available
Browse files Browse the repository at this point in the history
  • Loading branch information
mbklein committed Nov 10, 2011
1 parent db5ae90 commit 47b6e64
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -4,3 +4,4 @@ Gemfile.lock
pkg/*
.yardoc
coverage
rdoc
13 changes: 12 additions & 1 deletion README.md
Expand Up @@ -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
Expand Down
37 changes: 28 additions & 9 deletions lib/confstruct/hash_with_struct_access.rb
Expand Up @@ -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
Expand All @@ -22,7 +36,7 @@ def symbolize key
end
end

def initialize hash = {}
def initialize hash = @@hash_class.new
super(hash)
end

Expand All @@ -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
Expand Down Expand Up @@ -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?
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions spec/confstruct/hash_with_struct_access_spec.rb
Expand Up @@ -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 = {
Expand Down

0 comments on commit 47b6e64

Please sign in to comment.