Living without send(), or trying to
Object#send (or __send__) is a scrappy little tool we Rubyists pull out to ever-so-casually ignore method private and protected visibility, and to call methods dynamically.
Maybe we’ve gotten into the habit of using it a little too often. Let’s play with alternatives.
An evaluation
Here’s an example from practically every monkeypatching Rails plugin init.rb you’ve ever seen:
SomeRailsConstant.send(:include, MagicSauce)
Okay, so include is a private method, and we’re breaking through indiscriminately by using send. If you wanted to do away with send because it seems weird, you might try something like this:
class SomeRailsConstant
include MagicSauce
end
Seems nice and simple; basic Ruby, right? Yes. But if you do this in Rails, you’ll likely ensure the real SomeRailsConstant, provided by Rails, never gets loaded – since Rails autoloading is lazy. What you’ve done here isn’t to reopen the class; you’ve just defined it. Bad, bad. You need to try to use the constant to kick the autoloading into gear for you. Here’s another approach that does just that.
SomeRailsConstant.instance_eval { include MagicSauce }
What’s this instance_eval nonsense – and why is it named “instance?” Isn’t SomeRailsConstant a class? Yes, yes it is. Which also means it’s an instance of class Class. Neat (and confusing), right? You can see this easily in irb:
>> class MyClass
>> end
>> MyClass.class
=> Class
But, stepping away from the foibles of Ruby object hierarchy for a moment, let’s address this approach; instance_eval. This method allows you to temporarily change scope; it lets you get inside an object and call whatever you’d like.
Okay, that’s fine– but is instance_eval really better than send? No, probably not in the strictest sense. It’s even more typing. But to my purist brain, send is for calling methods dynamically – and I know exactly what I want. instance_eval is also more flexible, since I can do multiple operations in the block, access instance variables, etc… which means I get to use it more consistently. Consistency keeps code readable, even in the presence of insane metaprogramming.
I like instance_eval. I also like how it’s not a normal dictionary word that’s in common use. Want to call send on a Message instance? Get ready to use the lamer __send__ name, since the developer of Message quite reasonably appropriated the name for actual message sending. It’s exactly this kind of inconsistent application that drives me up the wall.
Okay, and next.
Method acting
So, we’ve gone down the road of glossary consistency for plain, non-dynamic invocations. Let’s go further. Here’s another send; this one for dynamic reasons.
1 # `format` is defined somewhere else. Example: 'pdf'
2 File.open("#{report.name}.#{format}", 'w') do |file|
3 file.write report.send("to_#{format}")
4 end
What are we really doing here? Let’s convert how the business part of line #3 reads into plain English:
Tell the report to “to_#{format}”
Does that sound right? Let’s try to make this sound clearer:
Invoke a method called “to_#{format}” on the report
There’s a simple way to put this into code:
report.method("to_#{format}").call
So, here we’re using the arcane Object#method method to get a Method instance. These suckers are flexible. You can reuse Method objects, pass them around, unbind and rebind their UnboundMethod counterparts, check their arity, see where they come from using to_s, and invoke them. That’s what we do with call. Simple.
Is this good practice? Maybe not. It’s certainly slower than send, invocation-to-invocation (though not greatly). It’s kind of fun, though, and maybe a bit easier to read & document.
class Report
def formatter(name)
method("to_#{name}")
end
end
report = Report.new
report.formatter(:pdf).call
Do I use this frequently? No. But it’s a nice tool to have in the kit. Kind of functional, which always makes me happy.
Summary
I’m not telling you to stop using send. It’s a great little method that’s easy to grasp, simple to use, and rarely causes any problems. I’m just telling you to think about the rough edges a little. Don’t say yes just because you see it everywhere.
Just use it critically.






