My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
RubyIdioms  
Ruby conventions and idioms
Updated Apr 29, 2012 by tokland@gmail.com

This document show some Ruby idioms, conventions and patterns (most of them accepted by the community, some of them more personal). This is a work in progress, feel free to email me add a comment if you have any suggestion (tokland AT gmail.com).

Idioms

General formatting

Choose a sound formatting style (first goal: legibility) and stick with it, but know when to break the rules (Waldo Emerson: "A foolish consistency is the hobgoblin of little minds").

Novice programmers tend to overlook the importance of code looks, but as SICP's authors remarked, programs must be written for people to read, and only incidentally for machines to execute. Be careful with the code you write.

Indentation

Use 2 spaces, no tabs, and never ever mix spaces and tabs. Keep your code under 80-char width whenever possible.

Whitespaces

  • Write an space after commas.
  • No spaces after or before a (, [ or { (when it's a hash):
array = [1, 2, 3]
hash = {:a => 1, :b => 2}
def fun(arg1, arg2, arg3); end

But use spaces to let the blocks breathe:

[1, 2, 3].map { |x| 2*x }

Note that some programmers do put spaces after "[" [ 1, 2, 3 ] and "{" { :a => 1 }. You'll see this style throughout Rails code, for example. I don't recommend it, values seem to "float" due to that space.

Naming

  • StudlyCaps for modules and classes.
  • ALL_UPPER_UNDERSCORED for constants (but StudlyCaps are also accepted).
  • lower_undercase for everything else: methods, attributes, local variables, whatever.

Blank lines

My personal rules are:

  • Don't insert empty lines between different indentation levels, only between definitions (module, class, def, ...) on the same level. The indentation itself should serve as visual hint.

  • In a given level insert blank lines, sparingly, to separate logic blocks.

Example:

module Animals
  class Dog
    attr_accessor :name
    attr_accessor :color
    
    def initialize(name, color)
      self.name = name
      self.color = color
    end
    
    def bark
      "bark, bark"
    end
  end
  
  class Cat
    ....
  end
end

Blocks

Single line blocks are written with brackets:

obj.method { |foo| ... }

Multi-line blocks are written with do/end:

obj.method do |foo|
  ...
  ...
end

Reginald Braithwaite proposed to use brackets for functional blocks and do/end for blocks with side-effects. While I find the idea interesting, brackets on multi-line blocks look weird, so I wouldn't recommend this style.

Multi-line array/hashes

There are many ways of writing multi-line array or hashes. I recommend this style:

array = [
  1,
  2,
  3,
]

Note that we insert a comma even after the last element. This is very handy because we have not to worry whether we are writing the last line or no (and you can reorder without further editing). The same idea applies for hashes or the combination of both:

hash = {
  :a => 1,
  :b => { 
    :b1 => "11",
    :b2 => "12",
  },
  :c => [
    "hello",
    "there",
  ],
}

Use of parenthesis

Parenthesis on method definitions/calls are (usually) optional in Ruby. But my personal advice is: just write them. The only valid exception I can thinkk of are DSL-style calls. The best known example are Rails class methods, which look very good indeed:

class Post < ActiveRecord::Base
  belongs_to :blog
end

Method name qualifiers

If your method returns a boolean, end it with ?:

dog.hungry?

If your method does does something "dangerous" (an in-place operation, or it may raise an exception on errors, or it's destructive, etc), end it with !:

database.destroy!

Testing for truth values

No, no, no:

  if !some_object.nil?
    ..
  end

Yes:

  if some_object
    ...
  end

Ruby's objects are always true (in boolean terms) except for nil and false, so the only valid reason to write the verbose object.nil? would be telling nil from false, something hardly needed.

Method arguments

No:

def method(arg1, arg2, arg3=1, arg4="hello", arg5="bye")
  ...
end

Yes:

def method(arg1, arg2, options = {})
  options.reverse_update(
    :arg3 => 1,
    :arg4 => "hello",
    :arg5 => "bye",
  })
  ...
end

Why:

  • It separates clearly required from optional arguments.
  • You don't have to write intermediate arguments to reach the argument your want.
  • You'll get a shorted (and cleaner) method signature. Long signatures are a nightmare both to read and call.

Downside:

  • Available options are explicit in code but not in documentation.
  • Hash#reverse_update is, of course, non-functional. If you choose FP purity, use two different variables and call reverse_merge. Note: these two functions are found in Activesupport.

Calling functions with key arguments

It's not necessary to use explicit {} to indicate it's a hash:

dog.bark(:volumen => 10, :duration => 5)

However, if there are lots of options and the line is too long, I recommend this multi-line style:

dog.bark({
  :volumen => 10, 
  :duration => 5,
  :direction => :ne,
})

Unpack enumerables in block arguments

No:

[[1, 2], [3, 5]].map do |array|
  array[0] - array[1]
end # [1, 2]

Yes:

[[1, 2], [3, 5]].map do |v1, v2|
  v2 - v1
end # [1, 2]

Note that you can expand as nested levels as you need to using parenthesis:

[["hello", [2, 3.3]], ["bye", [4, 1.1]]].map do |string, (integer, float)|
  ...
end

Use an script both as library or executable

if __FILE__ == $0
  # this script was not imported but executed, do something interesting here
end

Catch-all rescue's

What do you think of this code?

author_name = Book.find(book_id).author.name rescue nil

IMO this is plain unacceptable. A silent and indiscriminate rescue catches errors of all kinds (not only the ones you have in mind!), so it becomes a hard-to-debug source of bugs. Just don't use it.

Then how to write this code without filling it with conditionals? check the section that covers the "maybe" pattern.

Functional Ruby

Ruby is not a functional language. In fact, being a classic OOP language it tacitly promotes changes in the state of objects. But, at the same time, Ruby comes with great functional capabilities.

This section was getting pretty long, so I moved it to its own page: RubyFunctionalProgramming.

Build your own toolbox

Extend with generic functions

So you have a dog named Scooby and want to know if it's in your list of selected dogs. You would probably write:

["rantanplan", "milu"].include?("scooby") #=> false

Notice how the code reversed the phrasing. Why don't we create an abstraction that reflects how we think about the problem?

class Object
  def in?(enumerable)
    enumerable.include?(self)
  end
end
"scooby".in?(["rantanplan", "milu"]) #=> false

Use blocks as wrappers

Never wrote something similar to this?

tries = 0
loop do 
  begin
    process
  rescue Exception1, Exception2 => error
    tries += 1
    retry unless tries > 5
    nil
  end
end

There is nothing really wrong with this snippet, but look at the relevant code: it's literally buried in a jumble of infrastructure. Let's abstract the high-level construction so: 1) we simplify the code, and 2) we leave a method to be reused on other projects:

retry_on_exceptions([Exception1, Exception2], :max_retries => 5) do
  process
end

In this second version you see right away what's going on. This example presents a very useful pattern: use blocks as high-level wrappers. The more generic the pattern is, the more reason you should abstract it. This is nothing new, wrappers with callbacks are used in many languages, but the difference with Ruby is how easy is to send anonymous pieces of code as blocks.

Some abstractions

Managing nil's. The Maybe pattern

You have find a lost dog and you want to know where he lives. You'll write:

dog_address = dog.owner.address

But hey, there are street dogs with no owners. Then you should be careful and do this instead:

dog_address = dog.owner ? dog.owner.address : nil

Not nice, but we can live with it. But what happens if the chain continues? let's say we want to know which city the dog comes from:

dog_address = dog.owner ? dog.owner.address : nil
dog_address_city = dog_address ? dog_address.city : nil

We shouldn't be ok with this, each level adds more verbose and repetitive code. There are (at least) three approaches to solve this:

  • Use a wrapper: Activesupport's Object#try.
require 'active_support/core_ext/object/try'
dog_owner_street = dog.owner.try(:address).try(:street)
  • Use a proxied object: Ick's Object#maybe.

Rails' try is not bad, but some programmers don't like seeing that their usual object.method turned into object.wrapper(:method). Ick's maybe (or andand, it's the same idea) takes the proxy approach, you simple insert a maybes the object if it may be nil:

dog_owner_street = dog.owner.maybe.address.maybe.street
  • Use a block. Ick's Object#maybe also supports this pattern:
dog_owner_street = dog.owner.maybe { |owner| owner.address.street }

I'd recommend the second one, the maybe proxy approach without blocks: compact yet explicit.

Enumerable#map_select

Haskell uses a very cool mathematical construction called list comprehension to build lists. For example, to find out the squares of the first odd natural numbers between 1 and 10:

[x^2 | x <- [1..10], odd x]

Python smartly borrowed it:

[x**2 for x in range(1, 10+1) if x % 2 == 1]

But, alas, Ruby has no such construction. However, we can build our poor man's list comprehension, as long as we define which value (usually nil) is to be filtered out from the output. That's how it would look:

(1..10).map_select { |x| x**2 if x.odd? }

Note that real list-comprehensions are more powerful, as they allow nested iterations.

module Enumerable
  def map_select(value_for_skip = nil)
    self.inject([]) do |acc, item|
      value = yield(item)
      value == value_for_skip ? acc : acc << value
    end
  end
end

Enumerable#map_detect

Simple exercise: find the first element in a list with a square greater than 10 and return this squared value. Options:

1) Simple: detect + operation:

n = [1,2,3,4].detect { |x| x**2 > 10 }
result = n ? n**2 : nil # 16

Booh, we needed to repeat the operation for the matching value and we also need to control that it's not nil. Ugly.

2) map+first:

result = [1,2,3,4].map do |x| 
  square = x**2
  square if square > 10
end.compact.first # 16

That'd be conceptually ok in a lazy language, but Ruby maps the whole array, so we'll get very bad performance.

3) Our new abstraction Enumerable#map_detect:

result = [1,2,3,4].map_detect do |x| 
  square = x**2
  square if square > 10 
end # 16

So our map_detect is in fact a lazy version of the second example (map+compact+first). A possible implementation:

module Enumerable
  def map_detect(value_for_no_matching = nil)
    self.each do |member|
      if result = yield(member)
        return result
      end
    end
    value_for_no_matching
  end
end

Object#or_else

First an intro: operators in other languages are usually methods in Ruby. When you write 2 + 3 this is nothing more than sintantic sugar for 2.+(3). This is cool, because you can override all these methods. But && and || are special in Ruby, you cannot override them because they deal with short-circuited evaluation (and Ruby has no such feature for methods).

Ok, now consider this case where an expression in a short-circuit cannot be written in a single expression (expr1 || expr2), you need more:

result = expr1 || { expr2a; expr2b; ...; expr2final } # not valid Ruby

But this is valid:

result = expr1 || begin expr2a; expr2b; ...; expr2final end

But what if we want to use blocks for that? we've seen that || is not overridable so we cannot make it accept a block, we must write or own "or" method. Let's call it or_else:

result = expr1.or_else do 
  expr2a
  expr2b
  expr2final
end

And that's it, the same short-circuited expressions but now using blocks. The implementation is as simple as it gets:

class Object
  def or_else(&block)
    self || yield  
  end
end

Of course, if you find Object#or_else useful you may probably want to write Object#and_also. Now you can expand these methods with the options yous see fit. For example, it may be nice to decide what is a false value:

result = expr1.or_else(:if => :blank?) { alternative }

Object#presence

This code is usually ok:

name = database_name || session_name || default_name

However, sometimes one of these values will be an empty string (which Ruby considers it to be true) but you don't want empty strings. Activerecord's presence is a good solution, just "nilify" empty values:

name = database_name.presence || session_name.presence || default_name.presence

Of course you could have written:

name = [database_name, session_name, default_name].detect(&:present?)

But this would compute all values (|| does not), and they may not be as cheap to calculate as the ones in this example.

Object#as

Look at this code:

result = info[0] * info[1]

At first glance it's not clear what it is doing. What about:

width, height = info
area = width * height

Now this is better, we are getting the area of a rectangle. It's really good to name things before using. Well, there is nothing wrong now, but we needed an extra line to unpack the array. How about that?:

area = info.as { |width, height| width * height }

It's a matter of taste, but I think this is also pretty clear. Another example:

y = x**2
pair = [y, y+1]

Can be written as:

pair = (x**2).as { |y| [y, y+1] }

The implementation of Object#as is straighforward:

class Object
  def as(&block)
    yield self
  end
end

What is beautiful code?

Beautiful code looks visually good for us poor humans to read, beautiful code abstracts repeated patterns, beautiful code uses the right algorithms, beautiful code is generic and reusable, beautiful code is compact yet understandable.

Beautiful code divides instead of spreading complexity, beautiful code uses basic types whenever possible, beautiful code does not sacrifice clarity for efficiency, beautiful code is (almost) self-documented.

Ugly code only breeds ugly code. Beautiful code breeds more beautiful code.

Conclusion

  • Write beautiful code. This is valid for any language, but while some of them just keep getting in your way no matter how hard you try, that's not the case with Ruby.
  • Don't worry about performance too soon: Maintenance of code is very, very important. Write simple code that works (caveat: this does not mean ill-conceived code with the wrong algorithms). Profile it to see where the bottlenecks are and refactor. You know, "premature optimization is the root of all evil".

  • Ruby is not a functional language, and that's fine, but don't use it imperatively unless there is a good reason. Keep state to a minimum.
  • Ruby core developers can't forecast all your needs, but don't resign yourself to the existing toolbox. Programming is about building abstractions, your personal toolbox should be growing with each project you write. Reuse and progressively improve them.
In a nutshell: tweak the language to fit your needs and write maintainable, beautiful, compact, semantically meaningful code (incidentally, it would be great if it also works as expected... write tests!!).

Comment by princeka...@gmail.com, Mar 28, 2011

Hi, very useful..it made me to rethink about my coding styles. I was always reluctant to use inject in my code as I found it difficult to read. Your article forced me to have second thoughts about inject.


Sign in to add a comment
Powered by Google Project Hosting