Watch Out When Overriding Memoized Methods
This post was inspired by a bug that was caused by not thinking ahead when overriding a memoized method. Once we noticed the buggy behaviour, it took me some binding.pry time to figure out what was going on.
Lets check out what happened.
A simple Ruby class which is the base class for the specific actions we need to implement for our Rails app.
The action for which we figured out the bug was the action of creating comments on various objects. This is what the code looks like:
Not much code is actually shown but just enough to spot the bug. Found it?
What Was Going On?
Basically what was happening when running CreateComment#execute_action was the following:
In the execute_action we firstly need to interpolate some attributes – defined by the specific action class. In this case, for the CreateComment action that was only the 'body' attribute.
That’s fine – we do some logic, the 'body' attribute gets interpolated and that value is set as the new value for the 'body' key in the form_attributes hash.
But, as the implementation of the CreateComment class uses super.merge(...) what we get is actually a new hash each time the method is called.
The next time, after the interpolation is over, the method form_attributes is called when passing that hash to our form object and in that moment, the form object gets a newly generated hash that doesn’t have the interpolated 'body' value. So the interpolation was actually pointless as would any other modification of the form_attributes hash prior to the form.process call be.
The Solution
Added memoization to the CreateComment#form_attributes method.
Simple as that and when you think about it, also pretty obvious that it needed to look like this from the start.
p.s. These snippets maybe look a bit too simple and memoization probably looks like it’s not needed but it’s a stripped version of the real classes. The form_attributes method is called and modified quite a few times.