The last post on the basics of memoization covered what memoization was, how you can use it in your application, and some of the potential pitfalls.

In this post we’ll look at advanced assignment techniques, we’ll fix where basic memoization can fail, and you’ll see how to perform a more advanced memoization.

To refresh your memory, here’s a really basic example of memoization using the conditional assignment operator:

1 2 3 def current_user @current_user ||= User . find ( session [ :user_id ] ) end

Most times you can use a straight up single statement for performing memoization. But as your application grows you’re going to need additional, more expressive ways to perform memoization and build complex objects.

Advanced Assignment

Because Ruby is so awesome you can do memoization assignment with an if/else statement:

1 2 3 4 5 @current_user ||= if session [ :user_id ] User . find ( session [ :user_id ] ) else User . new ( guest : true ) end

It came as a real surprise to me when I found out that you can assign the results of an if/else to a variable. Another surprise was being able to use begin and end , like this:

1 2 3 @params ||= begin # do work end

These are two ways you can write a more advanced memoization method.

Where Conditional Assignment Fails

If you’re not careful, conditional assignment can bite you in the butt! Try this code in irb :

1 2 3 4 5 6 7 8 9 10 11 12 13 def foo @foo ||= begin puts "hit" sleep 5 false end end foo () # => "hit" foo () # => "hit"

Surprised that "hit" was printed twice? It turns out the conditional assignment operator is trickier than you thought!

Conditional assignment is always going to run if the variable @foo is falsely — that is if the value of @foo is nil or false .

Now it’s fairly rare to have code return false in a memoization scenario, but in the event that your code does, you can get around it using if_defined? and a guard return:

1 2 3 4 5 6 7 8 9 10 11 12 def foo return @foo if_defined? ( @foo ) puts "hit" sleep 5 @foo = false end foo () # => "hit" foo ()

This is a safer pattern to use when writing your memoization code, but more verbose versus the idiomatic Ruby version described previously. My personal preference is to use this only when it’s needed.

Memoizing with Parameters

In the last post on memoization one of the places I recommended not to use memoization was with parameterized methods. That was partially true because conditional assignment doesn’t handle parameters. However, you can get around that limitation by using a hash :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class A def initialize @results = {} end def expensive_operation ( p1 ) return @results [ p1 ] unless @results [ p1 ]. nil? @results [ p1 ] = begin puts "hit" sleep 5 end end end a = A . new a . expensive_operation ( 'a' ) # => "hit" a = A . new a . expensive_operation ( 'a' )

At this point you’re effectively re-implementing a cache. And to be perfectly honest, there are people smarter than you and I that have worked long and hard on cache, so there’s no point reinventing the wheel there! Cache on the other hand, is a future topic to dive into!