Preventing Callbacks from Firing
I have an ActiveRecord class called JournalQuery that performs a large webservice call out to the National Library of Medicine, then caches the result in our local database. Oftentimes, this call can take well over a minute to run, which is not good for the HTTP request/response cycle. In the Rails applicatin got around this limitation of HTTP by using an after_create callback to fork this web-services call into a background process using the library BackgrounDRB. My Rails app creates a new JournalQuery, and after it is saved to the database, the after_create callback launches the BackgrounDRB worker transparently.
That’s fine and dandy for the Request-Response cycle, but right now I need to write a Rake task to pre-compute and pre-cache a bunch of canned JournalQueries. I do not want to deal with the overhead and complications of forking into a background process; it’s totally overkill and unnecessary, since this Rake task is going to be fired by a cronjob, and cron obviously has no request-response cycle!
So, what about that callback? How can I avoid tripping it in my Rake task, without changing the code in my Model class?
The Easy but Bad Solution:
I could simply Monkey-Patch my Model class, redefining my callback function to return true, instead of forking off the background process. Monkey patching is the process of writing code that, at runtime, opens up a class, and redefines methods inside that class. Yes, Ruby lets you do that, and yes, it is often as terrible an idea as it sounds (although sometimes extremely handy).
I didn’t want to do this, because this egregious a monkey patch would probably lead to unforeseen and extremely weird behavior at some point down the line. Then I got to thinking…what if I saved the code inside the original callback, applied said egregious monkeypatch, then monkeypatched my monkeypatch with the original callback, thus restoring harmony to the universe?
The Elegant Solution That Took Two Hours Instead of Two Minutes:
…And what if I did it all inside a class method, that accepted a block? Yeah baby! So I wrote this method, which is sitting at the bottom of this post. The method is called allow_skipped_callbacks_on, and accepts a Ruby Constant (assumed to be the name of a Class that you want to violate). This method opens up the victim class with a class_eval, and defines a class method called without_callback. Without_callback accepts a symbol, which is the name of your callback function, and a code block; without_callback saves the original callback method into a local variable, overwrites it with a stub, then executes the code in your block, and finally undoes all the changes it made to your poor little class.
Example:
Let’s say you have an ActiveRecord class called MyTable, and MyTable has a before_create callback method named my_callback. Now let’s say I want to populate the my_table database table with some data, and do not want my_callback to fire. Here’s how to do it
allow_skipped_callbacks_on MyTable MyTable.without_callback(:my_callback) do 100.times{ |n| MyTable.create(:name => "my_table_#{n}") } end
Pretty cool huh? The code for allow_skipped_callbacks_on and without_callback is below:
Code
def allow_skipped_callbacks_on(model) model.class_eval do def self.without_callback(method_name, &block) raise "#{self.name}##{method_name} doesn't exist, Hosehead" unless method_defined?(method_name) original_callback = instance_method(method_name) remove_method(method_name) define_method(method_name){ true } begin yield ensure remove_method(method_name) define_method(method_name, original_callback) end end end end