tags:

views:

2175

answers:

9
+18  Q: 

Common Ruby Idioms

One thing I love about ruby is that mostly it is a very readable language (which is great for self-documenting code)

However, inspired by this question: http://stackoverflow.com/questions/609612/ruby-code-explained and the description of how ||= works in ruby, I was thinking about the ruby idioms I don't use, as frankly, I don't fully grok them.

So my question is, similar to the example from the referenced question, what common, but not obvious, ruby idioms do I need to be aware of to be a truly proficient ruby programmer?

By the way, from the referenced question

a ||= b

is equivalent to

if a == nil || a == false
  a = b
end

(Thanks to Ian Terrell for the correction)

Edit: It turns out this point is not totally uncontroversial. The correct expansion is in fact

(a || (a = (b)))

See these links for why:

Thanks to Jörg W Mittag for pointing this out.

+3  A: 

I like this:

str = "Something evil this way comes!"
regexp = /(\w[aeiou])/

str[regexp, 1] # <- This

Which is (roughly) equivalent to:

str_match = str.match(regexp)
str_match[1] unless str_match.nil?

Or at least that's what I've used to replace such blocks.

Daemin
+4  A: 

This slideshow is quite complete on the main Ruby idioms, as in:

  • Swap two values:

    x, y = y, x

  • Parameters that, if not specified, take on some default value

    def somemethod(x, y=nil)

  • Batches up extraneous parameters into an array

    def substitute(re, str, *rest)

And so on...

VonC
+18  A: 

The magic if clause that lets the same file serve as a library or a script:

if __FILE__ == $0
  # this library may be run as a standalone script
end

Packing and unpacking arrays:

# put the first two words in a and b and the rest in arr
a,b,*arr = *%w{a dog was following me, but then he decided to chase bob}
# this holds for method definitions to
def catall(first, *rest)
  rest.map { |word| first + rest }
end
catall( 'franken', 'stein', 'berry', 'sense' ) #=> [ 'frankenstein', 'frankenberry', 'frankensense' ]

The syntatical sugar for hashes as method arguments

this(:is => :the, :same => :as)
this({:is => :the, :same => :as})

Hash initializers:

# this
animals = Hash.new { [] }
animals[:dogs] << :Scooby
animals[:dogs] << :Scrappy
animals[:dogs] << :DynoMutt
animals[:squirrels] << :Rocket
animals[:squirrels] << :Secret
animals #=> {}
# is not the same as this
animals = Hash.new { |_animals, type| _animals[type] = [] }
animals[:dogs] << :Scooby
animals[:dogs] << :Scrappy
animals[:dogs] << :DynoMutt
animals[:squirrels] << :Rocket
animals[:squirrels] << :Secret
animals #=> {:squirrels=>[:Rocket, :Secret], :dogs=>[:Scooby, :Scrappy, :DynoMutt]}

metaclass syntax

x = Array.new
y = Array.new
class << x
  # this acts like a class definition, but only applies to x
  def custom_method
     :pow
  end
end
x.custom_method #=> :pow
y.custom_method # raises NoMethodError

class instance variables

class Ticket
  @remaining = 3
  def self.new
    if @remaining > 0
      @remaining -= 1
      super
    else
      "IOU"
    end
  end
end
Ticket.new #=> Ticket
Ticket.new #=> Ticket
Ticket.new #=> Ticket
Ticket.new #=> "IOU"

Blocks, procs, and lambdas. Live and breathe them.

 # know how to pack them into an object
 block = lambda { |e| puts e }
 # unpack them for a method
 %w{ and then what? }.each(&block)
 # create them as needed
 %{ I saw a ghost! }.each { |w| puts w.upcase }
 # and from the method side, how to call them
 def ok
   yield :ok
 end
 # or pack them into a block to give to someone else
 def ok_dokey_ok(&block)
    ok(&block)
    block[:dokey] # same as block.call(:dokey)
    ok(&block)
 end
 # know where the parentheses go when a method takes arguments and a block.
 %w{ a bunch of words }.inject(0) { |size,w| size + 1 } #=> 4
 pusher = lambda { |array, word| array.unshift(word) }
 %w{ eat more fish }.inject([], &pusher) #=> ['fish', 'more', 'eat' ]
rampion
DanSingerman
Martinho Fernandes
rampion
rampion
+5  A: 

I would suggest reading through the code of popular and well designed plugins or gems from people you admire and respect.

Some examples I've run into:

if params[:controller] == 'discussions' or params[:controller] == 'account'
  # do something here
end

corresponding to

if ['account', 'discussions'].include? params[:controller]
  # do something here
end

which later would be refactored to

if ALLOWED_CONTROLLERS.include? params[:controller]
  # do something here
end
ucron
Not really idiomatic ruby IMO. This has been widely used in PHP (CakePHP example): in_array(array('controller1', 'controller2'), $this->params['controller']))
kizzx2
+3  A: 

Here's a few, culled from various sources:

use "unless" and "until" instead of "if not" and "while not". Try not to use "unless" when an "else" condition exists, though.

Remember you can assign multiple variables at once:

a,b,c = 1,2,3

and even swap variable without a temp:

a,b = b,a

Use trailing conditionals where appropriate, e.g.

do_something_interesting unless want_to_be_bored?

Be aware of a commonly-used but not instantly obvious (to me at least) way of defining class methods:

class Animal
  class<<self
    def class_method
      puts "call me using Animal.class_method"
    end
  end
end

Some references:

Mike Woodhouse
+2  A: 

By the way, from the referenced question

a ||= b

is equivalent to

if a == nil   
  a = b 
end

That's subtly incorrect, and is a source of bugs in newcomers' Ruby applications.

Since both (and only) nil and false evaluate to a boolean false, a ||= b is actually (almost*) equivalent to:

if a == nil || a == false
  a = b
end

Or, to rewrite it with another Ruby idiom:

a = b unless a

(*Since every statement has a value, these are not technically equivalent to a ||= b. But if you're not relying on the value of the statement, you won't see a difference.)

Ian Terrell
Thanks - I will edit my question to reflect this
DanSingerman
I'm sorry, but your examples are wrong. This has been discussed numerous times on pretty much every mailinglist, newsgroup, forum and blog imaginable and the "best" answer (at least so far, until somebody finds yet another counterexample) is: a ||= b is equivalent to (a || (a = (b)))
Jörg W Mittag
See http://DABlog.RubyPAL.Com/2008/3/25/a-short-circuit-edge-case/ , http://DABlog.RubyPAL.Com/2008/3/26/short-circuit-post-correction/ and http://ProcNew.Com/ruby-short-circuit-edge-case-response.html for a discussion which shows that even Ruby luminaries like David Alan Black get it wrong.
Jörg W Mittag
No need to be sorry. My true point is that people forget that if you're working with booleans your false value can get overwritten. My examples aren't (in spirit) about rephrasing the idiom; they're about making it as expressive as possible so that people remember not just the nil case, but false.
Ian Terrell
A: 

Array.pack and String.unpack for working with binary files:

# extracts four binary sint32s to four Integers in an Array
data.unpack("iiii") 
SztupY
A: 

You can deepcopy with Marshaling object easily. - taken from The Ruby Programming Language

def deepcopy(o)
  Marshal.load(Marshal.dump(o))
end

Note that files and I/O streams, as well as Method and Binding objects, are too dynamic to be marshaled; there would be no reliable way to restore their state.

Comptrol
+2  A: 

Some more idioms:

Use of the %w, %r and %( delimiters

%w{ An array of strings %}
%r{ ^http:// }
%{ I don't care if the string has 'single' or "double" strings }

Type comparison in case statements

def something(x)
  case x
    when Array
      # Do something with array
    when String
      # Do something with string
    else
      # You should really teach your objects how to 'quack', don't you?
  end
end

... and overall abuse of the === method in case statements

case x
  when 'something concrete' then ...
  when SomeClass then ...
  when /matches this/ then ...
  when (10...20) then ...
  when some_condition >= some_value then ...
  else ...
end

Something that should look natural to Rubyists, but maybe not so to people coming from other languages: the use of each in favor of for .. in

some_iterable_object.each{|item| ... }

In Ruby 1.9+, Rails, or by patching the Symbol#to_proc method, this is becoming an increasingly popular idiom:

strings.map(&:upcase)

Conditional method/constant definition

SOME_CONSTANT = "value" unless defined?(SOME_CONSTANT)

Query methods and destructive (bang) methods

def is_awesome?
  # Return some state of the object, usually a boolean
end

def make_awesome!
  # Modify the state of the object
end

Implicit splat parameters

[[1, 2], [3, 4], [5, 6]].each{ |first, second| puts "(#{first}, #{second})" }
Chubas
According to David A. Black, "! does not mean that the method changes its receiver." - http://dablog.rubypal.com/2007/8/15/bang-methods-or-danger-will-rubyist
Yaser Sulaiman