I’m starting this series by taking a look at Ruby Facets. Facets is a collection of Ruby core and Ruby standard library extensions. It has a lot of good code embedded in it from many contributors so it should be a good place to start looking for some new ideas.
The Code
1 2 3 4 5 6 |
# File lib/core/facets/hash/autonew.rb, line 19 def self.autonew(*args) #new(*args){|a,k| a[k] = self.class::new(*args)} leet = lambda { |hsh, key| hsh[key] = new( &leet ) } new(*args,&leet) end |
Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#!/usr/bin/ruby -wKU # # Code Reading #1 require '../base' require 'facets/hash/autonew' class HashAutonew def data @data = Hash.autonew @data['users']['eric']['blog'] = 'http://theadmin.org' @data end end ap(HashAutonew.new.data) class HashAutonewTest < Test::Unit::TestCase def test_should_nest_values_automatically hashish = HashAutonew.new.data assert hashish.key?('users') assert hashish['users'].key?('eric') assert hashish['users']['eric'].key?('blog') assert_equal 'http://theadmin.org', hashish['users']['eric']['blog'] end end |
Review
Hash#autonew
is wrapping Hash#new
using #new
‘s block form. I can’t dig to deep into Hash#new
because it’s implemented in C but it looks like the lambda is using recursion to set a bunch of default values for the hash when it’s accessed with child elements. Hash#new
supports this but it only works one level deep:
1 2 3 4 5 6 7 8 9 10 11 |
>> Hash.new {} >> Hash.new{|hash, key| 'default'} {} >> h = Hash.new{|hash, key| 'default'} {} >> h['s'] "default" >> h['s']['s']['s'] NoMethodError: undefined method `[]' for nil:NilClass from (irb):5 |
So in my test:
-
@data
is getting a ‘users’ key with the value of - a new hash with the key ‘eric’ with the value of
- a new hash with the value of ‘http://theadmin.org’
- (recursion stops)
The recursion stops with the ‘http://theadmin.org’ string because #autonew
is passing String#new
the leet
block but String#new
doesn’t do anything with the block and just returns the string.
Sidebar: Precise loading
I noticed that facets provides many different ways to load it’s extensions:
-
require 'facets'
– load everything -
require 'facets/hash'
– only load the Hash extensions -
require 'facets/hash/autonew'
– only load theHash#autonew
extension
This is very useful since it lets a developer only extend what they need and is a nice change from ActiveSupport
which loads everything at once.