A Simple Pattern for Ruby's inject method

Posted by admin 24/01/2010 at 14h05

Ruby’s inject is an often misunderstood method, but you would be surprised at how much it can help you clean up your code. I see many developers skipping over this method in favor of the non-injectified way, mainly because they dont get how to use inject properly. So we’ll take a look at some common examples and then you will be able to see the pattern that allows you to take advantage of inject. Let’s take a look at inject’s signature:

enum.inject(initial) {| memo, obj | block } => obj

This method is part of the Enumerable module, so you can use this method on many different types, including Arrays, Hash, Strings and many others. Now, let’s take a look at each part of the signature:

  • initial - this is the initial value that you want to set for the memo
  • memo - this is the accumulator object (the sum variable from your standard array summing function)
  • obj - this is the individual obj from the enumerablized container.
  • block - you should know what this is :D

Alright, now that we know what each piece is, let’s look at some examples and the refactor them with inject.

Example 1

This will be a simple summing example:

sum = 0
(1..30).each do |num|
  sum = sum + num
end

Now let’s refactor this with inject:

(1..30).inject(0) do |sum, num|
  sum + num
end

Here, we take the initial value of sum (0) and pass it as the argument to inject, this is the initializer. We then take the variable name sum and use it as the initial argument to the block, this is the memo parameter. num then becomes the enumerated object and we continue as we did in our original each method, but instead of assigning the value of the aggregation to the aggregator, we can just aggregate and ruby will take the value of the last line of the block and assign it to the memo.

Example 2

Let’s take a look at a common Rails example that I see all the time in helpers:

ret = ""
@products.each do |prod|
  ret << content_tag(:li, prod.title)
end
content_tag(:ul, ret)

Now we refactor with inject:

lis = @products.inject("") do |ret, prod|
  ret + content_tag(:li, prod.title)
end
content_tag(:ul, lis)

This time we initialize inject with “” and we are aggregating li tags for a nice HTML list. There is a more Ruby way to refactor this which would look like:

content_tag(:ul, nil) do 
  @products.inject("") do |ret, prod|
    ret + content_tag(:li, prod.title)
  end
end

Now, dont think that you can only use one-liners for your inject methods. They are Ruby blocks, and given all the freedoms that all blocks are given. We could do something like this if necessary:

content_tag(:ul, nil) do 
  @products.inject("") do |ret, prod|
    if prod.active? && prod.is_special?
      ret + content_tag(:li, prod.special_title)
    elsif prod.active?
      ret + content_tag(:li, prod.title)
    else
      ret + content_tag(:li, prod.inactive_title)
    end
  end
end

One other thing to note, inject’s memo object isn’t just for aggregating, it can be used as a simple container for checks:

success = @products.inject(true) do |ret, prod|
  ret && prod.active?
end

The above is a way to write the Enumerable#all? method. If you replace && with || you get the any? method.

That’s it for this tip, stay tuned for other refactoring tips with Ruby.