After having revisited ActiveSupport a few weeks ago to investigate Hash#reverse_merge!, I noticed ActiveSupport::HashWithIndifferentAccess.Excessive object allocation and branching in the critical path scratched an itch and spawned hwia.
Ruby hashes and the symbol table
MRI ships with a generic hash table for storage of variables, methods, class inheritance chains and the like.Please head off to an excellent overview on this subject by Jake Douglas and please do come back.
Hashes is tightly coupled with the symbol table implementation and as such requires a comparison (2 values as arguments) function
as well as a hashing function for each value (single argument)
A new object hash structure wraps the hash callback functions and is assigned as the hash type to the symbol table that represent the hash object.
Hashing symbols and strings
Symbols and strings are somewhat interchangeable at a lower level and conversion between either is relatively snappy.We extract a strhash function ( leeched from st.c ) and define
String#strhash
Symbol#strhash # thanks raggi!
'key'.strhash == :key.strhash #=> true
We also now need to define new hash and comparison functions to take advantage of this feature.Apologies for the fugly 1.8 / 1.9 conditionals.
and let the symbol table that powers our string like hash call them
Conversion and coercion
Special case handling for certain value assignments is required for the most prolific use case, that of representing request parameters, in a possibly nested fashion.
- Hash converts to StrHash
- Array converts Hash elements to StrHash
Installation
sudo gem install methodmissing-hwia
'require "hwia_rails"' # from within an initializer
This extension is compatible with MRI > 1.8.6 && 1.9.2 and Rails > 2.3.x
Examples
The Rails compatibility layer …
methodmissing:~ lourens$ irb
>> require 'rubygems'
=> true
>> require 'hwia_rails'
LoadError: Rails environment required!
from /opt/local/lib/ruby/gems/1.8/gems/hwia-1.0.0/lib/hwia_rails.rb:2
from /opt/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:36:in `gem_original_require'
from /opt/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:36:in `require'
from (irb):2
>> require 'activesupport'
=> true
>> require 'hwia_rails'
=> []
>> HashWithIndifferentAccess
=> StrHash
… basic usage
methodmissing:~ lourens$ irb
>> require 'rubygems'
=> true
>> require 'hwia'
=> true
>> h = StrHash[:a => 1, 'b' => 2]
=> {:a=>1, "b"=>2}
>> h[:a]
=> 1
>> h['a']
=> 1
>> h[:b]
=> 2
>> h.class
=> StrHash
>> h = { 'a' => 1, 'b' => 2 }
=> {"a"=>1, "b"=>2}
>> h = h.strhash
=> {"a"=>1, "b"=>2}
>> h[:a]
=> 1
>> h[:b]
=> 2
This implementation is compatible with the current (30/08/2009) Rails master branch’s test suite.Please refer to the test cases of either project for further examples.
Performance
Benchmarks available from the README.
You’d notice very little, if any, performance improvements using this extension in the request / response cycle for the vast majority of apps.HashWithIndifferentAccess is limited to query params and ActiveRecord nested attributes assignment.
Consider this a step towards an extraction of a query param object, reusable across many different frameworks and projects.
Follow me @ github or twitter if you enjoyed this article.Thanks for reading!