Ruby yard documenting DSL method definition AND its uses> - ruby

Ruby with Yard 0.9.8.
I'm defining custom attribute accessors in a ClassMethods module that will be extended into classed and other modules.
What I want to do is document what the DSL accessor methods do and attach that to their definitions, but have invocations of the method inherit generic docstrings about the parameters, return value, and such for each use.
However, defining the DSL docstring with #!macro [attach] on the definition of the accessor method isn't workable, because:
The macro parameters ($1, &c.) don't match correctly between the accessor definitions and the accessor invocation, and
I don't want the "How this attribute declarator works and is invoked" docstring to show up for both the definition and the uses.
So far the best I've been able to do to get the desired result is to define two macros:
#!macro [new] doc.procject.classmethod.foo.def, which describes the declaration in the normal way you would for any method, and
#!macro [new] doc.project.classmethods.foo.use, which includes the generic DSL documentation such as you'd see for attr_accessor :foo with no user-supplied docstring at all.
And then putting #!macro doc.project.classmethod.foo in the method's definition docstring, and for each actual invocation have some attribute-specific text followed by #!macro doc.project.classmethod.foo. Or possibly #!macro [attach] ...
All this seems suboptimal and it feels like I'm missing something crucial. The tag documentation has a few examples, but they do not show how the result appears.
For example, here's some simple code that shows how I'd like to see the output look:
module Outer
module ClassMethods
# Describing the **class method** -- the declaration.
# #!method dsli(*args)
# This class method declares one or more DSL interface variables.
# #param [Symbol] args
# #return [void]
def dsli(*args) ; end
end
extend(ClassMethods)
# Describing the **invocation** of the class *method* to create
# an *attribute*.
>># #!attribute [rw] hoipolloi
# This variable controls the frobbing of the potrzbi.
>># Only instances of class DSL::Thingie can be stored in this
>># attribute; any other class will result in a TypeError exception
>># being raised.
>># #overload hoipolloi
>># #return [DSL::Thingie,nil] the current value of the attribute.
>># #overload hoipolloi=(newval)
>># Sets a new value for the attribute.
>># #param [DSL::Thingie] newval
>># #raise [TypeError] if `newval` is of any class other than
>># DSL::Thingie.
>># #return [DSL::Thingie] the new value.
dsli(:frotrzbi)
end
This sort of describes what I'd like to have happen. All the lines prefixed with >> should be inherited (somehow) from the dsli method declaration. I want to embed some of the invocation information in the class method's docco. The method's documentation should talk about how, but the docco for any attributes it declares should talk about what. The class method's documentation applies only to its declaration, but the inherited documentation applies to all the attributes defined with it.
I have not been able to get this to work with YARD macros (a requirement in order to insert the proper names of the attributes, &c.)
Is there a better way to define distinct docstrings for DSL methods, one for each method's definition and another for invocations of the method à la [attach]?
Thanks!

Related

Can YARD be used to declare #return type as type of one of the arguments?

What I want to do is basically:
# #param [class] cls
# #return [instanceof(cls)]
def get(cls)
# Imagine this is method and object has property #instances = {}
if !#instances.keys.include?(cls)
#instances[cls] = cls.new()
end
#instances[cls]
end
It can be very handy for the service container pattern, when using it like
service = container.get(MyClass) # language server (e.g. solargraph) will see service variable as of type MyClass
Is it possible with YARD?
UPDATE:
fixed example of function, it did return simply cls.new() in the past
Just Declare a Type or Duck-Type in Your Return Tag
YARD already does this. What you're likely misunderstanding is how YARD uses brackets to document the type of the return value. You're also welcome to add free-form descriptions if you really feel it's necessary. However, pragmatically speaking, you're really just expecting your cls argument to respond to #new, so I'd rewrite it (and make it slightly more idiomatic) like so:
# #param klass [#new] a Class or other object that
# can +#respond_to? :new+
# #return [Class] an instantiated instance of +klass+
def get klass
klass.new
end
If you want to be more explicit, you could change the type in the #param tag to:
# #param klass [Class, #new]
so that it's clear that you're expecting a Class, but will also accept a duck-typed object that responds to #new. Since the method is exactly one line long, I think it's pretty unambiguous either way, especially with the description. Your taste and code style may vary, though.
In either case, the square brackets should declare one or more types, even if they're duck-types. So, yes, you can do what you want.
Ruby 3.2 Note
Unless something changes in the next month, in Ruby 3.2.0 you can make this even shorter with an "endless" method definition. For example, using ruby-3.2.0-preview2:
# #param klass [Class, #new]
# #return [Class] instance of +klass+
def get(klass) = klass.new
Whether or not you like the new syntax, it certainly works. For example:
obj = get Object
obj.instance_of? Object
#=> true
This isn't better or worse than the old syntax, but I do think it lets you tighten up the code and the documentation while increasing clarity. Again, your mileage may vary.

Documenting methods created with meta-programming using YARD

I'm currently working on a gem and writing documentation for it. I currently have a class that has several method defined using defined_method as follows:
class Client
['one', 'two'].each do |method_name|
# Sets the value
# #param argument the argument to set.
define_method("set_#{method_name}") do |argument|
# Method content
end
end
end
I'm trying to document these methods using YARD, but when generating the documentation of the project, theses methods do not appear in the class documentation.
Does anyone know how I could document these? Am I missing something?
Instead of iterating an arbitrary list, you would generally use macros to define the methods by wrapping the dynamic behaviour into a class method that can be documented as DSL-style calls in your class:
class << self
private
# #macro [attach] container.increment
# #method $1()
# Increment the $1 container.
def make(name)
define_method(name) { container.send(name).increment }
end
end
make :lion
make :pigeon
end
Hope it works for you.

Document using YARD an instance method's return type that returns instance of class

I couldn't find this anywhere including Yard's documentation.
I am looking to document an instance method's return type. For example,
class Foo
def initialize
end
end
I am documenting it as
#return [???] returns the instance of `Foo`
What should I write in ??? Should it be Foo?
Correct, as per tags overview it mentions the following:
Class or Module Types
Any Ruby type is allowed as a class or module type. Such a type is simply the name of the class or module.

Define variable type Ruby in function declaration

How can i declare variable type in function definition?
In the next code:
def f(a, b)
#...
end
I can forget with what type it works with. How can i declare that it is a fixnum? Thanks
There are two points of confusion in your question:
Variables don't have types in Ruby. Only objects do.
Fixnum is not a type, it is a class. Classes aren't types. Protocols are types. Any object which speaks the correct protocol is of the same type. Some examples of well-known protocols in Ruby that do not have a corresponding class or mixin are what I will call the Appendable protocol (consisting of a method named << that appends its argument to its receiver) or the Iterable protocol (consisting of a method named each which yields each of its receiver's elements in turn). [Note: I made up those names. In reality, those Protocols are so deeply ingrained in Ruby culture that they don't even have names.]
In general, you will describe the protocol(s) your method expects from its arguments in the documentation of the method, but sometimes this description is left out if e.g. it is obvious from the name of the method or the name of the parameter.
See comments and the other answers for a clarification about what is really a "type".
How you can test a "variable's type" (in Ruby every variable references an object and we don't talk about types but classes)
How you can test for a class :
variable = MyClass.new
variable.class # => "MyClass"
variable.is_a?(MyClass) # => true
But typically in Ruby we are more interested in what the object can do, what we can do with them :
if variable.respond_to?(increment)
variable.increment
else
raise "Error : must provide as 'variable' an object that can increment itself"
end
Now to answer your question, I would "declare" the behavior in the function definition / comments using rdoc syntax
# Description of f
# Params:
# +a+:: command line string to be executed by the system
# +b+:: +Proc+ object that ....
def f(a, b)
# code
end

What are some other uses of class methods in Ruby, other than to just set an attribute/variable of a class?

Can you modify a class when you call a class method? Or is it just for setting attributes, like this example.
class UnitedStatesPresident
def self.citizenship
"United States of America"
end
end
p UnitedStatesPresident.citizenship
You can modify a class using a class method. Here's an example that defines a new class method whose purpose is to add a method to the class (itself) after the class has been defined:
class UnitedStatesPresident
def self.citizenship
"United States of America" # Notice the absence of backticks (`)
end
def self.create_method(name, &block)
self.class.send(:define_method, name, &block)
end
end
p UnitedStatesPresident.citizenship # => "United States of America"
UnitedStatesPresident.create_method(:name) do
"Barack Obama"
end
p UnitedStatesPresident.name # => "Barack Obama"
For more info, see the documentation: http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-define_method
EDIT: Notice that the author of the documentation uses #send in the example above to send the #define_method message, which is private. The author does this reluctantly, referring to this style as a "hack."
Yes, you can modify a class using a "class method". (Note that actually, there is no such thing as a class method. Class methods are just singleton methods like any other singleton method. And in fact, singleton methods don't exist either, they are just regular instance methods of the singleton class. Or, they are just instance methods of the class of a class, which is Class or any of its superclasses (Module, Object, BasicObject).)
In fact, I am surprised that you haven't come across some class methods that modify the class already! Here are some examples:
Module#private without arguments: sets the default visibility for methods defined after it to private
Module#protected without arguments: sets the default visibility for methods defined after it to protected
Module#public without arguments: sets the default visibility for methods defined after it to public
Module#attr_reader generates a getter method
Module#attr_writer generates a setter method
Module#attr_accessor generates a getter/setter method pair
Module#include mixes a module into the inheritance chain as a superclass
Module#prepend mixes a module into the inheritance chain right below
Module#alias_method creates a copy of a method
Module#const_set sets a constant
Module#remove_const removes a constant
Module#class_variable_set sets a class variable
Module#remove_class_variable removes a class variable
Module#define_method defines a method
Module#undef_method undefines a method
Module#remove_method removes a method
Module#refine creates a refinement
Module#using activates a refinement
I am very surprised that you haven't come across alias_method, private or attr_* yet.

Resources