How to Implement an Abstract Class in Ruby

How to implement an abstract class in Ruby

I don't like using abstract classes in Ruby (there's almost always a better way). If you really think it's the best technique for the situation though, you can use the following snippet to be more declarative about which methods are abstract:

module Abstract
def abstract_methods(*args)
args.each do |name|
class_eval(<<-END, __FILE__, __LINE__)
def #{name}(*args)
raise NotImplementedError.new("You must implement #{name}.")
end
END
# important that this END is capitalized, since it marks the end of <<-END
end
end
end

require 'rubygems'
require 'rspec'

describe "abstract methods" do
before(:each) do
@klass = Class.new do
extend Abstract

abstract_methods :foo, :bar
end
end

it "raises NoMethodError" do
proc {
@klass.new.foo
}.should raise_error(NoMethodError)
end

it "can be overridden" do
subclass = Class.new(@klass) do
def foo
:overridden
end
end

subclass.new.foo.should == :overridden
end
end

Basically, you just call abstract_methods with the list of methods that are abstract, and when they get called by an instance of the abstract class, a NotImplementedError exception will be raised.

Integer and other (?) abstract classes in Ruby

You can undefine the new class method calling undef_method on the singleton class of your class, for example:

class Foo
class << self
undef_method(:new)
end
end

Foo.new
# NoMethodError: undefined method `new' for Foo:Class

Or alternatively:

class Foo; end
Foo.singleton_class.send(:undef_method, :new)

Update: for the sake of completeness here's a quick (and possibly dirty) solution to the problem mentioned by @tadman in the comments. It uses the inherited method to define a new singleton method for the subclasses of Foo that mimics the implementation of Class#new:

class Foo
class << self
undef_method(:new)

def inherited(subclass)
subclass.define_singleton_method(:new) do |*args|
obj = subclass.allocate
obj.send(:initialize, *args)
obj
end
end
end

end

class Bar < Foo
attr_reader :bar
def initialize(bar)
@bar = bar
end
end

Bar.new('foobar').bar # => "foobar"

Setting up for an Abstract Super Class in Ruby

In Ruby you don't have the keyword abstract to defined abstract classes. If you need to encapsulate functionality that is intended to be shared among different classes, you can extract that into a module, and mix it in with the classes.

For instance:

module Player
# shared functionality
end

class Human
include Player
end

class Computer
include Player
end

This works well since abstract, as in disassociated from any specific instance, is just construct that can be achieve in many different ways. Different language choose to be more explicit about it, in ruby is more implicit.

Alternatives to abstract classes in Ruby?

Ruby does not offer this functionality, no. You are responsible for making sure that your classes implement what they ought to implement.

Part of the reason that such functionality is impossible for Ruby is that Ruby classes can be reopened, and Ruby supports loading arbitrary code at runtime, so we can't know whether a class implements a certain interface until we try to call it.

Supposing an Animal must have an eat method, and I do the following:

class Cat < Animal
def talk
puts "meow"
end
end

class Cat
def eat
puts "om nom nom"
end
end

By the end of that file, the Cat will have its eat definition, because Ruby classes can reopened and modified multiple times. Should the code error out after the first definition because eat wasn't defined yet? That implementation would hurt more than it would help, since reopening classes is common, even if this example is contrived. Should it error out once the eat method is called and does not exist, so we can be certain that it's defined once we need it? Well, if the method were missing, that would happen, anyway. The interpreter can never know if another class definition is on the way, so it can never cut you off until the method is actually called.

In short, superclasses simply cannot possibly require a method to be defined in Ruby, because the dynamic nature of classes contradict such a goal.

Sorry! This is a place where unit testing might come in handy, though, to ensure that your subclasses do what they're supposed to be doing, anyway.

Simulating abstract classes in Ruby (Rails)

Why would you want to do this? The point of abstract/interfaced classes are to hack Strongly typed languages into a dynamic paradigm. If you need your class to fit in the signature, name your methods according to the original class or make a facade and plug it in, no need to trick a compiler into allowing it, it just works.

def my_printer obj
p obj.name
end

So I defined the interface as any object with a name property

class person
attr_accessor :name
def initialize
@name = "Person"
end
end

class Employee
attr_accessor :name
def initialize
@name = "Employee"
@wage = 23
end
end

so nothing stops us from calling our printer method with either of these

my_printer Person.new
my_printer Employee.new

both print there names without a hitch :D

Rails - Best practice for abstract class definition and file naming

First of all, I think you must already realize that ruby does not have true abstract classes. But we can approximate the behavior. And while doing so, it sounds like you have a preference toward organizational structure which I will attempt to address.

I must start by saying, however, that I'm surprised that you're coming at the problem so strongly from the organizational angle. First on my mind would be whether I really wanted to implement single table inheritance or not and then let that drive the organizational problem. Usually the answer here is that Single Table Inheritance is not what you actually want. But... let's dive in!

Using Single Table Inheritance

Here's the standard way to utilize and organize models using Single Table Inheritance:

# app/models/mother_class.rb
class MotherClass < ActiveRecord::Base
# An "abstract" method
def method1
raise NotImplementedError, "Subclasses must define `method1`."
end

def method2
puts method1 # raises NotImplementedError if `method1` is not redefined by a subclass
end
end

# app/models/sub_class_a.rb
class SubClassA < MotherClass
def method1
# do something
end
end

# app/models/sub_class_b.rb
class SubClassB < MotherClass
def method1
# do something
end
end

Given the above, we would get an exception when calling MotherClass.new.method2 but not when calling SubClassA.new.method2 or SubClassB.new.method2. So we've satisfied the "abstract" requirements. Organizationally, you called this a big mess in the models folder... which I can understand if you've got tons of these subclasses or something. But, remember that in single table inheritance even then parent class is a model and is / should be usable as such! So, that said, if you'd really like to organize your models file system better then you are free to do so. For example, you could do:

  • app/models/<some_organizational_name>/mother_class.rb
  • app/models/<some_organizational_name>/sub_class_a.rb
  • app/models/<some_organizational_name>/sub_class_b.rb

In this, we are keeping all other things (i.e. the Code for each of these models) the same. We're not namespacing these models in any way, we're just organizing them. To make this work it's just a matter of helping Rails to find the models now that we've placed them in a subfolder of the models folder without any other clues (i.e. without namespacing them). Please refer to this other Stack Overflow post for this. But, in short, you simply need to add the following to your config/application.rb file:

config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**/}')]

Using Mixins

If you decide that Single Table Inheritance is not what you want (and they often aren't, really) then mixins can give you the same quasi-abstract functionality. And you can, again, be flexible on file organization. The common, organizational pattern for mixins is this:

# app/models/concerns/mother_module.rb
module MotherModule
extend ActiveSupport::Concern

# An "abstract" method
def method1
raise NotImplementedError, "Subclasses must define `method1`."
end

def method2
puts method1 # raises NotImplementedError if `method1` is not redefined
end
end

# app/models/sub_class_a.rb
class SubClassA
include MotherModule

def method1
# do something
end
end

# app/models/sub_class_b.rb
class SubClassB
include MotherModule

def method1
# do something
end
end

With this approach, we continue to not get an exception when calling SubClassA.new.method2 or SubClassB.new.method2 because we've overridden these methods in the "subclasses". And since we can't really call MotherModule#method1 directly it is certainly an abstract method.

In the above organization, we've tucked MotherModule away into the models/concerns folder. This is the common location for mixins in Rails these days. You didn't mention what rails version you're on, so if you don't already have a models/concerns folder you'll want to make one and then make rails autoload models from there. This would, again, be done in config/application.rb with the following line:

config.autoload_paths += Dir[Rails.root.join('app', 'concerns', '{**/}')]

The organization with the mixins approach is, in my opinion, simple and clear in that SubclassA and SubClassB are (obviously) models and, since they include the MotherModule concern they get the behaviors of MotherModule. If you wanted to group the subclass models, organizationally, into a folder then you could still do this of course. Just use the same approach outlined at the end of the Single Table Inheritance section, above. But I'd probably keep MotherModule located in the models/concerns folder still.

Ruby base class call child class like in abstract classes

Welcome to dynamically typed languages! You were probably nervous about just defining some function that wasn't declared anywhere. Don't be worried. It is very easy:

class Base
def process
# ...
real_processing
end

def real_processing # This method is optional!
raise "real_processing not implemented in #{self.class.name}"
end
end

class Child < Base
def real_processing
# ...
end
end

b = Child.new
b.process

EDIT: Here's another option for you which avoids the need to have two different method names:

class Base
def process
# ...
end
end

class Child < Base
def process
# ...
super # calls the process method defined above in Base
# ...
end
end

Abstract Method in 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.



Related Topics



Leave a reply



Submit