views:

522

answers:

4

Consider this contrived example:

# Dispatch on value of fruit_kind:

TYPE_A = :apple
TYPE_B = :banana
TYPE_C = :cherry

eating_method = nil

case fruit_kind
  # Methods to use for different kinds of fruit (assume these are
  #  already defined)
  when TYPE_A then eating_method = bite
  when TYPE_B then eating_method = peel
  when TYPE_C then eating_method = om_nom_nom
end

Now I'd like to invoke the target of eating_method with some arguments:

# Doesn't work; this tries to invoke a method called "eating_method",
# not the reference I defined earlier.
eating_method(some_fruit)

What's the right way to do this in Ruby?

+4  A: 

Use send. Send takes the function name, so use symbols:

case fruit_kind
  # Methods to use for different kinds of fruit (assume these are
  #  already defined)
  when TYPE_A then eating_method = :bite
  when TYPE_B then eating_method = :peel
  when TYPE_C then eating_method = :om_nom_nom
end

send(eating_method, some_fruit)


EDIT:

By the way, did you know you can make that case a little prettier by doing something like this:

eating_method = case fruit_kind
  # Methods to use for different kinds of fruit (assume these are
  #  already defined)
  when TYPE_A then :bite
  when TYPE_B then :peel
  when TYPE_C then :om_nom_nom
  else nil
end

Or, as Sii mentions, use a hash instead:

fruit_methods = {:apple => :bite, :banana => :peel, :cherry => :om_nom_nom}
send(fruit_methods[fruit_kind], some_fruit)
Pesto
A: 

The wording confuses me greatly.

You probably want Object#send though.

Sii
That said, I slightly dislike the design. It makes more sense to make an #eat method on the fruit, or have the case/when directly call the responsible method, or at least use a hash for fruit_kind => method symbol.
Sii
I wholeheartedly agree that this would be a code smell. Dispatching on name is usually a terrible idea. What I was more interested in is how to invoke a function via a reference. Thanks for your help!
Kyle Kaitan
+1  A: 

In Ruby you can use classes and methods as if they are values without too much work, thus you can reduce the amount you actually have to manage in your example to a single definition of Class -> Method and use a generalized algorithm to work with that definition.

Suppose each of your types implement the methods you specify, e.g. Apple.bite(), Banana.peel(), and Cherry.om_nom_nom() are all defined. Also suppose that fruit is an instance of *fruit_kind* You can manage the mapping in one place and use a generalized method to do all of the Class specific method evaluation like so:

fruit_table = {Apple => :bite, 
               Banana => :peel,
               Cherry => :om_nom_nom}
eating_method = fruit.method(fruit_table[fruit.type])
eating_method.call

Note that the reference *eating_method* is an instance of a method object specific to the instance of fruit. You could pull the table definition out to be a class variable, but you would want to evaluate fruit.method in the context of each time that you are deciding which function to call on an instance you are passed.

animal
A: 

The Ruby object model lets you call methods dynamically using the Object#send method which takes a symbol as the method you want to call.

So if you were to have a class called FruitEater you could send the eating method as:


f = FruitEater.new

# Dispatch on value of fruit_kind:

TYPE_A = :apple
TYPE_B = :banana
TYPE_C = :cherry

eating_method = nil

case fruit_kind
  # Methods to use for different kinds of fruit (assume these are
  #  already defined)
  when TYPE_A then eating_method = :bite
  when TYPE_B then eating_method = :peel
  when TYPE_C then eating_method = :om_nom_nom
end

f.send(eating_method)
ba