How to call a module method dynamically

A

Adrian Klingel

I know about object instantiation and the send method, but the methods I
want to call are not in a class. They live in a module.

module Foo

module Bar

def mymethod
"xyz"
end

end

end


I can call it statically, like:

myvar = Foo::Bar::xyz


But how can I call it dynamically. Basically I have some batch jobs
kicked off by a rake task. I want to use a single rake task to kick off
any job, so I don't have to have a task for every single job, I can just
pass the job name (i.e. the name of the module method) to my one task
and have it kicked off.

Any help is appreciated! I'm missing something simple, but I can't find
it on google or in pickaxe.
 
T

Tom Link

I can just pass the job name (i.e. the name of the module method)

Couldn't you also pass a block?

do_something {Foo::Bar.mymethod}

You can get the method object via instance_method but then you have an
unbound method. Or you could turn that method into a module_function
which you could invoke via MyModule.send:)mymethod). But IMHO that's
weird.
 
A

Adrian Klingel

Tom said:
Couldn't you also pass a block?

do_something {Foo::Bar.mymethod}

You can get the method object via instance_method but then you have an
unbound method. Or you could turn that method into a module_function
which you could invoke via MyModule.send:)mymethod). But IMHO that's
weird.

Even weirder is monkey-patching Object with my module so I can just use
"send", which is what I did.
 
L

lasitha

I know about object instantiation and the send method, but the methods I
want to call are not in a class. They live in a module.

module Foo

module Bar

def mymethod
"xyz"
end

end

end


I can call it statically, like:

myvar = Foo::Bar::xyz

I'm going to assume you meant Foo::Bar::mymethod, but even that wouldn't work.

The way you've defined mymethod above makes it an instance method.
The only way to access a module instance method is to mix the module
into a class/object. If you need an instance method to double as a
class method, Module#module_function can help:

$: irb
01> module Foo
02> def mymethod; "xyz"; end
03> end
--> nil
04> Foo::mymethod
NoMethodError: undefined method `mymethod' for Foo:Module
from (irb):4
from /usr/local/ruby1.9/bin/irb:12:in `<main>'
05> module Foo
06> module_function :mymethod
07> end
--> Foo
08> Foo::mymethod
--> "xyz"

Having said all that, you haven't indicated any reason for these be
instance methods. If they aren't intended to be mixed into anything
and the Module is essentially just a namespace in which to define your
methods, they might as well be class methods.

If so, then Object#send will work just fine:

09> Foo.send:)mymethod)
--> "xyz"

I want to use a single rake task to kick off
any job, so I don't have to have a task for every single job

Reconsider. One of the nice things about rake tasks is they double as
documentation. Being able to run 'rake -T' and see a listing of
available commands is really useful, and more durable than having to
remember (or document elsewhere) the names of the available methods.
And since rake tasks are created dynamically, some simple reflection
and loop will ensure the tasks always match the methods available:

namespace :command
Foo.methods(false).each do |method_name|
desc "run #{method_name}"
task method_name do
Foo.send:)method_name)
end
end
end

I'm still not sure i understand your problem (!), but i hope some part
of all the above helps :)

lasitha.
 
S

Sean O'Halpin

I know about object instantiation and the send method, but the methods I
want to call are not in a class. They live in a module.

module Foo

module Bar

def mymethod
"xyz"
end

end

end


I can call it statically, like:

myvar = Foo::Bar::xyz

Can you? I get:

irb --> myvar = Foo::Bar::xyz
NoMethodError: undefined method `xyz' for Foo::Bar:Module

Perhaps you meant:

module Foo
module Bar
def self.mymethod
"xyz"
end
end
end

myvar = Foo::Bar::mymethod # => "xyz"
But how can I call it dynamically.

myvar = Foo::Bar.send("mymethod") # => "xyz"

Regards,
Sean
 
A

Adrian Klingel

Ahhh, I'm missing the "self", thanks Sean. But here's the other issue.
At runtime I don't know what module is going to be used. It could be
one of several. I don't know that it's Foo::Bar. It might be Foo:Bat or
Bat::Fat.

Is the answer then to do a dynamic include on Foo::Bar and just call
plain old "send"?
 
L

lasitha

Ahhh, I'm missing the "self", thanks Sean. But here's the other issue.
At runtime I don't know what module is going to be used. It could be
one of several. I don't know that it's Foo::Bar. It might be Foo:Bat or
Bat::Fat.

You can use Module#const_get to look up a module by name.

It helps if you can arrange for all the possible modules to be
contained in a single namespace - it can be Foo::Bar or Foo:Baz, but
not X::Y. That way you can look up the module with
Foo.const_get(name).

If it can be any arbitrary module (bad idea), you'll have to call
const_get on Kernel.

Cheers,
lasitha.
 
7

7stud --

Adrian said:
Ahhh, I'm missing the "self"


Ahhh, I guess you don't need this then:

module Foo

module Bar
def mymethod
"xyz"
end
end

end


str = "Foo::Bar::mymethod"

pieces = str.split("::")
method_name = pieces.pop
module_name = pieces.join("::")

mod = eval(module_name)

mod.module_eval do
module_function method_name.to_sym
end

puts mod.module_eval(method_name)
 
7

7stud --

Adrian said:
Yeah, that's definitely new to me. Thanks stud.

Me too. Can anyone explain why I can do this:

num = 1
puts Object.module_eval("num") #=>1

def f
"xyz"
end

puts Object.module_eval("f") #=>xyz

but I can't do this:

module Foo
def mymethod
"xyz"
end
end

puts Foo.module_eval("mymethod")

--output:--
`module_eval': undefined local variable or method `mymethod' for
Foo:Module (NameError)

That result and the result from my previous example seem to imply that
free standing def's are added to Object's singleton class, i.e. they are
class methods. But in pickaxe2, p.346 it says:
 
L

lasitha

Can anyone explain why I can do this:

num = 1
puts Object.module_eval("num") #=>1

def f
"xyz"
end

puts Object.module_eval("f") #=>xyz

but I can't do this:

module Foo
def mymethod
"xyz"
end
end

puts Foo.module_eval("mymethod")

--output:--
`module_eval': undefined local variable or method `mymethod' for
Foo:Module (NameError)

That result and the result from my previous example seem to imply that
free standing def's are added to Object's singleton class, i.e. they are
class methods.

No, they are private instance methods of Object:

$: irb
01> def free_standing; end
--> nil
02> Object.private_instance_methods.grep /free_standing/
--> [:free_standing]

I suppose the confusion comes about because of the line:
puts Object.module_eval("f") #=>xyz

The reason this works is not that f was defined as a class method.
Its because the context in which f is resolved inherits from Object.

03> Object.module_eval do
04> puts "self: #{self}"
05> puts "ancestors:", self.class.ancestors
06> end
self: Object
ancestors:
Class
Module
Object
PP::ObjectMixin
Kernel
BasicObject

The current object inherits from Object (just like all objects) and
the method f was defined as an instance method of Object, so it is
available in this context.

It is of course confusing that the current object is Object and also
inherits from Object. How ruby keeps all that straight is beyond my
knowledge :), but it does make sense intuitively.

Cheers,
lasitha
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,965
Messages
2,570,148
Members
46,710
Latest member
FredricRen

Latest Threads

Top