Difficult Inheritance Problem

Hi all,

I have a problem I can’t think of a solution to with regard to
inheritence.
I have an abstract class that others inherit from:

class DataObject
class << self
attr_accessor :difference_mapping

...

end

end

The descendents of this class each define their own difference
mapping:

class Page < DataObject
self.difference_mapping = {:blah => :blah}
end

I would now like to inherit from the Page class but use its
difference_mapping. Obviously this does not work because the
attr_accessor in the DataObject uses a class instance variable that is
unique to each class. I tried using a proper class variable
(@@difference_mapping) but that is the same for all descending
classes.

Is there a way to define a class level variable that descends down the
inheritance tree unless it is overridden in the same way as this is
possible for methods? I mean I could wrap the data structure in a
method but that does not seem like a very clean solution.

Thanks in advance,
Toby

Hi,

I am using something like below for my program. I trick is to use
methods
instead of class variables as they persist through inheritance.

Martin

class A
def self.define_attr_method(name, value=nil)
sing = class << self; self; end
sing.class_eval “def #{name}; #{value.inspect}; end”
end

Sets and/or returns current difference_mapping

def self.difference_mapping(value = nil)
self.define_attr_method(‘difmap’, value) unless value.nil?
self.difmap
end
end

class B < A
difference_mapping ‘class B’
end

class C < B
end

class D < B
difference_mapping ‘class D’
end

puts B.difference_mapping
puts C.difference_mapping
puts D.difference_mapping

This will print:
class B
class B
class D

Is there a way to define a class level variable that descends down the
inheritance tree unless it is overridden in the same way as this is
possible for methods? I mean I could wrap the data structure in a
method but that does not seem like a very clean solution.

Try this, perhaps:

class DataObject
class << self
attr_writer :mapping

def mapping
  @mapping || (self == DataObject ? nil : superclass.mapping)
end

end
end

class Page < DataObject; end

DataObject.mapping = ‘foo’

puts Page.mapping #=> ‘foo’

You could write a class helper to generate these methods. I think Rails
has
something called ‘write_inheritable_attribute’ somewhere that does the
same
thing.

On 27 Aug 2008, at 10:08, Toby C. wrote:

possible for methods? I mean I could wrap the data structure in a
method but that does not seem like a very clean solution.

two possible approaches are rails’ class_inheritable_accessor and
superclass_delegating_accessor.

Both lean on class instance variables. In the case of
class_inheritable_accessor the attributes are store in a hash and
self.inherited is hooked to copy that hash over when a subclass is
created

superclass_delegating_accessor looks for an appropriate instance
variable and if it does not exist calls the superclass (stopping when
it gets to the the class that created the
superclass_delegating_accessor). Writing however always writes to an
instance variable of the class being changed. I put some more detail
about them at

Fred

Hi –

On Wed, 27 Aug 2008, Toby C. wrote:

method but that does not seem like a very clean solution.
The easiest way I can think of is:

class Page
class << self
attr_accessor :difference_mapping
end
def self.inherited©
c.difference_mapping = difference_mapping
end
end

Page.difference_mapping = { :blah => :blah }

class Next < Page
end

p Next.difference_mapping # { :blah => :blah }

David

On Wed, Aug 27, 2008 at 11:08 AM, Toby C. [email protected]
wrote:
I am currently working on a new release of Labardor, but there still
is sooo much to do, however, you might be interested in the following
code of Labrador.
I am however aware that this implies a rethink of your strategy,
however one of my goals is to explore different behavior based OO
approaches than inheritance and mixins, in case you find that a useful
paradigm shift.
And I appologize for the long post but Labrador just is in no shape
for a new release to Rubyforge.

HTH
Robert
Skip the rest if you are not particularily interested in different OO
approaches.
----------------------------- 8< ------------------------
#–

vim: sts=2 sw=2 tw=0 expandtab nu:

#*
#* Labrador, The Lazy Programmer’s Best Friend.
#*
#* Distributed under the terms of the BSD License.
#* Copyright (c) 2007 Robert D.
#* All rights reserved.
#*
#* Redistribution and use in source and binary forms, with or without
#* modification, are permitted provided that the following conditions
are met:
#* * Redistributions of source code must retain the above copyright
#* notice, this list of conditions and the following disclaimer.
#* * Redistributions in binary form must reproduce the above
copyright
#* notice, this list of conditions and the following disclaimer in
the
#* documentation and/or other materials provided with the
distribution.
#* * Neither the name of the Labrador packahe nor the
#* names of its contributors may be used to endorse or promote
products
#* derived from this software without specific prior written
permission.
#*
#* THIS SOFTWARE IS PROVIDED BY Robert D. ``AS IS’’ AND ANY
#* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED
#* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE
#* DISCLAIMED. IN NO EVENT SHALL Robert D. BE LIABLE FOR ANY
#* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES
#* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES;
#* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND
#* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT
#* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS
#* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#++

class Array

def ivar_names_to_strings
map { |ele| ele.to_s.sub(“@”,“”) }
end

def ivar_names_to_symbols
map { |ele| ele.to_s.sub(“@”,“”).to_sym }
end
def names_to_ivar_strings
map { |ele| “@#{ele.to_s.sub(”@“,”“)}” }
end

def names_to_ivar_symbols
map { |ele| “@#{ele.to_s.sub(”@“,”“)}”.to_sym }
end

end # class Array

class Object
def get_ivars *ivars
ivars = instance_variables if ivars.empty?
ivars.ivar_names_to_symbols.
inject( {} ) { |h, ivar|
h.update ivar => instance_variable_get( “@#{ivar}” )
}
end

def pop_ivars
set_ivars! @ivar_stack.pop
end

def push_ivars hash
( @ivar_stack ||= [] ).
push(
get_ivars( *hash.keys )
)
set_ivars! hash
end

def set_ivars hash
hash.keys.each do
| ivar |
ivar_name = “@#{ivar}”.sub( /^@@/, “@” )
instance_variable_set ivar_name, hash[ ivar ] unless
instance_variables.include? ivar_name
end
end

def set_ivars! hash
hash.keys.each do
| ivar |
ivar_name = “@#{ivar}”.sub( /^@@/, “@” )
instance_variable_set ivar_name, hash[ ivar ]
end
end

def set_only_ivars hash, *keys
set_ivars hash.keys.inject( {} ){ |h, k|
keys.include?( k ) ? h.update( k => hash[k] ) : h
}
end # def set_only_ivars hash, *keys

def set_only_ivars! hash, *keys
set_ivars! hash.keys.inject( {} ){ |h, k|
keys.include?( k ) ? h.update( k => hash[k] ) : h
}
end # def set_only_ivars hash, *keys
end # class Object

class Behavior
attr_reader :block
def initialize &blk
@block = blk
end
end

module Kernel
def Behavior &blk
Behavior::new &blk
end
end

class Module
def empty?; instance_methods.empty? end
def empty!; instance_methods.each do |im| remove_method im end end
end

module Pushable
CannotPopException = Class::new RuntimeError
ArgumentError = Class::new ::ArgumentError

def pop_behavior
@bsp -= 1
raise CannotPopException, “empty entity #{self}” if @bsp < 0
@behavior_stack[@bsp].empty!
end

def push_behavior *behaviors, &blk
@behavior_stack ||= []
@bsp ||= 0
raise ArgumentError,
“push_behavior takes at least one behavior or block” if
behaviors.empty? and blk.nil?

behaviors.each do |behavior|
  _push_behavior  behavior
end

_push_behavior blk if blk
self

end

private

def _push_behavior behavior
m = @behavior_stack[@bsp]
@behavior_stack << ( m = Module::new ) unless m
include m rescue extend m # this is autoprotected against double
inclusion; so finally turns out it is a feature :wink:
m.empty!
m.module_eval &(behavior.block rescue behavior)
@bsp += 1
end
end # module Pushable

class << Pushable
def new *args, &blk
c = Class::new( *args, &blk )
c.extend self
c
end
end # class << Pushable

class << PushableModule = Module::new
def new *args, &blk
m = Module::new( *args, &blk )
m.extend Pushable
m
end
end
------------------------------------ 8<

end

Page.difference_mapping = { :blah => :blah }

class Next < Page
end

p Next.difference_mapping # { :blah => :blah }

Depends whether you only need the definition done once. This technique
will
not cause the subclass to reflect the parent class’ value if it is
changed,
e.g.:

Page.difference_mapping = {:foo => :something}
Next.difference_mapping #=> {:blah => :blah}

Hi –

On Wed, 27 Aug 2008, James C. wrote:

end
Depends whether you only need the definition done once. This technique will
not cause the subclass to reflect the parent class’ value if it is changed,
e.g.:

Page.difference_mapping = {:foo => :something}
Next.difference_mapping #=> {:blah => :blah}

True, but the OP described it as: “Is there a way to define a class
level variable that descends down the inheritance tree unless it is
overridden in the same way as this is possible for methods?” In the
method case, you’d have:

class A
def x; 1; end
end

class B < A
end

class C < A
def x; 2; end
end

and even if A changed x, C would still have its override in effect. So
the same deal with the attribute might be OK in this case.

David

On Wed, Aug 27, 2008 at 1:37 PM, David A. Black [email protected]
wrote:
Wow that is great, I would call them viral class variables!
R.

Hi –

On Wed, 27 Aug 2008, Robert D. wrote:

On Wed, Aug 27, 2008 at 1:37 PM, David A. Black [email protected] wrote:
Wow that is great, I would call them viral class variables!

I think class variables are already viral :slight_smile:

David

On Wed, Aug 27, 2008 at 2:55 PM, David A. Black [email protected]
wrote:

Hi –

On Wed, 27 Aug 2008, Robert D. wrote:

On Wed, Aug 27, 2008 at 1:37 PM, David A. Black [email protected]
wrote:
Wow that is great, I would call them viral class variables!

I think class variables are already viral :slight_smile:
Well they are unique, applying your pattern however the following holds

class X < Page
@difference_mapping = 42
end

Y = Class::new( X )

Y.difference_mapping => 42
while
Next.difference_mapping is still the hash.

I meant with viral that the new values are propagated only down
the inheritance tree that is what OP wanted and I have to admit I did
not see immediately that your solution was correct in that regard.
When I realized how it was I found it quite amazing.

I should have said that self.inherited was viral.

Cheers
Robert

Toby C. wrote:

Is there a way to define a class level variable that descends down the
inheritance tree unless it is overridden in the same way as this is
possible for methods?

Yes, using a hash that inherits values:

http://redshift.sourceforge.net/superhash/
http://redshift.sourceforge.net/superhash/lib/superhash.html

Sorry for the crappy docs. It’s an old project, but I use it frequently.

Here’s an example

require ‘superhash’

class A
class_superhash :options

options[:foo] = “A foo”
options[:bar] = “A bar”

def options; self.class.options; end
end

class B < A
options[:foo] = “B foo”
end

p A.options
p B.options.to_hash
p B.new.options.to_hash

END

output:

{:foo=>“A foo”, :bar=>“A bar”}
{:foo=>“B foo”, :bar=>“A bar”}
{:foo=>“B foo”, :bar=>“A bar”}

Thank you all for your responses. I ended up using James C.'s
solution as that is what I was trying to achieve even if I didn’t make
that clear in my first post. But even so I’ve learnt something from
each of your responses.
Cheers,
Toby

On Wednesday 27 August 2008 06:44:46 James C. wrote:

end
Depends whether you only need the definition done once. This technique will
not cause the subclass to reflect the parent class’ value if it is changed,

In that case, let me take a shot:

class Page

class << self
attr_accessor :difference_mapping
end

module DescendantClassMethods
def difference_mapping
Page.difference_mapping
end
def difference_mapping= value
Page.difference_mapping = value
end
end

def self.inherited klass
klass.extend DescendantClassMethods
end
end

Since I’m in a late-night hacking mood, here’s a way that’s even more
meta:

because << drives me crazy

require ‘metaid’

name it whatever you want…

module ClassVariables
module BaseClassMethods
def class_variable name
self.meta_eval do
attr_accessor name
end
superclass = self
(self::InheritableClassVariables ||= Module.new).module_eval do
define_method name do
superclass.send name
end
assignment = :"#{name}="
define_method assignment do |value|
superclass.send assignment, value
end
end
end

def inherited klass
  mod = self::InheritableClassVariables
  # I think this, instead of extend, will let subclasses override
  # these methods properly.
  klass.meta_eval do
    include mod
  end
end

end

def self.included mod
mod.extend BaseClassMethods
end
end

Hmm. That’s… mostly complete. Usage would be:

class Page
include ClassVariable
class_variable :difference_mapping
end

But I haven’t so much as executed it to check for syntax errors.

2008/9/2 [email protected] [email protected]:

Thank you all for your responses. I ended up using James C.'s
solution as that is what I was trying to achieve even if I didn’t make
that clear in my first post. But even so I’ve learnt something from
each of your responses.

I believe there is a better approach that solves the inheritance issue
generally. (Jame’s solution will only reuse mappings as long as no
mapping is defined in the current class.) Usually you want to deal
with all the mappings aggregated so far in the inheritance hierarchy.
Thus my solution would be

class DataObject
class<<self
attr_writer :difference_mapping

def difference_mapping
  ancestors.inject({}) do |m,cl|
    m.merge(cl.instance_variable_get("@difference_mapping")) rescue 

m
end
end
end
end

class Page < DataObject
self.difference_mapping = {:blah => :blah}
p [self, difference_mapping]
end

class YourClass < Page
p [self, difference_mapping]
end

Kind regards

robert