Model monkey-patching how-to

J Jon Tara 3 years ago
13 1 0

You  might want to monkey-patch a model (in Ruby) to override one or more of the standard methods.

As an example, you might want to delete related records automatically when a record is deleted. So, you can monkey-patch destroy in your model.

Unfortunately, Rhom models lack the handy callbacks provided by (Ruby on Rails') ActiveRecord. ActiveRecord has callbacks that you can define that will be called at various points - for example, before or after deleting a record. Rhom models have no such callbacks. (They would be awfully handy, hint, hint! Maybe something to consider for newORM?)

(Aside: I took a look at newORM. One major difference is that much of the code is now written in (cross-platform) C++, rather than Ruby. There is still a Ruby object factory, but most of the core functionality, e.g. CRUD is done in C++ code.)

Note that you cannot "subclass", because models are not derived from some common class. They are produced by an object factory, and worst, it is done for you automagically through a file-naming convention.

Here's how I monkey-patched my Venue model to delete any related Song records.

Note: unfortunately, delete_all does not call destroy for each record. For my own use case, this is fine, since I only ever delete Venue records individually, using destroy(). Again, those callbacks would be really handy! It might require considerable refactoring of the Rhom code to provide some of the callbacks, though, since I'd imagine Rhom just hands parameters for, e.g. delete_all() to SQLite, and so there would need to be some means for SQLite to send some callback for each record deleted.

Note: in the code below, the {{logInfo}} is just some preprocessing I do on my Ruby files. It inserts a call to Rho::Log (or not, depending on build-time options options).

BTW, did you know that using transactions both protects against partial updates, as well as bringing a HUGE performance boost? Make sure to use transactions whenever you create or modify a bunch of records. It's especially effective when seeding a database from e.g. some downloaded data. And also especially so when you are adding records to fixed-schema tables with a lot of indices. While in the transaction, SQLite is just writing flat "log" records, which is very fast. (I'm not sure if another thread is starting to insert real records while the log accumulates.) In any case, the transaction is completed and control returned to your code much sooner than without the transaction.

venue.rb

  # Redefine destroy so that related Song records are also destroyed.
  #
  # We cannot "subclass" destroy, becuase the model is created by a factory.
  # So, we have to monkey-patch.
  #
  # For an explanation of the "method wrapping" used here, see:
  # http://stackoverflow.com/questions/4470108/when-monkey-patching-a-metho…
  #
  # Note that destroy is NOT called if you call the delete_all class method.
  # This is fine for our use case, as we will only be deleting venues using the
  # destroy instance method.
  orig_destroy = instance_method :destroy
  define_method :destroy do
    db = ::Rho::RHO.get_src_db self.class.name.to_s
    db.startTransaction
    begin
      Song.delete_all_with_venue object
      orig_destroy.bind(self).()
      db.commitTransaction
      {{#logInfo}} "Successfully destroyed Venue and contigent records" {{/logInfo}}
    rescue
      db.rollbackTransaction
    end # begin
  end # define_method :destroy

song.rb

  def self.delete_all_with_venue(venue_id)
    {{#logInfo}} "Deleting all Songs with venue_id = #{venue_id}" {{/logInfo}}
    delete_all :conditions => {:venue_id => venue_id}
  end

Please register or login to post a reply

1 Replies

J Jon Tara

Note that this may not really be the best way to deal with deleting contingent records.

SQLite can do this on it's own, since it supports triggers.

That seems much preferable to me, and so I am going to explore just how much and how easily we can use SQLite directly (we have find_by_sql, but is there an easy way to issue ANY arbitrary SQL statement?) Need to see what version we have, if it supports triggers, and if Rhodes builds SQLite with trigger feature enabled.

Has anybody tried it?

CONTACT
Can’t find what you’re looking for?