Handling keyword parameters in `method_missing` - ruby

When I write method_missing in a custom class I am not sure if I can implement it def method_missing(meth, *args, **kwargs, &block) or just def method_missing(meth, *args, &block). (The docs for method_missing do not cover keyword arguments.) How are keyword parameters handled in missing_method?
(N.B. This is mentioned in a comment on the question In Ruby is there a built-in method that requires keyword arguments?, but the answer does not cover the question in the comment.)

method_missing works just like any other method in this regard. When you use a double splat it will slurp any keyword arguments and kwargs will always be a hash:
module Foo
def self.method_missing(*args, **kwargs, &block)
kwargs
end
end
irb(main):049:0> Foo.bar(baz: 'Hello World')
=> {:baz=>"Hello World"}

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.

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'.

Undefined method `lambda' in ruby code

I have a code
class A < BasicObject
def initialize var1, *args, &block
if var1 == :lambda
#var1 = lambda &block
end
end
end
a = A.new :lambda, 123 do |var|
puts "ha ha ha"
end
why does it cause an error?
undefined method `lambda' for #<A:0x00000001687968> (NoMethodError)
unlike this one (it doesn't cause it)
class A
def initialize var1, *args, &block
if var1 == :lambda
#var1 = lambda &block
end
end
end
The lambda method is defined in the Kernel module. Object includes Kernel. BasicObject does not. So if you want to use lambda from a BasicObject, you have to call it as ::Kernel.lambda.
Note that this is not specific to lambda - it applies to any other Kernel method (like e.g. puts) as well.
PS: Note that #var1 = lambda &block does the same thing as just writing #var1 = block, so the use of lambda isn't actually necessary here.
You are using BasicObject as base class which is an explicit Blank class and specifically does not include Kernel, so you need the qualifier ::Kernel when you access any Kernel method.
On a separate note -
Instead of passing an argument that you have a block you can use the Kernel method block_given?
So taking your example -
class A
def initialize *args, &block
if block_given?
#var1 = lambda &block
end
puts #var1.call
end
end
a = A.new 123 do |var|
puts "ha ha ha"
end

Change Block Binding Without Eval?

I realize that you can change the binding of a block using instance_eval
class Foo
def bar &block
instance_eval &block
end
end
Foo.new.bar { self } # returns the instance
But some built in methods accept blocks and in that case it doesn't seem possible to change the binding of the block without messing with the internals of the built in method.
class Foo
def enum &block
Enumerator.new &block
end
end
Foo.new.enum { self }.each {} # returns main!!!
Is there a way around this?
You can work around it this way:
class Foo
def enum &block
Enumerator.new do |*args|
instance_exec *args, &block
end
end
end
But I'm confident that you cannot change the binding of an existing Proc short of instance_eval/instance_exec-ing it.

Extending a class method in a module

I'm playing with ruby's metaprogramming features, and I'm finding it a bit hairy. I'm trying to wrap a method call using a module. Currently, I'm doing this:
module Bar
module ClassMethods
def wrap(method)
class_eval do
old_method = "wrapped_#{method}".to_sym
unless respond_to? old_method
alias_method old_method, method
define_method method do |*args|
send old_method, *args
end
end
end
end
end
def self.included(base)
base.extend ClassMethods
end
end
class Foo
include Bar
def bar(arg = 'foo')
puts arg
end
wrap :bar
end
Three questions:
Is there any way to do this without renaming the method, so as to allow the use of super? Or something cleaner/shorter?
Is there a clean way to set the default values?
Is there a means to move the wrap :bar call further up?
1) Cleaner/shorter
module ClassMethods
def wrap(method)
old = "_#{method}".to_sym
alias_method old, method
define_method method do |*args|
send(old, *args)
end
end
end
class Foo
extend ClassMethods
def bar(arg = 'foo')
puts arg
end
wrap :bar
end
As far as I know there is no way to achieve this without renaming. You could try to call super inside the define_method block. But first of all, a call to super from within a define_method will only succeed if you specify arguments explicitly, otherwise you receive an error. But even if you call e.g. super(*args), self in that context would be an instance of Foo. So a call to bar would go to the super classes of Foo, not be found and ultimately result in an error.
2) Yes, like so
define_method method do |def_val='foo', *rest|
send(old, def_val, *rest)
end
However, in Ruby 1.8 it is not possible to use a block in define_method, but this has been fixed for 1.9. If you are using 1.9, you could also use this
define_method method do |def_val='foo', *rest, &block|
send(old, def_val, *rest, &block)
end
3) No, unfortunately. alias_method requires the existence of the methods that it takes as input. As Ruby methods come into existence as they are parsed, the wrap call must be placed after the definition of bar otherwise alias_method would raise an exception.

Resources