Abstract Method in Ruby

RubyAbstract

Ruby Problem Overview


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?

Ruby Solutions


Solution 1 - Ruby

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.

Solution 2 - Ruby

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.

Solution 3 - Ruby

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?!?

Solution 4 - Ruby

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

Solution 5 - Ruby

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

Solution 6 - Ruby

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

Solution 7 - Ruby

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionHunter McMillenView Question on Stackoverflow
Solution 1 - RubyAndyView Answer on Stackoverflow
Solution 2 - RubyBoris StitnickyView Answer on Stackoverflow
Solution 3 - RubypvandenberkView Answer on Stackoverflow
Solution 4 - RubyRob DawsonView Answer on Stackoverflow
Solution 5 - Rubybaash05View Answer on Stackoverflow
Solution 6 - RubySamuel GarrattView Answer on Stackoverflow
Solution 7 - RubyLam PhanView Answer on Stackoverflow