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 memomemo
- this is the accumulator object (thesum
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.