Memoization and RubyOnRails

Often with RubyOnRails (and other Ruby frameworks) we rely on instance variables and the or-equals operator (||=) to store some expensive data in memory to avoid costly database or API queries : this is called memoization.

Note :
As this post is fairly short I decided to not record an audio version of it, apologies.

class Group
	def users
	  @users ||= expensive_query

For those unfamiliar with this construct : if a value is already present in @users then expensive_query will not be called and the value will be returned directly.

Keeping things limited

There is a clear interest in using memoization. Queries to a database can be costly at times. Even with proper indexes, some queries are sometimes too much to be done often.

Instance variables are available throughout the instance once they have been defined. They are quite well adapted to be used within models or services objects has the context won’t be easily exposed beyond what we would want.
But with controllers, it's different. Defining an instance variable within a controller will also expose that variable to the rest of chain such as in the view or the template. It's expected as we use this mechanism in RoR to allow views and templates to work.

Usually, for the view or template, we only rely on instance variables that are defined within the controller’s action. Relying on instance variables from methods called by the controller is risky as it extend the dependency chain from the view to what ever defines those methods in the first place : a module, or a parent controller.
If one is then refactoring the way the module or parent controller work there might be an impact beyond what was expected.

The view and template should not have to know details of the implementation of the backend. They should be provided with just the data needed by the action rendering them.

Service objects

There is an idea that Controllers should be pretty light in content. Actions should be fairly short and pass on any major work to service objects, or background workers.

Instance variables needed by views and templates can then be prepared with the return values of those objects. Using such separate objects explicitly avoid bleeding of extra instance variables downstream.

There is also an argument to be made to keep whatever is needed to be done to fill the instance variable to a separate method.

class Group
	def users
	  @users ||= expensive_query


	def expensive_query
    # do something expensive

This allows to keep the first method smaller and more readable. It also means that the expensive_query can be tested separately.

Keep things simple and limited in responsability

I recently encountered a case of misuse of memoization while reworking a controller. It reminded me of some readings such as Sandy Metz ‘POODR’.

Overall I am a fan of keeping things simple and limit responsibility of objects and their methods as much as possible. Sometimes one has to balance this with the complexity it can add but it’s often worth it. The main interests with such approaches are to ensure code is readable, easy to test and to maintain.

You can read more on such topics in the following links :

Need help ?

We specialise in helping small and medium teams transform the way they build, manage and maintain their Internet based services.

With more than 10 years of experience in running Ruby based web and network applications, and 6 years running products servicing from 2000 to 100000 users daily we bring skills and insights to your teams.

Wether you have a small team looking for insights to quickly get into the right gear to support a massive usage growth, or a medium sized one trying to tackle growth pains between software engineers and infrastructure : we can help.

We are based in France, EU and especially happy to respond to customers from Denmark, Estonia, Finland, France, Italy, Netherlands, Norway, Spain, and Sweden.

We can provide training, general consulting on infrastructure and design, and software engineering remotely or in house depending on location and length of contract.

Contact us to talk about what we can do :