Difficult Inheritance Problem

T

Toby Clemson

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
 
M

Martin Boese

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
 
J

James Coglan

[Note: parts of this message were removed to make it a legal post.]
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.
 
F

Frederick Cheung

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.

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 http://www.spacevatican.org/2008/8/19/fun-with-class-variables

Fred
 
R

Robert Dober

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 Dober
#* 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 Dober ``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 Dober 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 ;)
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<
-----------------------------------------
 
D

David A. Black

Hi --

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.

The easiest way I can think of is:

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

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

class Next < Page
end

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


David
 
J

James Coglan

[Note: parts of this message were removed to make it a legal post.]
The easiest way I can think of is:

class Page
class << self
attr_accessor :difference_mapping
end
def self.inherited(c)
c.difference_mapping = difference_mapping
end
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}
 
D

David A. Black

Hi --

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
 
R

Robert Dober

Hi --



I think class variables are already viral :)
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
 
J

Joel VanderWerf

Toby said:
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 :eek:ptions

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"}
 
D

David Masover

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

tobyclemson

Thank you all for your responses. I ended up using James Coglan'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
 
R

Robert Klemme

2008/9/2 [email protected] said:
Thank you all for your responses. I ended up using James Coglan'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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top