0

I'm writing a base class for an interface.. I want all the inherited classes to implement a few methods, is there a way to make them?

I have a payment_method_base class that I will be inheriting from.

I want all my payment_method classes to implement the method kind() that will return a string say 'credit_card' or 'braintree' or 'paypal' or 'amazon_pay'...

Is there a way to make sure that classes that inherit from payment_method_base are forced to implement the method kind()

Keeping in mind that the creator of a new payment_method class may not know about these requirements before they create the class.

In java these are called abstract methods. I'm wondering if ruby has something like that? https://github.com/shuber/defined/blob/master/lib/defined.rb

---- Evolution
I am wondering if there have been fixes to the language that allow for abstract methods.

---- partial answer This answer might be the clue to adding the hooks I need. Is there a hook similar to Class#inherited that's triggered only after a Ruby class definition?

This doesn't quite work the way I expected it would. The call doesn't seem to be done at the right time. Even so it is how many gems handle it. https://github.com/shuber/defined/blob/master/lib/defined.rb


SIMPLES ANSWER THAT I CAN'T SUBMIT BECAUSE THIS IS A REPEAT OF A QUESTION THAT HAD NO REAL ANSWER.
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
Community
  • 1
  • 1
baash05
  • 4,394
  • 11
  • 59
  • 97
  • I realize this is a duplicate of a question posed almost 4 years ago. I was wondering if the language had changed or been improved to account for making contracts with inheritance. – baash05 Feb 26 '15 at 22:42
  • Then you should specify that in your question that you've seen it doesn't have this as a language feature, and you were wondering if it was added. – b4hand Feb 26 '15 at 22:46
  • corrected question to include the version of ruby.. – baash05 Feb 26 '15 at 22:47
  • When was the question asked about version 2.2.0 or greater? Just wondering. Seems when the question was asked it was not about 2.2.0 – baash05 Feb 26 '15 at 23:15

2 Answers2

1

Ruby doesn't really implement anything like interfaces as it's against it's philosophy but if you really want to simulate interfaces you can use some gem, for example https://github.com/djberg96/interface.

Rene
  • 450
  • 1
  • 7
  • 10
  • 1
    You can also have the base class raise an exception as a default behaviour. That usually highlights potential problems. – tadman Feb 26 '15 at 22:49
  • as @rochkind said there's no real point to that except for documenting.. because the child class would raise when the missing method was called anyway.. – baash05 Feb 27 '15 at 03:48
1

There are no abstract methods in ruby.

I suppose you could implement a method like this, and I have seen it from time to time:

def some_method
  raise "This method should be over-ridden by a sub-class"
end

If a sub-class neglects to implement that method -- it won't be caught at 'compile time', because that doesn't happen in ruby. But if someone tries to call that method, which hasn't been implemented, you'll get an exception raised.

Of course, if you hadn't put that method (that does nothign more than raise) in the "abstract" superclass, and it was called on a sub-class without the sub-class implementing it... you'd still get a NoMethodException raised anyway. So the "abstract" method implementation that does nothing more than raise doesn't add much. Maybe a documentation of intent.

I think the most ruby-like way to handle this would probably be to provide a test suite of some kind, that another developer implementing a sub-class can easily run against their sub-class. The test suite will complain if the sub-class hasn't implemented things it's supposed to -- and it can even go beyond method signatures, and make sure the semantics of the sub-class are as expected too (you can't do that with just method signatures in Java!). I haven't actually seen anyone do that though, but have often thought it would make sense.

jrochkind
  • 22,799
  • 12
  • 59
  • 74
  • The test suite is a great suggestion.. Only how would the creator of the new class know to run it? It was a fair point that the call to the method if not in the superclass would result in an error. I wanted the error to show up the first time the class was loaded. So any tests on the class would raise it. – baash05 Feb 26 '15 at 23:01
  • Any thoughts on how to run code after a class is interpreted? So by just being a sub class it ran a bit of code after first load – baash05 Feb 26 '15 at 23:06
  • i think they'd only know through docs. But yes, theoretically you CAN actually run code every time a sub-class is created. See http://ruby-doc.org//core-2.2.0/Class.html#method-i-inherited I haven't done anything with this; it's possible it doesn't work properly in jruby (i might have imagined that though), and in general I think you're going to wind up running into gotchas with it. (Don't forget that in ruby, classes can be created in "parts", not all at once). My intuition is it won't work out well, but not sure why I think that, you could try. – jrochkind Feb 27 '15 at 02:19
  • I think I'm going to end up overriding the new( * ) method then it will check before the object is created.. it is a bad kludge though. – baash05 Feb 27 '15 at 03:46
  • Why `new` instead of the usual `initialize`? I'd stick to `initialize`. But I guess then sub-classes implementing it would need to call super, and nobody ever implements `new`. I dunno. Good luck. Running checks like this every time an object is created is probably not going to be good for performance. I think if you want the kind of checking you want working in the way you want, you might want a language other than ruby! If you want ruby, maybe give it a try without the kind of strict type checking you're trying to make happen. – jrochkind Feb 27 '15 at 14:58