Happy Bear Software

Assorted Ruby Tips and Tricks

Browse the code of installed gems with Bundler

For example, in a rails project try running the following command from your project root:

$ bundle open activesupport

This will open whatevers in your $EDITOR environment variable at the source root of whatever gem you've picked (in this case activesupport).

While gem documentation in the ruby world tends to be good, nothing beats actually reading the code you're running when you want to get a good understanding of what's happening under the covers.

Work with and return an object using using #tap

In ruby 1.9+, Object has a tap method that yields iteslf to a passed in block and then returns itself.

Without tap:

def create_some_object(params)
  object = SomeClass.new
  object.field1 = 'some value'
  object.field2 = 'some other value'
  object.some_method!
  object
end

The equivalent using tap:

def create_some_object(params)
  SomeClass.new.tap do |o|
    o.field1 = 'some value'
    o.field2 = 'some other value'
    o.some_method!
  end
end

Whether you like the first or second example comes down to taste. Personally I like using tap when I'm working on another object for a few lines of code and I intend to return that object afterwards.

Generate one value from many with #reduce

Also known as inject or in some languages foldl, reduce is for whenever you want to generate a single value based on a computation over a bunch of them.

The simplest case is for things like summing an array:

[1,2,3,4,5].reduce { |memo, item| memo + item } #=> 15

# or shorter:
[1, 2, 3, 4, 5].reduce(:+) #=> 15

In this case, memo starts as the first value in the list and the return value of the block is the next value for memo.

Sometimes we want to build a value of a different type out of what we have in an array. Given an array of objects for example, you want to create a hash of those objects keyed by some value on that object (call it field1).

require 'ostruct'

objects = [
  OpenStruct.new(field1: 'foo', field2: 'cheese'),
  OpenStruct.new(field1: 'bar', field2: 'on'),
  OpenStruct.new(field1: 'baz', field2: 'toast')
]

keyed_by_field1 = objects.reduce({}) do |memo, item|
  memo[item.field1.to_sym] = item
  memo
end
#=> 
{
  :foo => #<OpenStruct field1="foo", field2="cheese">,
  :bar => #<OpenStruct field1="bar", field2="on">,
  :baz => #<OpenStruct field1="baz", field2="toast">,
}

In this case, the first parameter to reduce is a starting value for memo.

While this seems like a small difference to the working of reduce, I've found that using reduce in this way makes it an extremely generic tool for for any computation of a single value over a list, rather than the traditional averages and sums that it's used for. Anytime you find yourself building up a value while iterating over a list is probably a good time to see if you can do it with reduce instead.

Make simple data objects with Struct and OpenStruct

It's not very object oriented (where 'object oriented' == 'arbitrarily coupling implementation, type and state') but if you ever find yourself wanting to define simple data types in ruby, Struct is what you're looking for:

Customer = Struct.new :first_name, :last_name, :address
Address  = Struct.new :line_1, :line2, :town, :country, :postcode

leo = Customer.new({
  first_name: 'Leonardo',
  last_name:  'Turtle',
  address: Address.new({
    line_1: 'Turtle Hide Out',
    line_2: 'The Sewers',
    town:   'New York',
    country: 'USA'
  })
})

leo.freeze

This is slightly wierd syntax for ruby. Struct.new generates an instance of StructClass using the passed in symbols as fields.

Make objects immutable with #freeze

In the above example we called freeze on our finished data object. Making objects immutable makes it a lot easier to reason about their behaviour when they're used as arguments and return values of functions. Alas, a treatise on the benefits of immutable state won't quite fit into this blog post, but rest assured that immutability is good times.

Decorate objects with SimpleDelegator

For those of you that missed out on object oriented design patterns, the decorator pattern is a way to add methods to objects at runtime without explicitly changing them.

This is useful for you want to:

SimpleDelegator is a class in the standard library that makes implementing decorators nice and simple. Here's an example:

Customer = Struct.new :first_name, :last_name, :title, :address

# Subclasses of SimpleDelegator delegate
# everything they don't respond to to whatever
# you pass into the constructor
class CustomerDecoratator < SimpleDelegator
  def name
    "#{title}. #{first_name} #{last_name}"
  end

  def reversed_name
    "#{last_name}, #{first_name}".upcase
  end

  def to_hash
    {
      name: reversed_name,
      address: address
    }
  end
end

# Create a simple customer struct
fred = Customer.new({
  first_name: 'Freddie',
  last_name: 'Mercury',
  title: 'Mr',
  address: 'London'
})

# Decorate it with our decorator
fred = CustomerDecoratator.new(fred)
fred.name #=> "Mr. Freddie Mercury"
fred.to_hash #=> { name: "MERCURY, FREDDIE", address: "London" }

# Decorators can be stacked arbitrarily
class CustomerLocaleDecorator < SimpleDelegator
  def initialize(obj, locale)
    super(obj)
    @locale = locale
  end

  def to_s
    "#{field_names[:name]}: #{name}, #{field_names[:address]}: #{address}"
  end

  def field_names
    {
      ja: { name: '名前', address: '住所' },
      en: { name: 'name', address: 'address' }
    }[@locale]
  end
end

fred = CustomerLocaleDecorator.new(fred, :ja)
fred.to_s #=> "名前: Mr. Freddie Mercury, 住所: London"