Login

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 (automatically disabled after 1 year)

Damien Cassou 6519 days ago

Really really great. I'll reread your posts to install glorp. I really want to test this.

And #dynamicallyRefreshed is a good idea. You should put it in Magritte directly.

Ramon Leon 6519 days ago

Thanks, looking forward to some feedback, and yea, I guess that extension method does kind of belong in Magritte.

Giovanni Binda 6509 days ago

Following your progress with most interest. Building on magritte for persistence mechanism makes a lot of sense ! I suppose that the class SMMoneyDescription is a custom class for Magritte from you, because I have no such class in Magritte and you are using the S (for Sentora) prefix, am I wrong ? Monticello makes an argument to it, that I simply Proceed. I only loaded your package and will do the testing those next days. Good work ! ... many thanks from people that are skeptic of oodbms (for scalable project), when we have allready existing database such postgres !!!

Ramon Leon 6509 days ago

Yea, that's just a custom description I have for a custom money class I have, you can safely ignore any such warnings. Yes, S was for Sentorsa.

I'll try and keep squeak source up to date with my changes, I just uploaded current version with its dependencies. I'm still working with the old version of Glorp but hope to move to the new port when Todd has it working well enough.

This is of course a work in progress, so expect bugs and changes in design as I figure out the cleanest easiest way to do things. I'll add features as I run into the need for them in my current projects, and hopefully integrate with Todds schema migration code soon removing the need to drop and recreate your schema when you change your model.

Of course, I'm always interested in any comments or discussion on the package.

Sebastian 6457 days ago

I really like this. I have downloaded your DevImage. What would you recomend to read after trying to use Glorp + your active record? cheers. Sebastian

Sebastian 6451 days ago

Ramon, do you know how one can make to be unique two or more described instVars with this system? (keys of several fields)

Ramon Leon 6451 days ago

Unique constraints are something best handled directly in the database. There's no efficient way to do such in code.

Brian 6447 days ago

Having fun using your image and your ActiveRecord implementation. This in conjunction with Magritte is very effective. #createSchema works great, and can register new objects into the database using my subclass of DescriptorSystem class>>getSession. I've created a subclass of WASystemConfiguration and added #glorpUserName, et. al. But I can't seemt to make #findAll work. MGQueryRunner>>execute calls #execute: on the current session, which gives the following error: "WASession(Object)>>doesNotUnderstand: #execute:" Any thoughts on what I am doing wrong?

Ramon Leon 6447 days ago

Yup, you need to also subclass MGGlorpSession and use it in your seaside app. I should mention that #find and #findAll work on the current "Seaside" session, not Glorp session. I can see from the error, you've not setup a custom session object, you're using the default one.

Brian 6446 days ago

You're absolutely right. I had re-initialized the component class and forgotten to use the config app to use my MGGlorpSession subclass. Is there a way to set the session class in the class initialization?

Ramon Leon 6446 days ago

Sure, just set the preference...

(self registerAsApplication: #yourAppName)
    preferenceAt: #sessionClass put: YourSessionClass
Brian 6446 days ago

Ah, that's easy -- thanks. BTW, I'm loading your code into one of my images, but the GlorpPreload in MagritteGlorp doesn't seem to be loadable. Do you know if the glorppreload from squeaksource is compatible with the rest of the packages in MagritteGlorp?

Ramon Leon 6446 days ago

Yea, it'll work, I haven't really changed anything in it.

Brian 6432 days ago

BTW, your GlorpPreload loaded just fine with a new squeak system, so it must have something odd with my old system. Thanks again for the Glorp-Magritte unifying tool :)

Ramon Leon 6432 days ago

No problem, if you really use it and find it useful, I'd be really interested in more detailed feedback.

Mike Stramba 6395 days ago

Hi Ramon,

I'm downloading your devImage now, is this Glorp "framework" specific to only Postgres? ... or is there any config parameters that will let me use it with other DB's (Odbc, Mysql, Magma, OmniBase) ?

I've tried to get PostGres running on my XP box without success.
(priviliged/non user being the stumbling block to even start the sever)

Are you running Postgres on an XP box by any chance? .. If so any links / pointers to how to get it running / working versions would be great.

Mike

Ramon Leon 6395 days ago

Glorp will work with any database that a driver has been written for. It just so happens that in Squeak, that has only been done for postgres. In Visualworks Glorp works with many more databases.

As for getting Postgres up and running, I just do that standard install using the latest version from the webiste, and then change the authentication from md5 to password to avoid the complexity of getting cryptography working in Squeak. I've never has any trouble installing it or starting it up under XP, it just works.

With Gemstone's release of it's Seaside port in a GemstoneS/64 image, I'm likely to abandon Glorp and relational databases in general. I'd rather work with a Smalltalk image that just saves my objects directly and can scale to any level I need. If you're doing anything new, I'd look into that. Glorp is an object relational mapping layer, neither Magma nor OmniBase would have any use it of, they save objects directly.

Warren Wilkinson 6386 days ago

I've got your package installed from Monticello now, and I was wondering what it takes to get setup for the testcases to run? I've updated MTSetup >> getGlorpSession(class method) to have the right DB credentials, and similarly updated MTDescriptors >> defaultLogin(class method), and then ran

MTDescriptors createSchema

My database log file tells me that tables 'mtuser', 'mtgroup' and 'association_object' are created, and have no rows when I select * from them.

When I run the tests, my DB log shows: Could not receive data from client: Connection reset by peer unexpectedEOF on client connection (followed by DELETE statements which I guess come from the cleanup method)

This assert line doesn't pass: self assert: testUser associations notEmpty. (in MTAssociationTest >> testGroup )

Tables 'association_object' and 'mtgroup' are untouched, while 'mtuser' gets a blank user added (mtuserid is filled out, all other entries are blank).

Ramon Leon 6386 days ago

See the update at the end of the above article.

Conrad Taylor 6322 days ago

Hi Ramon, thanks for the implementation of ActiveRecord and I really appreciate the effort because I was looking to recreate many of the Rails examples in Seaside. After reading about the Gemstone/S 64 bit image, it seems that Gemostone is the way to go.

Ramon Leon 6322 days ago

I think Gemstone will be the way to go, for those who have the option. I think I'll be using GOODS for it's simplicity during development and then porting to Gemstone for production, once it's available.

Bill 6272 days ago

Could you elaborate a bit on why you would choose GOODS over Magma?

Thanks a ton for this most helpful site! Have you thought about doing an "Intro to Seaside" book, including basics of Magritte persistence? Squeak By Example such a book would be a tremendous pair!

Ramon Leon 6271 days ago

Mostly, because it's simpler and I'm only using it for development, not production. Magma would be much better for a production site because it has more advanced features like querying and indexing, a necessity. However, I still think Gemstone will be the best choice as soon as their GLASS appliance is ready, which should be very very soon. Gemstone also has the advantage of not being a SqueakVM, so it's likely much faster. The SqueakVM is designed to be open and extensible, more for research, not for speed.

Pablo Lalloni 6229 days ago

I'm trying to load MagritteGlorp-rjl.62 from SqueakSource but Monticello keeps me away complaining about a not existant required class SSSession which I don't have in my image... I've found out that MGGlorpSession inherits from SSSession and that SSSession is your personal WASession customization but I don't know/can't find which package should I load to get that. (I've seen it in your DevImage though).

Ramon Leon 6229 days ago

You can just change that superclass to WASession instead, or load the previous version that didn't depend upon it. If you really want it, you can get it from my image, but it's not necessary. I kind of backed off of generating Glorp mappings as I got further into a real project that required mappings complex enough to justify writing them by hand.

I'm going to tear the Magritte stuff out of it and use the package for Glorp and Seaside integration. I've come to prefer simple pragmas for meta data rather than Magritte. I put up that last version because it has some nifty schema migration automation in the Glorp descriptor class, even though I pretty much disabled the Magritte auto mappings.

Cedric 6198 days ago

Hi, I'm trying to use the package for a little application using postgres, magritte and glorp and I met a problem with the MAFileDescription. I have a description like this :

descriptionImage ^MAFileDescription new selectorAccessor: #image; kind: MAExternalFileModel; label: 'Image'; priority: 150; yourself

The idea is to have the image on the disk and the informations (path, filename, type, size, ...) on the database. With only this description and no modification, the generated form work to upload the file, but it try to save the MAExternalFileModel object as a byteArray directly on the database using "do:" method which is not implemented. I'm new to squeak and have some problem to see how to solve this properly. Any idea ?

Ramon Leon 6198 days ago

I suggest you write your Glorp mappings manually in your MGDescriptorSystem subclass. This package was meant to act as scaffolding to get you up and going until you can replace the simple automatic mappings with real hand written mappings that can take full advantage of Glorp's capabilities.

If you're not already, you'll want to hop on the Glorp mailing list and search the archives to see lot's of examples of similar mappings. Glorp is far more capable than the simple inferred mappings can handle.

about me|good books|popular posts|atom|rss