A Smalltalk ActiveRecord using Magritte, Seaside, and Glorp
I’ve been working on a side project that’s given me reason to want to use Glorp with Seaside. Having just mapped the sample blog written from my screencast into Glorp manually, by writing Glorp descriptors, I decided that I wanted something simpler, something more like Ruby on Rails, automatic persistence, with almost no configuration.
Having used Magritte for a while to describe my Seaside UI’s, I decided that those same descriptions contained all the necessary meta data to write Glorp descriptions from. Unlike the ActiveRecord in Rails, or the one Alan Knight is working on, I’m not using the database as the source of my metadata, I’m using the objects themselves with meta data from Magritte instead.
I sat down and started hacking out my own ActiveRecord implementation, which is really just a small framework that glues these three existing frameworks together for me and makes using Seaside against a Postgres database easy for me. Needless to say, knowledge of Magritte is a prerequisite for using this code.
I just open sourced the code I’ve been using on SqueakSource, just add this repository to Monticello to get a copy
MCHttpRepository
location: 'http://www.squeaksource.com/MagritteGlorp'
user: ''
password: ''
This is a first cut Alpha release, I make no guarantees, only the brave need attempt using it. To use it, simply requires the following.
subclass MGActiveRecord, this will be your root class, all your biz classes can descend from this.
MGActiveRecord subclass: #SBActiveRecord instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'SeasideBlog-Glorp'
subclass MGDescriptorSystem and override #rootClass, returning the class above, like so…
rootClass ^SBActiveRecord
and on the class side, override #defaultLogin…
defaultLogin
^(Login new)
database: PostgreSQLPlatform new;
username: 'xxxx';
password: 'xxxx';
connectString: '127.0.0.1_yourDatabaseName'
and optionally #initializeDatabase: if you want to insert some test data on creation of the schema…
initializeDatabase: aSession
aSession inUnitOfWorkDo: [aSession register: SBPost testPost]
Now, describe your classes with Magritte, here’s an example…
descriptionCategories
^ (MAMultipleOptionDescription selector: #categories label: 'Categories' priority: 1000)
options: [SBCategory findAll execute] dynamicallyRefreshed ;
classes:{SBCategory};
reference: SBCategory description;
componentClass: MACheckboxGroupComponent;
yourself
[SBCategory findAll execute] dynamicallyRefreshed is a shortcut for (MADynamicObject on:[SBCategory findAll execute]) that I implemented, since Magritte descriptions are cached, I do this to ensure each time a UI is rendered, a fresh query is done against the database.
You must manually create your database in Postgres. Once created, to create your schema, simply call #createSchema on your MGDescriptorSystem subclass like so…
SBGlorpDescriptions createSchema.
It will use your default connection to infer and create the schema necessary to support your object model. I’ve only used this on two schemas so far, but it seems to work OK, though I’m sure there must be bugs.
Now, to use this all in Seaside, subclass MGGlorpSession and override #glorpDescriptionClass like so…
glorpDescriptionClass
^SBGlorpDescriptions
Once done, from Seaside, I can execute queries like so…
blogPosts
"Grab published blog posts from database and return them in reverse order"
^(SBPost findAll)
limit: self numberOfPostsToShow;
where: [:each | each isPublished];
orderBy: [:each | each timestamp descending];
execute
And have the full power of Glorp available from two class methods, #find and #findAll which return Glorp queries wrapped in a decorator that allows you to call execute on them for the current Seaside context. All classes are commented. If anyone is brave enough to use this, I’d appreciate any feedback on any trouble you run into, or just general discussion about the approach. As I said before, I make no guarantees, but I’m using this code myself, and so far, it seems OK.
UPDATE: The tests included in the package are there to demonstrate a missing feature, automatic inheritance mapping. They do not need to be ran and have nothing to do with the base package. If createSchema works, you’re done, just start using it.
Comments(27)