I’ll admit – I’m very tempted by Ruby on Rails. I like its “Ruby Everywhere” philosophy. I like its “beauty leads to happiness” philosophy. I like its “damn straight it’s opinionated” attitude. I like how easily it gives you an application starting point out of the box (oh, you don’t know how I’ve been struggling with this this week). At least, I like all of this in theory. I haven’t tried making any real application in it yet. I did a quick version of the Knowledge Base application, just to see some results and see how using Tags felt compared to what I’ve done in Zope 3, but it's nothing close to complete (or even to matching the Zope 3 implementation).
Anyways, I’ve started exploring how this application might work in Rails. And it didn’t take me long to find fresh love for the ZODB.
The ZODB is a full Python persistence system. It’s native. A tuple is a tuple, string is a string, a CrewContact object is a CrewContact object, and so on. When developing in Zope 3 in the (general) fashion that I’ve been developing – straight Persistent model objects, all of the views, adapters, etc, coming in off disk (looked up and bound by the component architecture, not by Zope 2 techniques like acquisition) – using the ZODB for a new application is straightforward and quite easy.
I think an object-relational mapper is good when developing against an established database. And usually there needs to be some strong mapping for that to work and match database policies against expected object behavior. Rails’ ActiveRecord is a really nice system in many respects, and it provides a lot of nice features for fairly natural expressions for establishing relationships, doing validation, etc. But it also seems to expect a fairly rigid structure in place to work. Not that this is necessarily bad. But it begs the question: if you’re going through such terrific overhead and making certain restrictions (Single Table Inheritance, which I’ll get to in a minute), why use an RDBMS at all?
Well, I know some of the answer to this, and I agree with it in part: the DBMS vendors (including open source ones) are better at the data storage, retrieval, distribution, and connectivity side of things. “Let the database management system handle that,” some say. “Let SQLObject and ActiveRecord make it feel like natural objects with a minimum of fuss.” OK. That’s admirable.
But I realized today that it’s been a long time since I’ve had to think relationally just to store my objects – objects that have meaningful relations. Specifically, I ran into this problem.
- I have a class for Itinerary objects. An Itinerary is a container, which can contain many Legs. There are three types of legs, sharing a common base class/interface.
- ScheduledMeeting – Just a meeting that’s taking place in this itinerary.
- GroundTransportation – A car service, etc. Who’s providing it, any other notes (where to meet for pickup, etc).
- Flight – many fields (and in my latest implementation, sub-objects) with information concerning a flight, its crew, its passengers (crew and passengers linked lists to their respective targets), crew contact information, flight details, other notes.
I’ve been wondering how to do this in Rails. In Zope 3, I have a base interface, IItineraryLeg and specify that an IItinerary implementation contains them:
class IItinerary(IItinerarySchema, IContainer): """ Base itinerary information. ItineraryLegs are associated with Itineraries to give trip details. """ contains(IItineraryLeg)
And in the implementation:
class Itinerary(BTreeContainer): implements(IItinerary, IItineraryContained) title = DublinCoreProperty('title') start_date = None end_date = None # Use ``Business`` as default, as is also specified in the interface trip_type = u"Business" notes = u"" @property def legs(self): return sorted(self.values(), key=startDate) class ItineraryLeg(Persistent, Contained): implements(IItineraryLeg, IItineraryLegContained) start_date = None end_date = None notes = u"" class ScheduledMeetingLeg(ItineraryLeg): """ 'Scheduled Meetings' add nothing to the IItinerayLeg schema, really """ implements(IScheduledMeetingLeg) class GroundItineraryLeg(ItineraryLeg): implements(IGroundItineraryLeg) transport_type = u"" service_name = u""
See, these two legs are very simple. In ActiveRecord or any object-relational mapper using Single Table Inheritance, this would have been easy. But it’s that Flight leg that gets messy. Very messy. By my understanding of Single Table Inheritance, the flight leg, ground leg, and scheduled meeting legs data would all be mashed up in one table. If I were designing tables in an RDBMS, I would never design that way – and I’m no genius Relational designer.
The point is, there are obviously situations where my expectations of objects AND relational storage, as mapped by ActiveRecord, don’t quite mesh up. I’m not belittling the system – I just realized that (a) I have no idea how I’d want to make these tables (especially since I have some equivalent of
has_one to tiny little objects, a transition made this week. Not just to tiny little objects, but a couple of them. Has two?), and (b) after I got them built, I have no idea how to map them into ActiveRecord. I may, however, be distracted things that I may or may not need, like new features Polymorphic Associations and Through Associations.
I guess, after thinking about it, I would write the three legs as separate tables/objects and write one legs action to combine them (in Rails)?
def legs (ground_itinerary_legs + scheduled_meetings).sort do |a,b| a.start_date <=> b.start_date end end
Well, it let me do something like that in the console, but not in my active record class.
Anyways, the point is that for many (not all, but many) tasks where a new “web application” is being created, a system like the ZODB is great. No translation. No having to bend your brain around RDBMS table creation and styles, and then back to objects, and to what can be done in between. Python data, through and through. The object database doesn’t work for every situation. But damn, it can be nice.