----- Original Message -----
From: "Tom Gilbert" <tom / linuxbrit.co.uk>


> Hey,
>
> I'm currently trying to create some kind of hierarchial data class. Kind
> of like "sparse" data layers that sit on top of each other and let
> lookups "fall through" them.
>
> An example of this would be for multiple configuration layers
> (application defaults, user defaults, current settings) - the idea being
> that any change in the app defaults (including live changes) propagates
> through to any derived settings where that value has not been overridden
> from the default.
>
> I have hacked something out which comes maybe 20% of the way, and was
> hoping for suggestions to make this nicer. Here's what I have so far:
>
> ===
>
> class DataDelegator
>   def initialize(parent=nil, hash=nil)
>     @parent = parent
>     @hash = hash
>     @hash = Hash.new if @hash == nil

Style only: @hash = hash || {}

>   end
>
>   def [](key)
>     if @hash.has_key?(key)
>       return @hash[key]
>     elsif @parent != nil
>       return @parent[key]
>     else
>       # raise "no such member"
>       return nil
>     end
>   end
>
>   def []=(key,value)
>     @hash[key] = value
>   end
> end
>
> app_defaults = DataDelegator.new
> app_defaults["foo"] = "default_foo"
> app_defaults["bar"] = "default_bar"
> app_defaults["baz"] = "default_baz"
>
> user_defaults = DataDelegator.new(app_defaults)
> user_defaults["foo"] = "user_foo"
>
> current_settings = DataDelegator.new(user_defaults)
> current_settings["baz"] = "my_baz"
>
> puts current_settings["foo"]
> puts current_settings["bar"]
> puts current_settings["baz"]
>
> ===
>
> This outputs:
> $ ./test.rb
> user_foo
> default_bar
> my_baz
>
> So it kind of works,

Yep, seems pretty good to me.

> but I'd really like it not to use a hash

I don't see why not.  You want "some kind of hierarchial data class"; a hash
is a data class, you're providing the hierarchy.  It sounds perfect.

> ideally I'd like to access current_settings.foo

I don't like that idea.  current_settings["foo"] is fine.

> and not be allowed to lookup
> something not found in the instance or any of its parents (very much
> like a class hierarchy).

That's the case at present, isn't it?  Maybe I don't understand this point.

> I'd like current_settings.each to do the right
> thing,

So implement it!  What is "the right thing"?  Getting all elements,
including those defined in a parent, but not overwritten in the child?
Following code is not tested.

class DataDelegator
  def keys
    if parent == nil
      @parent.keys | @hash.keys
    else
      @hash.keys
    end
  end

  def each
    for key in keys
      value = self[key]
      yield key, value
    end
  end
end


> and even nicer would be the ability to do stuff
> like:
> current_settings["list of stuff"] << "whoopie"
> and have that .dup the parent's list before appending to it, and have
> current_settings["some array"].clear not clear the array in some parent.

Nice, but I've no idea, I'm sorry.  Hopefully someone else will know.
Otherwise, I'd be providing methods to perform these operations.  If you
really wanted to do what you say, you'd probably have to implement an
Association class for your use.  Something like:

class Association
  attr_accessor :key, :value

  def initialize(key, value)
    @key, @value = key, value
  end

  def <<(element)
    if @value.responds_to? <<
      value << element
    else
      raise ArgumentError
    end
  end

  def clear
    value.clear if value.responds_to? clear
    raise ArgumentError otherwise
    # !
  end
end

Then you'd need to redefine a bit, as the @hash now stores Associations.
This means it can intercept methods like << and clear, and cause them to
operate on the value.

class DataDelegator
  def [](key)
    if @hash.has_key?(key)
      return @hash[key].value     # this line changed
    elsif @parent != nil
      return @parent[key]
    else
      # raise "no such member"
      return nil
    end
  end

  def []=(key,value)
    if (key = @hash[key])
      key.value = value
    else
      @hash[key] = Association.new(key, value)
    end
  end
end


Ahem.  I've just realised that this is garbage.  Since it doesn't change the
interface (current_settings["foo"] still returns what they want), the
behaviour is not going to change when you call current_settings["foo"] <<
"whoopie".

Still, I'll leave the code there in case you can do something with it.

One possibility is to do the following:
  current_settings.send("foo", <<, whoopie)

It may not be pretty, but you may like it and improve it.  It wouldn't need
the Association class.


> This is all so like a class hierarchy I wonder if I'm missing something
> obvious that would stop this becoming rather hacky (I had considered
> using method_missing to catch "foo=" and "foo" etc and do the lookup
> from there).

Eurgghhh... I don't like method_missing.  (I know cleverer people than me
will disagree.)  What if you have a key named "hash", so you call
current_settings.hash.  What do you get?  Either:
 (a) the hash code of the object, which is not what you want; or
 (b) the setting, which screws you if you want to put the object in a Hash.

> Anyone got anything similar? Or maybe some suggestions?

Keep going; you're nearly there.

> Tom.
> --

Sorry for all the mess.  It's last; I'll clean up in the morning ;)

Cheers,
Gavin