-
11
MAY
2012Delaying Decisions
I love playing with Ruby's
Hash
. I think it has a neat API and experimenting with it can actually help you understand how to write good Ruby. Let's dig into this idea to see what I mean.The nil Problem
In Destroy All Software #9 Gary chooses to show an example in Python because, unlike Ruby's
Hash
, it will raise an error for a non-existent key. Ruby just returnsnil
, he explains.What Gary said isn't really true, but I'm guessing he just didn't know that at the time. He was in the process of switching to Ruby from Python and I'm guessing he just didn't have a deep enough understanding of Ruby's
Hash
yet. I bet he does know how it works now.But assume he was right. What's he saying and why does it matter? Consider some code like this:
class SearchesController < ApplicationController def show terms = params[:terms] SomeModel.search(terms) # ... end end
This is what Gary doesn't like, and rightfully so. Because I indexed into
params
here with the[]()
method, I will indeed get anil
if the:terms
key wasn't inparams
. -
9
OCT
2008Dual Interface Modules
I'm guessing we've all seen Ruby's
Math
Module
. I'm sure you know that you can call methods in it as "module (or class) methods:"Math.sqrt(4) # => 2.0
That's just one way to use the
Math
Module
though. Another is to treat it as a mixin and call the same methods as instance methods:module MyMathyThing extend Math def self.my_sqrt(*args) sqrt(*args) end end MyMathyThing.my_sqrt(4) # => 2.0
Ruby ships with a few
Module
s that work like this, including the mightyKernel
.How is this dual interface accomplished? With the seldom seen
module_function()
method. You use this much like you wouldprivate()
, to affect all following method definitions:module Greeter module_function def hello "Hello!" end end module MyGreeter extend Greeter def self.my_hello hello end end Greeter.hello # => "Hello!" MyGreeter.my_hello # => "Hello!"
As you can see, it magically gives us the dual interface for the methods beneath it. You can also affect specific methods by name, just as you could with
private()
. This is equivalent to my definition above: -
8
OCT
2008Readable Booleans
There's a great little trick you can do to improve the readability of your code. A common problem is dealing with methods that have a boolean flag arguments. Here's an example I ran into just today in a Rails application:
def rating_stars(..., clickable = false) # ... end
The problem with this is that you typically see calls like this scattered around the application:
<%= rating_stars(..., true) %>
Would you know what
true
did there if I hadn't shown you the name of the variable first? I didn't. I had to go hunting for that method definition.Ironically the opposite problem, a magical dangling
false
, is much more rare in my experience. That's typically the default for these kind of arguments and it just makes more sense and reads better to leave it out.Anyway, the point is that we can typically improve the ease of understanding the common case. Remember that in Ruby
false
andnil
are false while everything else is true. That means that truth is very loosely defined and we can pass a lot of things for our boolean flag value. For example, after looking up the method and understanding what was needed, I chose to call it like this: -
6
OCT
2008Conversion Methods
I want to take a step back from all the syntax I've been covering lately and just talk about some simple methods in Ruby's core. Ruby ships with so many great helpers, it's often hard to keep track of what everything can do. Specifically, let's talk about the type conversion methods.
I assume we all make calls to
to_s()
andto_i()
regularly:255.to_s # => "255" "255".to_i # => 255
There shouldn't be any surprises there. Even these two simple methods can do more though. They make it possible to convert to and from various numeric bases. For example, here are the same conversions into and out of base 16 (hexadecimal):
255.to_s(16) # => "ff" "ff".to_i(16) # => 255
Ruby has other ways to do these same conversions. Here are two unusual methods (beginning with capital letters) that are similar:
String(255) # => "255" Integer("255") # => 255
I'll be honest and tell you that I don't really find
String()
useful as it just callsto_s()
for you, butInteger()
is a different story. First of all,to_i()
is very lenient about what it converts whileInteger()
is more strict: -
25
JUN
2008The One Method Config
I've used this technique a couple of times now for dirt-simple configurations. The idea is to provide a trivial way to read and write configuration values with just a single method. Let me show you what I mean:
module Configurable module_function def config(new_config = nil) if new_config.nil? @config ||= { } else config.merge!(new_config) end end end include Configurable config # => {} config :a => 1, :b => 2 config # => {:a=>1, :b=>2} config[:a] # => 1 config :a => -1, :c => 3 config # => {:a=>-1, :b=>2, :c=>3} config.clear config # => {}
There's no deep magic here, obviously. The method has two function: read and write for the configuration. Read is handled with what I like refer to as Ruby's "caching operator" (
||=
). The first time that line is triggered, it will cache an emptyHash
in the variable. Thereafter, the same call is just a cache hit to get the sameHash
back.