19

I have the following classes:

CachedObject
CachedObjectSource
CachedObjectDbSource < CachedObjectSource
CachedObjectDalliSource < CachedObjectSource

CachedObject is a non-database object that is getting pulled from a third-party API and stored locally. CachedObject will be stored in both the database and Dalli (memcache), the real-time code will ping the Dalli source for a copy of the object, and the Dalli source will search the database source and update its cache if the object does not exist. So it's a nested call that requires each child class of CachedObjectSource to implement the same set of methods. IE, an interface.

Is there a way to write the CachedObjectSource class so that its child classes must implement the interface? Am I going about this the wrong way?

4
  • 1
    I think the Ruby philosophy is against static checks for interfaces in a class definition. If you want to be sure that children of CachedObjectSource implement the same methods, maybe the Ruby way is to write a unit test that contains a list of expected methods, and verifies that an instance of each type of source responds_to? each method in the list. Commented Sep 11, 2013 at 15:53
  • 1
    @RoryO'Kane Of course, the unit test approach fails if "each type of source" isn't deterministic. (Like if it can be loaded from plugins.) It'd be more appropriate for the implementations themselves to be tested anyway. Commented Sep 11, 2013 at 16:21
  • 1
    You could add a testing helper which can easily be included to test the validity of child classes, similar to what is e.g. available for ActiveModel Commented Sep 11, 2013 at 16:38
  • Here's a nice blog post about how to handle the concept of interfaces through unit tests. morningcoffee.io/interfaces-in-ruby.html Commented Mar 21, 2023 at 18:56

3 Answers 3

26

Ruby doesn't know interfaces similar to e.g. Java. Instead, Ruby programs typically use an approach called Duck Typing which basically means that you can send any message to any object which then can decide if it will respond to that, i.e. each object decides on its own which methods it has.

The closest thing you can get to an "interface" is a class (or module) that implements a method but only raises a NotImplementedError similar to this:

class CachedObjectSource
  def my_method
    raise NotImplementedError, "Implement this method in a child class"
  end
end

That way, the method will be present and return a sensible error when called without being overwritten in a child class. Then, you should write some documentation making it clear what child classes have to implement to be compliant.

Sign up to request clarification or add additional context in comments.

3 Comments

You can also not implement the method by not implementing it, which produces the same sort of error. Ruby tends to lean much more heavily on unit testing to expose problems like this than other languages because the language itself doesn't have any strict checks.
Sure, in the end, it's just an additional hint that there should be a method implemented, a NoMethodError has by default the same meaning as NotImplementedError. However, users of the class can see it in the code what they need to implement and get a clearer error message. In the end, it's more or less the question of explicit vs implicit (while I lean more to the explicit side of things).
if you like to give some more information about the method that has to be implemented, I like to use the following line: fail NotImplementedError, "#{self.class}##{__method__} method must be implemented"
3

You can use the gem interface.
This gem emulates interfaces similar to Java interfaces.
For example:

require 'interface'

MyInterface = interface{
  required_methods :foo, :bar, :baz
}

# Raises an error until 'baz' is defined
class MyClass
  def foo
    puts "foo"
  end

  def bar
    puts "bar"
  end

  implements MyInterface
end

This gem which is useful together with abstract gem.
Abstract gem provides abstract methods.

Comments

2

You could also use rint

gem 'rint'

that comes with a CLI:

$ rint c CachedObjectSourceInterface my_method

will create an interface definition, but you can also add it manually to any class. Then you can do something like:

class Instrument 
  implements CachedObjectSourceInterface
end

and it will throw a meaningful Exception if the method my_method is not implemented.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.