Use Memoization to Speed Up Your Code in Ruby/Rails

Have you ever heard Memoization? In this article you will get an introduction of Memoization. You will learn what it is, how you can use it to speed up your code.

What is Memoization?
Memoization is the process of storing a computed value to avoid duplicated work by future calls.

What are Memoization used for?
- Perform some work
- Store the work result
- Use stored results in future calls

Using
In Ruby the most common pattern for using Memoization is the conditional assignment operator: ||=.

Example
In Rails if you’ve ever worked with a user login system, you really family with the pattern of loading the current_user in the application_controller.rb:

application_controller.rb
1
2
3
def current_user
  User.find(session[:user_id]) if session[:user_id]
end

Within each request in a Rails application you will usually see multiple calls to current_user that means User.find method is run multiple times.

In this case, we know the problem is because there are multiple calls to current_user occurring. Let’s fix this code by introducing Memoization into the current_user method and storing the result of User.find method by using conditional assignment to an instance variable:

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

Well, there are no more calls to rebuild the User object each time current_user method is called.

When should you memoize?
- When you’ve got duplicated database
- When you’ve got expensive calculations
- When you’ve got repeated calculations that don’t change

When shouldn’t you memoize
- Memoization shouldn’t be used with methods that take parameters:

1
2
3
4
5
6
7
8
#incorrect
def full_name
  @full_name ||= "#{first_name} #{last_name}"
end

puts full_name('Bunlong', 'Van') #=> "Bunlong Van"

puts full_name('Ryan', 'Van') #=> "Bunlong Van"

- Memoization shouldn’t be used with methods that use instance variables:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#incorrect
def full_name
  @full_name ||= "#{first_name} #{last_name}"
end

@first_name = 'Bunlong'
@last_name = 'Van'

puts full_name #=> "Bunlong Van"

@first_name = 'Ryan'
@last_name = 'Van'

puts full_name #=> "Bunlong Van"

Memoization conditional assignment have problem when return nil or false in Ruby
If you are not clear in using conditional assignment it can bite you, let try code below:

1
2
3
4
5
6
7
8
9
10
11
def print
  @print || = begin
                puts "printing"
                sleep 2
                false
              end
end

print #=> "printing"

print #=> "printing"

Suprised that “printing” was printed twice? Conditional assignment is always going to run if the instance variable @print is false or nil.

Well, we can solve the problem by using defined?:

1
2
3
4
5
6
7
8
9
10
def print
  return @print if defined?(@print)
  puts "printing"
  sleep 2
  @print = false
end

print #=> "printing"

print

Memoization conditional assignment have problem when return nil or false in Rails

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

What’s happen if User.find return nil? Conditional assignment is always going to run and current_user method is always going to call if the instance variable @print is false or nil.

Well, we can fix the problem by using the Memoizable API in Rails:

1
2
3
4
5
6
7
# somewhere inside the class
extend ActiveSupport::Memoizable

def current_user
  @current_user ||= User.find(session[:user_id]) if session[:user_id]
end
memoize :current_user

Or we can use Memoizable to fix this problem

So far so good, I hope this article comes in handy for some of you who haven’t heard of memoization yet or who just didn’t really understand what’s going on there. :)