How define a method generation receiving variadic arguments - ruby

In a ruby class definition, I want to define a method generator. Say us that every method that I want to generate differs in a symbol and the number of parameters. So, I have something like this:
def self.my_define_service1(name) # generate for 1 parameter
define_method(name) do |p1|
# basic stuff
call(name.intern, p1)
end
end
def self.my_define_service2(name) # generate for 1 parameter
define_method(name) do |p1, p2|
# basic stuff
call(name.intern, p1,p2)
end
end
# and so on ... many definitions as the number of parameters I have
call is a method kind of dispatcher which receives a symbol name and the parameters.
My question is: is it possible to write only a generator? Something like that
def self.my_define_service(name, *args)
define_method(name) do |args| # <-- how to do here?
# basic stuff
call(name.intern, *args)
end
end
and if the answer is affirmative, how?

try with:
def self.my_define_service(name, *args)
define_method(name) do |*args|
# basic stuff
call(name.intern, *args)
end
end

It was a lot easier with eval:
def self.my_define_service(name, *args)
pars_list = args.join ', '
d = %{
def #{name}(#{pars_list})
call(':#{name}', #{pars_list})
end
}
eval d
end

Related

define_method multiple times with the same name

I've written a little wrapper over method_missing like this:
module Util
def method_missing_for(regex, &blk1)
define_method(:method_missing) do |name, *args, &blk2|
match = name.to_s.scan(regex).flatten[0]
if match
blk1.call(match, *args, &blk2)
else
super(name, *args, &blk2)
end
end
end
end
Then using it:
class Foo
extend Util
method_missing_for(/^say_(.+)$/){ |word| puts word }
end
Foo.new.say_hello
# => "hello"
The problem is that I cannot call this multiple times for a class. The method_missing I add with define_method just gets overridden. What alternative do I have? Conceptually I know I can refactor method_missing_for to take multiple regex => block mappings and then call it once instead of multiple times. At its core it would be a big case statement which tests all the regex. But I would rather be able to take advantage of super.
class Foo
extend Util
method_missing_for(/^say_(.+)$/) { |word| puts word }
method_missing_for(/foobar/) {}
end
Foo.new.say_hello # => NoMethodError
The problem is that I cannot call this multiple times for a class. The method_missing I add with define_method just gets overridden.
No, it doesn't. It gets overwritten.
You already have the solution in that sentence: you need to override it instead:
module Util
def method_missing_for(regex, &blk1)
prepend(Module.new do
##################### this is the only change compared to your code
define_method(:method_missing) do |name, *args, &blk2|
match = name.to_s.scan(regex).flatten[0]
if match
blk1.(match, *args, &blk2)
else
super(name, *args, &blk2)
end
end
end)
####
end
end
class Foo
extend Util
method_missing_for(/^say_(.+)$/) { |word| puts word }
method_missing_for(/foobar/) {}
end
Foo.new.say_hello
# hello
Note: it may or may not be beneficial to name the module, so that it shows up with a sensible name in the ancestors chain, which improves the debugging experience:
Foo.ancestors
#=> [#<Module:0x007fa5fd800f98>, #<Module:0x007fa5fd801df8>, Foo, Object, Kernel, BasicObject]
See also When monkey patching a method, can you call the overridden method from the new implementation? for a more comprehensive treatment.
My idea is to register the definition when declaring, and define those methods when all the declaration is done. The usage will be a little bit different, but I think is still acceptable
class Foo
extend Util
phantom_methods do
method_missing_for(/^say_(.+)$/){ |word| puts word }
method_missing_for(/^foo_(.+)$/){ |word| puts word }
end
end
The implementation of Util will be
module Util
def phantom_methods
# Initialize the registration table
#_phantom_methods = {}
# Allow declaring methods
yield
# Consume the registration table and define a single `method_missing`
define_method(:method_missing) do |name, *args, &blk|
pair = self.class.instance_variable_get(:#_phantom_methods).find {|regexp, prc| name =~ regexp}
pair ? pair[1].call($1) : super(name, *args, &blk)
end
# Good practice to also define `respond_to_missing?`
define_method(:respond_to_missing?) do |name, include_private = false|
self.class.instance_variable_get(:#_phantom_methods).any? {|regexp, _| name =~ regexp}
end
end
# Declare a phantom method
def method_missing_for(regexp, &blk)
# Register the definition
#_phantom_methods[regexp] = blk
end
end
By the way, I borrowed the phrase phantom method from this book.

How to create an object in Ruby without using new

It's possible to create a Complex number in Ruby using
c = Complex.new(1,2)
but, it can be shortened to
c = Complex(1,2)
Is it possible to achieve the same functionality without having to define a function outside the class, like in the example below?
class Bits
def initialize(bits)
#bits = bits
end
end
def Bits(list) # I would like to define this function inside the class
Bits.new list
end
b = Bits([0,1])
I think Ruby should allow at least one of the proposed constructors below
class Bits
def initialize(bits)
#bits = bits
end
def self.Bits(list) # version 1
new list
end
def Bits(list) # version 2
new list
end
def Bits.Bits(list) # version 3
new list
end
end
Have this snippet:
def make_light_constructor(klass)
eval("def #{klass}(*args) #{klass}.new(*args) end")
end
Now you can do this:
class Test
make_light_constructor(Test)
def initialize(x,y)
print x + y
end
end
t = Test(5,3)
Yes, I know you're still defining a function outside a class - but it is only one function, and now any class you want can make use of its implementation rather than making one function per class.
c = Complex(1,2)
is actually calling a method on Kernel
Basically you can't - the () operator cannot be overriden in Ruby (Complex class is written in C).
You could achieve something similar using []:
class Bits
def self.[](list)
Bits.new list
end
end
Which would allow something like:
b = Bits[[1,2]]
If you pack your classes into some module you can use 2 methods:
self.included - called when you include Mod
self.extend - called when you extend Mod
I have created very basic method using self.included.
Cons: It is hard to write. You can say it is complex; It may not contain all features.
Pros: It looks exactly like Complex(2,3) (it uses () instead of [] as in https://stackoverflow.com/a/24351316/2597260 answer); You create just initialize, self.included create the rest.
module M1
# some random classes
class A; end
class B
def initialize list
#list = list
end
attr_accessor :list
end
class C
def initialize var1
#var1 = var1
end
attr_accessor :var1
end
Answer = 42
# called on `include module_name`
def self.included mod
# classes are constants (in normal cases)
constants.each do |cons|
class_eval do
# I don't like hard-coded `::M1`
klass = ::M1.const_get cons
if klass.class==Class
define_method cons do |*args, &block|
klass.new *args, &block
end
end
end
end
end
end
include M1
p A()
b = B([1,2,3])
p b.list
c = C 42
p c.var1
puts Answer()
# NoMethodError: undefined method `Answer' for main:Object
# thats good, because Answer is not a class!
Here's another hack that you could (but shouldn't) use, inspired by this blog post:
def method_missing(sym, *args, **kwargs, &blk)
Object.const_get(sym).new(*args, **kwargs, &blk)
end
This simply expects any unknown method name to be the name of a class and calls :new on the class.
With rudimentary error handling:
alias sys_method_missing method_missing
def method_missing(sym, *args, **kwargs, &blk)
cls = Object.const_get(sym) if Object.constants.include? sym
if cls.is_a?(Class) then cls.new(*args, **kwargs, &blk)
else sys_method_missing(sym, *args, **kwargs, &blk) end
end
If an unknown method name is the name of a class, this calls :new on the class. Otherwise, it delegates the call to the original implementation of method_missing().
Usage:
class Foo
end
foo = Foo()
p foo
Result:
#<Foo:0x00007f8fe0877180>

How can I automatically forward all paramters from one method to another in Ruby?

If I have methods:
def method_a(p1, p2)
# do some stuff
method_b(p1, p2)
end
def method_b(p1, p2)
# do other stuff
end
Is there a way to call method_b and automatically pass all parameters to it? (Sort like how you can call super and it automatically forwards all params)
I know one appriximate method:
def method_a *args
# do some stuff
method_b *args
end
def method_b *args
# do other stuff
end
or expanding arguments in the second method:
def method_a *args
# do some stuff
method_b *args
end
def method_b p1, p2
# do other stuff
end
Since super is key-work method, the ruby interperter can treat it as of the same argument list as in the specific method you've called. But default from to call a method without argument is the same as for super method, just method name:
method_a # calls to :method_a without arguments, not as the same argument list for the caller method.
So, it will be strong omonim for the call method syntax.
Considering arbitrary number of arguments and a possibility of a block, the most general format is:
def method_a(*args, &pr)
# do some stuff
method_b(*args, &pr)
end
Then, in the definition of method_b, you can set a specific number of arguments and whether or not it takes a block.
Use *args like this:
def method_a(*args)
...
method_b(*args)
end
def method_b(p1, p2)
...
end
You can process the arguments like an array in the method_a.
Wow, this is hacky. But it works
def fwd_call b, meth
send(meth, *b.eval('method(__method__).parameters.map { |p| eval(p.last.to_s) }'))
end
def method1 x, y
fwd_call(binding, :method2)
end
def method2 x, y
x+y
end
puts method1(3, 4)
# 7

Ruby DSL define_method with arguments

This is what I'm looking to do.
# DSL Commands
command :foo, :name, :age
command :bar, :name
# Defines methods
def foo(name, age)
# Do something
end
def bar(name)
# Do something
end
Basically, I need a way to handle arguments through define_method, but I want a defined number of arguments instead of an arg array (i.e. *args)
This is what I have so far
def command(method, *args)
define_method(method) do |*args|
# Do something
end
end
# Which would produce
def foo(*args)
# Do something
end
def bar(*args)
# Do something
end
Thoughts?
I think the best workaround for this would be do to something like the following:
def command(method, *names)
count = names.length
define_method(method) do |*args|
raise ArgumentError.new(
"wrong number of arguments (#{args.length} for #{count})"
) unless args.length == count
# Do something
end
end
It's a little weird, but you can use some type of eval. instance_eval, module_eval or class_eval could be used for that purpose, depending on context. Something like that:
def command(method, *args)
instance_eval <<-EOS, __FILE__, __LINE__ + 1
def #{method}(#{args.join(', ')})
# method body
end
EOS
end
This way you'll get exact number of arguments for each method. And yes, it may be a bit weirder than 'a little'.

Ruby call method on all following def calls?

I've seen some code that makes a class method such that you can write
class_method :instance_method,
to alias instance_method and call it from in a wrapper method every time it is called. Is there a way to be able to call class_method and have it apply to all the following definition calls (like how private works)?
I don't quite understand your question. In the future, please provide a specification of what it is exactly that you are trying to do, preferably in the form of an executable testsuite, so that we can check for ourselves whether our answers really answer your question.
Are you perhaps talking about something like this?
module MethodHook
private
def class_method(m=nil)
return if #__recursing__ # prevent infinite recursion
return #__class_method__ = true unless m
#__recursing__ = true
old_m = instance_method(m)
define_method(m) do |*args, &block|
puts "before #{m}(#{args.join(', ')})" # wrap wrap wrap
old_m.bind(self).(*args, &block)
puts "after #{m}" # more wrapping
end
#__recursing__ = nil
end
def method_added(m)
class_method(m) if #__class_method__
super
end
end
Use like this:
class Foo
extend MethodHook
def unwrapped
puts __method__
end
class_method
def wrapped
puts __method__
end
end
f = Foo.new
f.unwrapped
# unwrapped
f.wrapped
# before wrapped()
# wrapped
# after wrapped
class Foo
class_method(:unwrapped)
end
f.unwrapped
# before unwrapped()
# wrapped
# after unwrapped

Resources