Class hierarchy... for data

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
end

def
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, but I’d really like it not to use a hash, ideally
I’d like to access current_settings.foo, and not be allowed to lookup
something not found in the instance or any of its parents (very much
like a class hierarchy). I’d like current_settings.each to do the right
thing, 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.

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).

Anyone got anything similar? Or maybe some suggestions?

Tom.

.^. .-------------------------------------------------------.
/V\ | Tom Gilbert, London, England | http://linuxbrit.co.uk |
/( )\ | Open Source/UNIX consultant | 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
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
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 :wink:

Cheers,
Gavin

···

----- Original Message -----
From: “Tom Gilbert” tom@linuxbrit.co.uk

Heh. Just hoping I was missing a nicer way to do it - because once I’ve
implemented all the enumerable and hash methods I’ll want this thing to
have it’s going to be a fairly fat class :slight_smile:

Thanks for the feedback.

Tom.

···
  • Gavin Sinclair (gsinclair@soyabean.com.au) wrote:

Anyone got anything similar? Or maybe some suggestions?

Keep going; you’re nearly there.


.^. .-------------------------------------------------------.
/V\ | Tom Gilbert, London, England | http://linuxbrit.co.uk |
/( )\ | Open Source/UNIX consultant | tom@linuxbrit.co.uk |
^^-^^ `-------------------------------------------------------’

I have wanted to do what you talked about. Here is my solution. Please ruby
experts make it better if possible :->

class Object
def Object.parent_field(*names)
names.each do |name|
module_eval <<-“end;”
def #{name}
if type.instance_methods.include?(“#{name}“) then
send(”#{name}
”)
elsif defined?(@#{name}) then
@#{name}
else
@parent.#{name}
end
end

			def #{name}=(value)
				if type.instance_methods.include?("#{name}_=") then
					send("#{name}_=", value)
				else
					@#{name} = value
				end
			end
	 	end;
	 end
end

end

class A
parent_field :x, :y, :z

def initialize(parent)
	@parent = parent
end

def x_=(value)
	@x = value * 2
end

end

a = A.new(nil)
b = A.new(a)
c = A.new(b)

a.x = 1
a.y = 2
a.z = 3
b.y = 11
b.z = 12
c.z = 21

print “#{a.x}, #{a.y}, #{a.z}\n”
print “#{b.x}, #{b.y}, #{b.z}\n”
print “#{c.x}, #{c.y}, #{c.z}\n”

Steve Tuckner

···

-----Original Message-----
From: Tom Gilbert [mailto:gilbertt@fs.tomsflat]On Behalf Of Tom Gilbert
Sent: Wednesday, August 07, 2002 12:47 PM
To: ruby-talk ML
Subject: Re: Class hierarchy… for data

  • Gavin Sinclair (gsinclair@soyabean.com.au) wrote:

Anyone got anything similar? Or maybe some suggestions?

Keep going; you’re nearly there.

Heh. Just hoping I was missing a nicer way to do it - because once I’ve
implemented all the enumerable and hash methods I’ll want this thing to
have it’s going to be a fairly fat class :slight_smile:

Thanks for the feedback.

Tom.

.^. .-------------------------------------------------------.
/V\ | Tom Gilbert, London, England | http://linuxbrit.co.uk |
/( )\ | Open Source/UNIX consultant | tom@linuxbrit.co.uk |
^^-^^ `-------------------------------------------------------’