51

How can I force a subclass to implement a method in Ruby. There doesn't seem to be an abstract keyword in Ruby, which is the approach I would take in Java. Is there another more Ruby-like way to enforce abstract?

diedthreetimes
  • 4,086
  • 26
  • 38
Hunter McMillen
  • 59,865
  • 24
  • 119
  • 170
  • Additionally, Ruby uses something called Duck Typing: http://en.wikipedia.org/wiki/Duck_typing – mikeycgto Jul 22 '11 at 15:41
  • 8
    @delnan, there was no need to phrase your answer like that. If I was trying to stick to a Java mindset I wouldn't be asking for a "Ruby-like" soltution. Thank you, however ,for you suggestion about the runtime exception. – Hunter McMillen Jul 22 '11 at 15:45
  • 3
    I'm sorry if I came off rude. I've just seen *so many* people trying to program in language A as if it was language B. Your question seemed a bit like this too, as you asked how to do what abstract classes do in Java (instead of "an ruby equivalent to abstract classes" or something likr zhsz). Again, no offense meant, perhaps I got you wrong. –  Jul 22 '11 at 15:50

7 Answers7

50

Abstract methods are supposed to be less useful in Ruby because it's not strongly statically typed.

However, this is what I do:

class AbstractThing
  MESS = "SYSTEM ERROR: method missing"

  def method_one; raise MESS; end
  def method_two; raise MESS; end
end

class ConcreteThing < AbstractThing
  def method_one
     puts "hi"
  end
end

a = ConcreteThing.new
a.method_two # -> raises error.

It rarely seems to be necessary, however.

idmean
  • 14,540
  • 9
  • 54
  • 83
Andy
  • 1,480
  • 14
  • 17
  • 10
    Ruby is strongly typed. Just not statically typed. – Jonathan Sterling Jul 22 '11 at 15:54
  • 1
    +1, that's how it's done in Smalltalk with `subclassResponsibility` (`^ self subclassResponsibility`). – Michael Kohl Jul 22 '11 at 17:42
  • 10
    If you just delete the entire contents of `AbstractThing`, you get the exact same behavior: an exception when trying to call `method_two`. In fact, you get a slightly *better* behavior, since instead of a generic non-descript, non-semantic `RuntimeError`, you get a `NoMethodError`, which tells you exactly what is wrong with your code. – Jörg W Mittag Jul 22 '11 at 21:26
  • 6
    @Jorg. This is broadly true, except of course for two things -- first, I can raise whatever error I like; I just kept the example simple. The point is you get a more specific error. Second, defining the abstract class makes your intentions very plain to anyone reading the code (especially if you subclass it multiple times, which is usually the case). – Andy Jul 23 '11 at 20:41
  • 2
    This is not the same. If a method is abstract, you should get an error when class is loaded, not even needing to explicitly call the missing method. – Rok Kralj Jan 21 '13 at 16:30
  • You could also add self.abstract_class = true to first class To make it really really clear. – baash05 Jul 12 '13 at 05:22
  • @JonathanSterling, what do you call a weak typed then? I can't find any definition that would put ruby as a strongly typed language. What is see is that ruby cares about types as little as possible. – akostadinov Jul 25 '14 at 21:29
  • @Jörg I prefer Andy's solution because defining the method in the Abstract Class serves as documentation for implementation class author. – Wakeel Jul 01 '22 at 10:10
19

I like the answer by pvandenberk, but I would improve it as follows:

module Canine      # in Ruby, abstract classes are known as modules
  def bark
    fail NotImplementedError, "A canine class must be able to #bark!"
  end
end

Now if you make a class belonging to Canine "abstract class" (ie. a class that has Canine module in its ancestors), it will complain if it is found that #bark method is not implemented:

class Dog
  include Canine   # make dog belong to Canine "abstract class"
end

Dog.new.bark       # complains about #bark not being implemented

class Dog
  def bark; "Bow wow!" end
end

# Now it's OK:
Dog.new.bark #=> "Bow wow!"

Note that since Ruby classes are not static, but always open to changes, Dog class itself cannot enforce existence of #bark methods, since it doesn't know when is it supposed to be finished. If you as a programmer do, it is up to you to test it at such time.

Boris Stitnicky
  • 12,444
  • 5
  • 57
  • 74
  • 3
    I was searching for `NotImplementedError` and ran across [this](http://chrisstump.online/2016/03/23/stop-abusing-notimplementederror/). – Hakanai May 09 '18 at 04:15
  • 1
    `NotImplementedError`'s intent is to signal that a method is not available on the current platform (e.g. calling an API on windows that only nix based systems have). https://ruby-doc.org/core-2.5.0/NotImplementedError.html – Damon Aw Dec 23 '19 at 19:50
8

My preferred approach is similar but slightly different... I prefer it as follows, because it makes the code self-documenting, giving you something very similar to Smalltalk:

class AbstractThing
  def method_one; raise "SubclassResponsibility" ; end
  def method_two; raise "SubclassResponsibility" ; end
  def non_abstract_method; method_one || method_two ; end
end

Some people will complain that this is less DRY, and insist on creating an exception subclass and/or put the "SubclassResponsibility" string in a constant, but IMHO you can dry things up to the point of being chafed, and that is not usually a good thing. E.g. if you have multiple abstract classes across your code base, where would you define the MESS string constant?!?

Psylone
  • 2,788
  • 1
  • 18
  • 15
pvandenberk
  • 4,649
  • 2
  • 26
  • 14
  • How about beat em with a symbol :) then it's a constant that floats around, not a string in each case. Like your answer. – baash05 Jul 12 '13 at 05:16
  • only objection, is that the sub classes would have to implement no_abstract_method too, else when it was called (presumably to test), method one would be called, and method 2 might be called. – baash05 Jul 12 '13 at 05:18
4

I like the use of a gem like abstract_method which gives a dsl rails style syntax abstract methods:

class AbstractClass
  abstract_method :foo
end

class AbstractModule
  abstract_method :bar
end

class ConcreteClass < AbstractClass
  def foo
    42
  end
end
Rob Dawson
  • 1,349
  • 10
  • 21
1

This code will not let you load the class if the methods 'foo', 'bar' and 'mate' are not defined in the inherited class.

It does not account for classes being defined across many files, but lets get honest do many of us actually define class methods across many files? I mean if you don't count mix-ins. (which this does account for)

def self.abstract(*methods_array)
  @@must_abstract ||= []
  @@must_abstract = Array(methods_array)
end
def self.inherited(child)
   trace = TracePoint.new(:end) do |tp|
      if tp.self == child #modules also trace end we only care about the class end   
        trace.disable
        missing = ( Array(@@must_abstract) - child.instance_methods(false) )
        raise NotImplementedError, "#{child} must implement the following method(s) #{missing}" if missing.present?
      end
  end 
  trace.enable
end

abstract :foo
abstract :bar, :mate
baash05
  • 4,394
  • 11
  • 59
  • 97
0

If you want to have an error thrown when you create an instance of the class you could do the following

class AbstractClass
  def self.new(args)
    instance = allocate # make memory space for a new object
    instance.send(:default_initialize, args)
    instance.send(:initialize, args)
    instance
  end

  #This is called whenever object created, regardless of whether 'initialize' is overridden
  def default_initialize(args)
    self.abstract_method #This will raise error upon object creation
  end
  private :default_initialize

  def initialize(args)
   # This can be overridden by new class
  end
end


class NewClass < AbstractClass
end

NewClass.new #Throw error
GreyCat
  • 16,622
  • 18
  • 74
  • 112
Samuel Garratt
  • 301
  • 3
  • 5
0

Because the question is (focus on) "How can I force a subclass to implement a method in Ruby", so i think we can use TDD :D, for example: rspec shared example

shared_examples "MUST implement abstract method" do |method_sym|
  it { is_expected.to respond_to(method_sym) }
end

describe Stack do
  it_behaves_like "MUST implement abstract method", :push
  it_behaves_like "MUST implement abstract method", :pop
end

Maybe Tests are better than Abstract :D , reference: http://morningcoffee.io/interfaces-in-ruby.html

Lam Phan
  • 3,405
  • 2
  • 9
  • 20