0

>Sharing database and related code between two Rails applications

>For the last couple of days, I’ve been wrestling with Rails to find the best way for us to share a database and related code between two Rails applications, both of which are on an svn server. Our needs are:

  • We have two application A and B.
  • These two are sharing database DB.
  • We want to have a model class library (let’s call this MCL for short) for DB, shared between A and B.
  • We want A and B to be able to define and use their own modifications to the classes in the MCL.
  • We want to have migrations shared as well.

If you are like me and coming from a CPP/Java based OO background, the first thought that comes to the mind is inheritance. In fact, I believe that the intuitive way to do this is:

  • Define model classes in MCL, inheriting from ActiveRecord::Base.
  • Then in each application, derive from the classes in MCL.

But, hey! That does not work. The reason is that when ActiveRecord converts database rows to objects, it either creates an objects from the class that inherits directly from the ActiveRecord::Base class, or it uses what is called “Single Table Inheritance“. Thus if we go for the intuitive solution, this happens:

  1. Suppose we have a class M, inheriting from ActiveRecord::Base in MCL.
  2. Now we define a class N in application A, inheriting from M, and define a new method a() in N (let’s say M does not have a method a()).
  3. We call N.find(:all) in application A.
  4. We try to call a() on one of the objects returned by the find, and we find that that object does not have a method a(), because its type is M, not N.

If ActiveRecord supported the factory pattern, the solution would have been simple, with this tiny additional step between steps 2 and 3 above:

  • Register class N with the ActiveRecord’s object factory.

A nice thing about ruby is that since classes are themselves classes, writing a generic factory is extremely straightforward.

A disclaimer is required here: I did not come across anyone discussing this simple factory pattern based alternative to single table inheritance, and this is a simple solution, so there might be something keeping people back from implementing this (and if you know what that reason is, please enlighten me).

Anyway, back to our problem. So, in order to solve the problem, I had to leave aside my OO programming instincts and give in to ruby way of doing things: extend a class that is defined elsewhere! So now our system works like this:

  1. Suppose we have a class M, inheriting from ActiveRecord::Base in MCL.
  2. Now we extend class M in application A, by just requiring its file in MCL, and enclosing the additions between lines class M and end. Suppose we add a new method a() in N (let’s say M does not have a method a())
  3. We call M.find(:all).
  4. We try to call a() on one of the objects returned by the find, and we find that it simply works.

By the way, if you use the same method here, you should remember to add the path to MCL in order to be able to require files in it.

This brings us to the next question: how can we share this library? For this purpose, we found that svn externals is capable of solving our problem, but there is a catch: when you commit, svn does not commit the external dependencies. This means it is easy for any of our developers to forget committing files they modified in MCL. To solve this, I have written a commit script in ruby that gets the list of external dependencies from svn:external property, then checks the external dependencies to find out whether they have been modified, and commits them if they are. This means that some of our commits are resulting in multiple changesets, but we properly commit all changed files.

I know this is not a thorough tutorial. If you have any questions or think that a full-fledged tutorial might be useful, send me an e-mail or comment to this blog entry. I’ll then try to find time to write that tutorial (just trying to conserve time :) ).

Cheers,
–eg

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir