Freezing Deep Ruby Data Structures Sunday, August 24, 2008

On one of my current ruby projects, I'm reading in a YML file and using the generated data structure as a hackish set of global configuation settings:

firm_1:
    departments:
        sales: 419
        executive: 999
        IT: 232
    locations:
        NY: 19
        WV: 27
        CA: 102
firm_2:
    ...

Because these should be treated as constants, they should not be overwritten (accidentally, of course). I wanted to go ahead and freeze them:

global_conf = YAML.load_file("...")
global_conf.freeze
global_conf['firm_1'] = {'foo' => 'bar'}
=> TypeError: can't modify frozen hash

But, as you probably know, Ruby's freeze doesn't affect the objects in a container.

global_conf['firm_1']['departments'] = {'foo' => 'bar'}
=> {"foo"=>"bar"}

That's bad.

So I hacked up a quick monkeypatch (or whatever the duck punchers call it these days) to recursively freeze containers:

#
#  allow us to freeze deep data structures by recursively freezeing each nested object
#
class Hash
    def deep_freeze # har, har ,har
        each { |k,v| v.deep_freeze if v.respond_to? :deep_freeze }
        freeze
    end
end
class Array
    def deep_freeze
        each { |j| j.deep_freeze if j.respond_to? :deep_freeze }
        freeze
    end
end

After loading these patches, calling deep_freeze does what we want:

global_conf = YAML.load_file("...")
global_conf.deep_freeze
global_conf['firm_1']['departments'] = {'foo' => 'bar'}
=> TypeError: can't modify frozen hash

Nice!

3 comments:

Max Schubert said...

Hi, excellent code :), I have extended it to freezing child objects and to preventing simple access to hash and array single elements as well.

http://www.semintelligent.com/blog/articles/29/freezing-ruby-data-structures-recursively

I give you full credit for your code at the top of the article. Feel free to hack at my code, re-publish on your blog, extend etc as you wish :).

Thanks again!

Nick Urban said...

Thanks for the tip. I added this to Enumerable instead of to Hash and Array. Works like a charm.

Digwuren the Gray said...

Enumerable is not a good choice because on a Hash, you'd then only iterate over values. In a deep freeze, you'll want your keys deep-frozen, too. It matters when using complex structures such as arrays containing arrays as keys; the Hash itself only freezes the first level.