Abstract -- what's the Ruby way?

Here is one way. Its not very satisfying, as you have no way to access
the initialize method on any parent classes of the Animal class (such
as ActiveRecord)...

class Animal

  def initialize
    raise 'Invalid instantiation of class'
  end

  def talk
    puts 'hi'
  end

end

class Dog < Animal

  def initialize
      # this can be empty
      # don't use super here as will cause exception from Animal
  end

end

# this will fail (as it should) with an exception
# a = Animal.new

# this succeeds
d = Dog.new
d.talk

···

On Dec 12, 5:10 pm, "Tim Rowe" <n...@digitig.co.uk> wrote:

What's the Ruby way to define a class that shouldn't be instantiated, but
with descendants that can be? Other than a comment in the documentation, of
course.

Thanks!

Here is one way. Its not very satisfying, as you have no way to access
the initialize method on any parent classes of the Animal class (such
as ActiveRecord)...

Is it possible to pull of maybe using some clever passing back and
forth up through the super chain?

raise 'Invalid instantiation of class' unless
child_class_name.constantize <= Animal

or maybe detecting caller and raising unless it's a child class?

I missed the beginning, so hope this response is to the point. See the code below. (What I do is name the class AbstractSomething and trust that's warning enough.)

···

On Dec 12, 5:10 pm, "Tim Rowe" <n...@digitig.co.uk> wrote:

What's the Ruby way to define a class that shouldn't be instantiated, but
with descendants that can be? Other than a comment in the documentation, of
course.

=================================

class Abstract

   attr_reader :abstract_class_initialize_args

   def initialize(*args)
     raise "Initialized abstract class #{self.class}" if self.class == Abstract
     @abstract_class_initialize_args = args
   end
end

class SubclassWithInitializer < Abstract

   attr_reader :subclass_initialize_arg

   def initialize(arg)
     super
     @subclass_initialize_arg = arg
   end

end

class SubclassWithoutInitializer < Abstract
end

require 'test/unit'

class TestCase < Test::Unit::TestCase

   def test_abstract_class_raises_exception
     assert_raises(RuntimeError) { Abstract.new }
   end

   def test_a_subclass_can_call_super
     sub = SubclassWithInitializer.new('arg')
     assert_equal(['arg'], sub.abstract_class_initialize_args)
     assert_equal('arg', sub.subclass_initialize_arg)
   end

   def test_a_subclass_need_not_define_its_own_initialize
     sub = SubclassWithoutInitializer.new('arg')
     assert_equal(['arg'], sub.abstract_class_initialize_args)
   end
end

-----
Brian Marick, independent consultant
Mostly on agile methods with a testing slant
www.exampler.com, Exploration Through Example, www.twitter.com/marick

rbaldwin wrote:

What's the Ruby way to define a class that shouldn't be instantiated, but
with descendants that can be? Other than a comment in the documentation, of
course.

Thanks!

Here is one way. Its not very satisfying, as you have no way to access
the initialize method on any parent classes of the Animal class (such
as ActiveRecord)...

You could privatize new and define a new new for inheriting classes that calls the old new:

class Animal
   class << self
     private :new
     def inherited(cls)
       cls.instance_eval "def new(*args); super; end"
     end
   end
end

class Dog < Animal

puts Dog.new # => #<Dog:0x2506c>
pust Animal.new => NoMethodError: private method 'new' called for Animal...

I use instance_eval here because I don't like the semantics of define_method (or I dislike them more than eval magic); but it would probably work about the same.

The downside of this approach is subclasses that want to also define their own 'new' would have to know to call super. I'd expect that's pretty rare.

- Charlie

···

On Dec 12, 5:10 pm, "Tim Rowe" <n...@digitig.co.uk> wrote:

rbaldwin wrote:

What's the Ruby way to define a class that shouldn't be instantiated, but
with descendants that can be? Other than a comment in the documentation, of
course.

Thanks!

Here is one way. Its not very satisfying, as you have no way to access
the initialize method on any parent classes of the Animal class (such
as ActiveRecord)...

You could privatize new and define a new new for inheriting classes that calls the old new:

class Animal
   class << self
     private :new
     def inherited(cls)
       cls.instance_eval "def new(*args); super; end"
     end
   end
end

class Dog < Animal

puts Dog.new # => #<Dog:0x2506c>
pust Animal.new => NoMethodError: private method 'new' called for Animal...

I use instance_eval here because I don't like the semantics of define_method (or I dislike them more than eval magic); but it would probably work about the same.

The downside of this approach is subclasses that want to also define their own 'new' would have to know to call super. I'd expect that's pretty rare.

This downside can be easily avoided: simply change #inherited to make new public again instead of redefining it.

irb(main):001:0> class Animal
irb(main):002:1> class <<self
irb(main):003:2> private :new
irb(main):004:2> def inherited(cl)
irb(main):005:3> class <<cl
irb(main):006:4> public :new
irb(main):007:4> end
irb(main):008:3> end
irb(main):009:2> end
irb(main):010:1> end
=> nil
irb(main):011:0> class Dog < Animal
irb(main):012:1> end
=> nil
irb(main):013:0> Animal.new
NoMethodError: private method `new' called for Animal:Class
         from (irb):13
irb(main):014:0> Dog.new
=> #<Dog:0x7ff71960>
irb(main):015:0>

Kind regards

  robert

···

On 13.12.2007 07:58, Charles Oliver Nutter wrote:

On Dec 12, 5:10 pm, "Tim Rowe" <n...@digitig.co.uk> wrote:

         from :0