The Real Adam Always course correcting

Posted
28 April 2008 @ 10am

In
Code

Can I send you a message?

Object#send - the joy of Rubyists and the scourge of those who would write refactoring tools. Let’s talk about it.

I recently ended up writing some code that would have proven really hideous if I couldn’t call #send. So I had a class like the following (written using code generation for brevity):


class Foo
  %w{one two three}.each do |name|
    class_eval <<-EOC
      def #{name}
        '#{name}'
      end
    EOC
  end
end

I needed to call one, two or three from a bit of code that takes some user input (in this case, an attribute value in a template language). So I cooked something like this up:


%w{one two three foo}.each do |arg|
  meth = case arg
  when 'one'
    :one
  when 'two'
    :two
  when 'three'
    :three
  else
    raise 'Unknown arg'
  end
  
  puts Foo.new.send(meth)
  
end

So I’m using a case statement to convert valid method names to symbols, which I then pass to #send and bam!, my method is called. This made my code a ton easier to write and I’m here espousing the technique to you.

But what is the drawback? Well, if someone were to ever really get up the courage to tackle the task of building a refactoring browser for Ruby, this sort of thing would give them fits. They can’t really tell where those methods are called on my class until they are actually called. Heck, given the code above, they can’t even figure out what methods exist on Foo without running the code.

The other drawback is that this code is a little hard to read. Most new casual Rubyists won’t think to search for the symbol version of a method name. Its even harder if you’re still somewhat new to Ruby and you aren’t aware this sort of thing even happens.

For me personally, the concise code I can write with #send far outweighs the drawbacks. I preach the importance of code reading regularly, and its how one can get over the “hump” that is knowing how to navigate software that sends dynamic messages to objects.

Your homework is to share how you feel about #send in the comments.


6 Comments

Posted by
Phil
28 April 2008 @ 11am

If you’ve already got the verbosity of the case statement, #send is entirely unjustified. Just remove the colon from the symbol names to call the methods directly.

Of course, the entire case statement is unnecessary to begin with; something this trivial shouldn’t take more than two lines:

raise “Unknown arg” if ![‘one’, ‘two’, ‘three’].include?(arg)
puts Foo.new.send(meth)

Three lines would be acceptable if you want to factor out a VALID_ARGS constant and check against that instead of inlining it.


Posted by
Greg Pierce
28 April 2008 @ 11am

In most cases, I think I’d rather have my object be the one to decide what valid arguments were, since that’s part of it’s domain. So I’d add a method on Foo, like…

def do_it(do_what)
…case or whatever to break out to method…
 end

my $0.02. greg.


Posted by
Ryan
28 April 2008 @ 12pm

Are you aware of the #to_sym method on String? I believe (as Phil pointed out above), #send works with a string or a symbol, so there’s not really a need for the case statement. However, let’s pretend you had to pass a symbol to #send.

puts Foo.new.send(meth.to_sym)


Posted by
jeem
28 April 2008 @ 6pm

I’m fine with your send, but your case creeps me out. How about:
raise ‘Unknown Arg’ unless %W(one two three).include? arg
puts Foo.new.send(arg)

(send can take a string as the message name.)


Posted by
Matthijs Langenerg
29 April 2008 @ 11am

Let’s pretend we didn’t see that. (don’t worry, I won’t tell anybody)


Posted by
Adam Keys
30 April 2008 @ 10pm

OK, ya’ll are right that I could stand to use an Array.include? rather than a case statement.

@Greg, I have mixed feelings about methods doing dispatch in that way. But maybe I’m the pot calling the kettle black.


Leave a Comment

Some language twins teach each other Halo Photography

Flickr View All » RubyConf Notes #3RubyConf Notes #2RubyConf Notes #1RubyConf Notes #0Dogs in the morningMy Day, YesterdayGetting aroundMolly loungingasdfjkl;