Posted by avdi January 24, 2012
Sometimes you need to prevent code from executing recursively. I had to do this recently in order to prevent an infinite recursion in ActiveRecord callbacks. A before-save hook needed to save a parent model attribute, which because of an :autosave option would then try to save the child model, which would then try to save the parent… and so on until the stack overflowed.
Unless the method is directly calling itself, the only way to short-circuit a method when it recurses is to have it set some global flag which it can then check in recursive calls. If the flag is already set, the recursively called method bails out early. Once the original invocation is finished it unsets the flag.
It’s not safe to use a true global variable for this flag, since this would not be threadsafe. The safe way to do it (albeit not a terribly pretty one) is to use thread-local variables.
I decided to go ahead and write a macro which can turn any method into a non-recursive one. Here’s the code:
Let’s take a closer look at that.
The method takes an argument, method_name, which is the name of the method to make non-recursive.
Then it creates a name for the recursion flag. Since thread-local variables are global for the whole thread, we need a variable name which is reasonably unique. We get one by combining the current class and the method name.
We’re going to replace the method with a modified version, so we need to stash the original implementation somewhere. We grab an UnboundMethod object by calling #instance_method() on the current class:
Then we proceed to redefine the method:
The first thing the modified method does is check to see if the recursion flag is set. If it is, it bails out.
Otherwise, it sets the flag:
And then it binds the UnboundMethod stored in original to the current instance. This generates a Method instance, which it proceeds to call, passing any arguments along.
(Note that I wrote this code for a Ruby 1.8 codebase. If it were for 1.9 I would pass the &block down to the original as well. Since blocks in 1.8 can’t accept &block arguments, I can’t do that here so long as I’m using the block form of define_method. So this macro will only work for methods which do not take blocks.)
Finally, the code ensures that the recursion flag will be unset when the method is finished, even if an exception is raised.
Note that setting a thread-local variable to nil removes it:
I have no idea what :__inspect_key__ is, but as you can see the :foo key goes away after setting it to nil.
Let’s take a look at this in practice. Here’s are two methods #foo and #bar (original, no?). #foo is recursive, with #bar as an intermediary, preventing it from passing a recursion flag directly to itself.
Calling the #foo method results in a stack overflow:
But if we update the method with prevent_recursion, it successfully exits:
Posted by Taus May 31, 2011
Ruby on Rails makes it easy to bootstrap a greenfield project. Unfortunately not all projects are greenfield, so sometimes you need to work with a legacy database schema. ActiveRecord is a great choice when you have a full control over your database from the very beginning, but what should you do if you need to connect to a database with a schema that isn’t in-line with Rails conventions?
DataMapper Core Contributor and Code Benders team member, Piotr Solnica, has written a great blog post for our partner Engine Yard entitled Using DataMapper and Rails with Legacy Schemas. Read it and you can say goodbye to your fear of inheriting a legacy app.
Read more...Posted by Taus May 23, 2011
A well-deserved congratulations to the team at CiviData on the Beta launch of their new local government data aggregation and comparison tool. After several months of iteration, the product is being launched to invitees only. If you are an employee of a local or state government and want a better understanding of how your services compare to other localities, you can sign up to join the free Beta test by visiting the CiviData website.
You can learn more about the project here.
Read more...