I have a typical OO pattern: one base abstract class (that defines abstract methods) and several classes that implement these abstract methods in class-specific way.
I'm used to write documentation only once in abstract methods and then it automatically propagates to several concrete classes (at least it works the following way in Javadoc, in Scaladoc, in Doxygen), i.e. I don't need to repeat the same description in all concrete classes.
However, I couldn't find how to do such propagation in YARD. I've tried, for example:
# Some description of abstract class.
# @abstract
class AbstractClass
# Some method description.
# @return [Symbol] some return description
# @abstract
def do_something
raise AbstractMethodException.new
end
end
class ConcreteClass < AbstractClass
def do_something
puts "Real implementation here"
return :foo
end
end
What I get:
AbstractMethodException
is called in abstract class, does the job in concrete classAbstractClass
is clearly defined as abstract, ConcreteClass
is normalAbstractClass
AbstractMethodException
in AbstractClass
Object
return type in ConcreteClass
, there's not a single notice of that an abstract method exists in base class.What I expect to get:
ConcreteClass
from info at AbstractClass
ConcreteClass
description, with some reference link from ConcreteClass#do_something
to AbstractMethod#do_something
.Is it possible to do so?
I think the issue boils down to what you're trying to do. It looks like you're trying to implement an Interface in Ruby, which makes sense if you're coming from Java or .NET, but isn't really how Ruby developers tend to work.
Here is some info about how the typical thought on Interfaces in Ruby: What is java interface equivalent in Ruby?
That said, I understand what you're trying to do. If you don't want your AbstractClass to be implemented directly, but you want to define methods that can be used in a class that behaves like the AbstractClass stipulates (as in Design by Contract), then you probably want to use a Module. Modules work very well for keeping your code DRY, but they don't quite solve your problem related to documenting overridden methods. So, at this point I think you can reconsider how you approach documentation, or at least approach it in a more Ruby-ish way.
Inheritance in Ruby is really (generally speaking from my own experience) only used for a few reasons:
There are obviously other edge cases, but honestly this is what inheritance tends to be used for in Ruby. That doesn't mean what you're doing won't work or violates some rule, it just isn't typical in Ruby (or most dynamically typed languages). This atypical behavior is probably why YARD (and other Ruby doc generators) doesn't do what you expect. That said, creating an abstract class that only defines the methods that must exist in a subclass really gains you very little from a code perspective. Methods not defined will result in a NoMethodError exception being thrown anyway, and you could programmatically check if an object will respond to a method call (or any message for that matter) from whatever calls the method, using #respond_to?(:some_method)
(or other reflective tools for getting meta stuff). It all comes back Ruby's use of Duck Typing.
For pure documentation, why document a method that you don't actually use? You shouldn't really care about the class of the object being sent or received from calling a method, just what those objects respond to. So don't bother creating your AbstractClass in the first place if it adds no real value here. If it contains methods you actually will call directly without overriding, then create a Module, document them there, and run $ yardoc --embed-mixins
to include methods (and their descriptions) defined in mixed-in Modules. Otherwise, document methods where you actually implement them, as each implementation should be different (otherwise why re-implement it).
Here is how I would something similar to what you're doing:
# An awesome Module chock-full of reusable code
module Stuff
# A powerful method for doing things with stuff, mostly turning stuff into a Symbol
def do_stuff(thing)
if thing.kind_of?(String)
return thing.to_sym
else
return thing.to_s.to_sym
end
end
end
# Some description of the class
class ConcreteClass
include Stuff
# real (and only implementation)
def do_something
puts "Real implementation here"
return :foo
end
end
an_instance = ConcreteClass.new
an_instance.do_somthing # => :foo
# > Real implementation here
an_instance.do_stuff("bar") # => :bar
Running YARD (with --embed-mixins) will include the methods mixed-in from the Stuff module (along with their descriptions) and you now know that any object including the Stuff module will have the method you expect.
You may also want to look at Ruby Contracts, as it may be closer to what you're looking for to absolutely force methods to accept and return only the types of objects you want, but I'm not sure how that will play with YARD.
Not ideal, but you can still use the (see ParentClass#method)
construct (documented here). Not ideal because you have to type this manually for every overriding method.
That being said, I'm no Yard specialist but given its especially customizable architecture, I'd be surprised that there would be no easy way to implement what you need just by extending Yard, somewhere in the Templates department I guess.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With