If you want to get all methods defined in a Module
, you can use one of the Module#instance_methods
family of methods, depending on what, exactly, you are looking for:
Each of these has an optional boolean parameter include_super=true
, which lets you decide whether to include inherited methods (the default) or only return methods from the exact module you are sending the message to (when passing false
).
If you want to get the parameters of those methods, you first need to obtain an UnboundMethod
reflective proxy object which represents the method you are interested in. You can do this by using the Module#instance_method
.
Once you have an UnboundMethod
, you can use UnboundMethod#parameters
to get a description of the method's parameters. Note, however, that you do not get the default argument of an optional parameter. That is actually impossible.
With these building blocks, you can build something like this:
class MethodHeaderFormatter
private
attr_accessor :name, :parameter_list
def initialize(name, parameter_list)
self.name = name
self.parameter_list = MethodParameterListFormatter.new(parameter_list)
end
public
def to_s = "def #{name}" + if parameter_list.empty? then '' else "(#{parameter_list})" end
class MethodParameterListFormatter
private
attr_accessor :parameter_list
def initialize(parameter_list)
self.parameter_list = parameter_list.map(&MethodParameterFormatter.method(:[]))
end
public
def empty? = parameter_list.empty?
def to_s = parameter_list.join(', ')
module MethodParameterFormatter
private
attr_accessor :name, :prefix, :suffix
def initialize(name) = self.name = name
public
def self.[]((type, name)) = const_get(:"#{type.capitalize}MethodParameterFormatter").new(name)
def to_s = "#{prefix}#{name}#{suffix}"
class ReqMethodParameterFormatter; include MethodParameterFormatter end
class OptMethodParameterFormatter
include MethodParameterFormatter
def initialize(name)
super
self.suffix = '=unknown'
end
end
class RestMethodParameterFormatter
include MethodParameterFormatter
def initialize(name)
super
self.prefix = '*'
end
end
class KeyreqMethodParameterFormatter
include MethodParameterFormatter
def initialize(name)
super
self.suffix = ':'
end
end
class KeyMethodParameterFormatter
include MethodParameterFormatter
def initialize(name)
super
self.suffix = ': unknown'
end
end
class KeyrestMethodParameterFormatter
include MethodParameterFormatter
def initialize(name)
super
self.prefix = '**'
end
end
class BlockMethodParameterFormatter
include MethodParameterFormatter
def initialize(name)
super
self.prefix = '&'
end
end
private_constant *constants
end
private_constant *constants
end
private_constant *constants
end
And you can use it like this:
module Test
def foo(a, b, c) end
def bar; end
def baz(d) end
def quux(m, o = 23, *r, k:, ok: 42, **kr, &b) end
alias_method :blarf, :quux
attr_accessor :frotz
end
puts Test.public_instance_methods(false).map { |meth| MethodHeaderFormatter.new(meth, Test.instance_method(meth).parameters) }
# def baz(d)
# def quux(m, o=unknown, *r, k:, ok: unknown, **kr, &b)
# def frotz=()
# def blarf(m, o=unknown, *r, k:, ok: unknown, **kr, &b)
# def frotz
# def foo(a, b, c)
# def bar
HOWEVER, please note that listing the methods of some module does not give you the protocol (i.e. the set of messages that are understood) of that module!
Here are two simple examples where the set of methods defined in a module does not correspond to the set of messages understood by instances of that module:
class Foo
def bar = raise(NoMethodError)
def respond_to?(meth) = meth != :bar && super
end
foo = Foo.new
foo.respond_to?(:bar) #=> false
foo.bar # NoMethodError
While this is a stupid example, and code that hopefully nobody would write for real, it clearly shows that while Foo
has a method named bar
, its instances do not respond to a bar
message the way you would expect.
Here is a more realistic example:
class Bar
def method_missing(meth, *) = if meth == :foo then 'Fooooo!' else super end
def respond_to_missing?(meth, *) = meth == :foo || super
end
bar = Bar.new
bar.respond_to?(:foo) #=> true
bar.foo #=> 'Fooooo!'
And finally, just in case you get your hopes up that you can find some insane meta-programming abstract interpretation trick that actually lets you list out the entire protocol of a module, let me disabuse you of that notion:
class Quux
def method_missing(*); end
def respond_to_missing?(*) = true
end
Voilà: a class whose instances respond to an infinite number of messages, in fact, they respond to every possible message. And if you think this is unrealistic, well, actually, something like this is what one of the most widely-used libraries in the Ruby universe does: ActiveRecord.