On Smalltalk

thoughts on Smalltalk and programming in general…
  • Home
  • About
  • Good Books
  • My Squeak Image
  • Popular Posts

Mapping Seaside Blog to PostgreSQL with Glorp

By Ramon Leon - December 28, 2006 under Databases, Programming, Seaside, Smalltalk

In a previous post I created a simple blog containing two business objects, BlogPost and BlogComment. I implemented persistence for that blog in the simplest manner possible, I put an OrderedCollection of BlogPosts in an accessor called repository on the BlogPost class…

BlogPost class>>repository
    ^repository ifNil: [repository := OrderedCollection new]

Allowing the class itself to serve as the database for all its instances. This is great for development speed, and gives you the ability to play and try out many different things until your object model settles down and you figure out what your model is really going to look like. Using the image as an object database allows you to remain nimble as you learn more about the problem and change your mind as you gain new insight into the domain model. Put off transactional persistence as long as you can, it’s likely you can build almost your entire program without actually leaving the image or thinking about a database.

Let’s assume the we’re at that point now, and we’re going to map BlogPost and BlogComment into a real relational database as if this were a production app. I’m going to use PostgreSQL and Glorp, my first time using either, and see what it takes. I’m not claiming anything I do here is best practice, only sharing my experiences as I learn.

For the record, Alan Knight, the author of Glorp, has a working implementation of ActiveRecord, somewhat like Ruby on Rails, that would allow me to avoid all this meta-data, but as far as I know, it hasn’t been ported to Squeak yet, and is still in Alpha; I hope he’s done soon.

Until ActiveRecord is available, I’ll have to write the meta-data the old fashioned way. After reading the Glorp Tutorial, I see I need to subclass DescriptorSystem and create a class containing all the meta data for classes, tables, and the mappings between them. I call it SBGlorpDescriptions.

It seems I need to override two methods to tell Glorp about the tables and classes…

allTableNames
    ^#(POST COMMENT)

constructAllClasses
    ^(super constructAllClasses)
        add: BlogPost;
        add: BlogComment;
        yourself

Then I need to write methods for the table meta-data. Glorp will create the actual schema in PostgreSQL for me from this metadata, as well as use it to map between tables and classes.

tableForPOST: aTable
    (aTable createFieldNamed: #postId type: platform sequence) bePrimaryKey.
    aTable
        createFieldNamed: #title type: (platform varchar: 100);
        createFieldNamed: #body type: (platform text)

tableForCOMMENT: aTable
    | postId |
    (aTable createFieldNamed: #commentId type: platform sequence) bePrimaryKey.
    postId := aTable createFieldNamed: #postId type: platform int4.
    aTable
        createFieldNamed: #name type: (platform varchar: 100);
        createFieldNamed: #comment type: platform text;
        addForeignKeyFrom: postId
            to: ((self tableNamed: #POST) fieldNamed: 'postId')

I also need to write methods for the class model which gives me a chance to control how Glorp loads the classes. For example, I may want to force Glorp to use accessors rather than direct instance variable access. Here I simply tell Glorp that a Post has a collection of Comments.

classModelForPost: aModel
    aModel
        newAttributeNamed: #persistentId;
        newAttributeNamed: #title;
        newAttributeNamed: #body;
        newAttributeNamed: #comments collectionOf: BlogComment

classModelForComment: aModel
    aModel
        newAttributeNamed: #persistentId;
        newAttributeNamed: #name;
        newAttributeNamed: #comment

Now Glorp has a meta-data model for both classes and tables. Now, given the table meta-data and the class meta-data, I create a map between them for each class on each class…

BlogPost class>>glorpSetupDescriptor: aDescriptor forSystem: aDescriptorSystem
    | table |
    table := aDescriptorSystem tableNamed: #POST.
    aDescriptor table: table.
    (aDescriptor newMapping: DirectMapping) from: #persistentId
        to: (table fieldNamed: #postId).
    (aDescriptor newMapping: DirectMapping) from: #title
        to: (table fieldNamed: #title).
    (aDescriptor newMapping: DirectMapping) from: #body
        to: (table fieldNamed: #body).
    (aDescriptor newMapping: OneToManyMapping)
        attributeName: #comments;
        referenceClass: BlogComment;
        collectionType: OrderedCollection

BlogComment class>>glorpSetupDescriptor: aDescriptor forSystem: aDescriptorSystem
    | table |
    table := aDescriptorSystem tableNamed: #COMMENT.
    aDescriptor
        table: table;
        addMapping: (DirectMapping from: #persistentId to: (table fieldNamed: #commentId));
        addMapping: (DirectMapping from: #name to: (table fieldNamed: #name));
        addMapping: (DirectMapping from: #comment to: (table fieldNamed: #comment))

I’m not using the real power of Glorp here, because my models mostly match, however, were I programming against an existing legacy schema, I might start to appreciate all this meta-data a bit more, knowing I could map any shape class to any shape table. This is where Glorp truly shines over Rail’s ActiveRecord, which is an extremely simple and not so flexible object mapping system. Everything I’m doing here, Rails could do easily, but Glorp is much more powerful and can be used against legacy schemas that look nothing like the class model.

Rails does one thing, one way, very well, with inferred meta-data and virtually no configuration. Glorp can do anything, any way you like it, but requires explicit meta-data, with a bit of configuration. Alan’s about to give us the best of both worlds, by inferring the basic meta-data like Rails does, but allowing you to mix and match it with custom meta-data for more complex mappings, I can’t wait, and I’m glad Rails has pushed Alan in this direction. Glorp will be much easier to use for greenfield applications if it infers the meta-data with reasonable defaults, and Rails has proven how popular this approach is with developers.

OK, now that all the meta-data and mappings are created, I need to have Glorp create the schema for me. I fire up a Workspace and create a Glorp session using my new SBGlorpDescriptions class…

login := (Login new)
    database: PostgreSQLPlatform new;
    username: 'xxxxxx';
    password: 'xxxxxx';
    connectString: '127.0.0.1_seasideBlog'.

accessor := DatabaseAccessor forLogin: login.
accessor login.
session := GlorpSession new.
session system: (SBGlorpDescriptions forPlatform: login database).
session accessor: accessor.

Then I tell Glorp to create my schema…

session inTransactionDo:
    [session system allTables
        do: [:each | accessor createTable: each ifError: [:error | error inspect]]].

So far so good, everything works, time to change the blog and query through Glorp. On my Seaside session, I created a few delegation methods to pass through to the Glorp session which it contains…

database
	^database ifNil: [database := self buildDbSession]

buildDbSession
	| login accessor |
	login := (Login new)
				database: PostgreSQLPlatform new;
				username: ‘xxx’;
				password: ‘xxx’;
				connectString: ‘localhost_seasideBlog’.
	accessor := DatabaseAccessor forLogin: login.
	accessor login.
	^(GlorpSession new)
		system: (SBGlorpDescriptions forPlatform: login database);
		accessor: accessor;
		yourself

unregistered
    super unregistered.
    self database accessor logout

commit: aBlock
    ^self database inUnitOfWorkDo: aBlock

execute: aQuery
    ^self database execute: aQuery 

register: anObject
    ^self database register: anObject

And then make a few changes to the main blog component…

blogPosts
    ^BlogPost repository reversed

Becomes…

blogPosts
    ^self session execute:
        ((SimpleQuery returningManyOf: BlogPost limit: 10)
            orderBy: [:each | each persistentId descending];
            yourself)

And I modify #newPost and #addCommentTo: to look like so…

newPost
    | post |
    post := self call: ((BlogPost new asComponent)
                        addValidatedForm;
                        yourself).
    post ifNotNil: [self session commit: [self session register: post]]

addCommentTo: aPost
    | comment |
    comment := self call: ((BlogComment new asComponent)
                        addValidatedForm;
                        yourself).
    comment ifNotNil:
            [self session commit:
                    [self session register: aPost.
                    aPost addComment: comment]]

And that’s it, the sample blog now works against PostgreSQL. In all honesty, it was a bit of work, more than I expected, but given Glorp’s capabilities, I understand the need for so much meta-data. I’ll sure be glad when Alan finishes his ActiveRecord implementation because most of the time, that’s all I need.

In the mean time, I’ll have to write up a quick code generator to generate most of this meta-data for me directly from the classes using reflection. I’ve got a bigger project coming up where I plan to use Glorp, and there’s no way I’m writing all this by hand again.

Tags: Programming, Ruby, Seaside, Smalltalk, Sql

Related posts
    at: "Popular Posts";
    at: "Squeak Smalltalk and Databases";
    at: "Making a Connection Pool for Glorp in Seaside";

17 Comments so far

  1. Martial on January 6th, 2007

    Hi,

    Your tutorial is a pretty good work. It’s exactly what I was looking for. But I don’t understand the last part. I mean it looks like there is no definition of the database message for the session. I guess it must reach aPostgreSQLPlatform object but I don’t know how to do this. I’m blocked at the line ‘So far so good…’ and after that I am a bit lost. I made a new WASession subclass and I added the following methods but it doesn’t work…

    Martial

  2. Ramon Leon on January 6th, 2007

    OK, I added the missing methods for session. I left them out because I’m not sure I want to keep one Glorp session per one Seaside session, I have to do a bit of testing before I decide what’s best practice there.

  3. Martial on January 7th, 2007

    Thanks! It works now. I agree there must be a better way. I thought about create a BlogDatabase class to manage the session connection because aWASession object is only seen from the BlogView thus I guess it’s not a pure MVC architecture. In such a class, I copy the methods you create for the session and so, I can use the message addCondition:labelled used by BlogPost class>>descriptionTitle as you show it in your previous video tutorial.
    I write this as follows:

    addCondition: [:value | (BlogDatabase
    commit: [BlogDatabase database
    readOneOf: BlogPost
    where: [:each | each title = value]]) isNil]
    labelled: ‘Already here’;

    Finally I notice you forgot to mention the creation of the i-var persistentId in BlogPost and BlogComment and their accessor messages.

  4. Ramon Leon on January 7th, 2007

    Actually, the session is available from anywhere as a singleton, so BlogDatabase database would be the same as saying WACurrentSession value database.

    However, as I said, I’m still working on how I’d like to do it. As for the persistentId, I’ll be removing that, Glorp supports pseudo variables and doesn’t really require it. I’d also implement that rule with a database constraint rather than a query, so I wouldn’t use it anyway.

    I’m currently busy creating my own version of ActiveRecord that creates all the mappings automatically from the meta data contained within the Magritte descriptions. I have it working and have the blog mapped to PostgreSQL with zero configuration. I’ll figure out how I want to handle the session along with this and open source the code shortly after I do some more testing and feel it’s stable enough.

  5. Martial on January 7th, 2007

    Oops! I have more to learn about seaside and the session…
    An ActiveRecord should be far better. If you need help for testing your classes, tell me!

    Regards,

    Martial

  6. Ramon Leon on January 7th, 2007

    It’s sort of like ActiveRecord, except the database is never read, only written, I consider the Smalltalk objects the real schema and the database as totally subservient to them.

  7. Valy on February 20th, 2007

    Hi,

    I am trying to get started with Seaside ( I am new to Squeak and Smalltalk ), and I am getting stuck at several points.

    One is the access to Postgresql ( which I will need for a new project ). My confusion begins with the versions and VM images, 3.9 for example lists Glorp in the packages list but fails installation ( not supported ), the link for the Squeak port found here http://www.glorp.org/versions.html is dead.

    Is there any website/blog with an image that includes the necessary ‘components’ (Glorp, script.aculo.us, Seaside,etc) ?

    Valy

  8. Ramon Leon on February 21st, 2007

    I will see about putting up a ready to run Glorp image tonight.

  9. BradleyWinters on April 1st, 2007

    Nice. I absolutely agree with you.
    Keep up the nice work. I’ll be back for more!

  10. LavernRengerson on April 2nd, 2007

    Oh I like that! Nice post.
    This place is alright…I’ll be back for sure.

  11. And on October 9th, 2007

    I wrote a small app with Seaside using Squeak just to get the “feel” of it, but I am new to Smalltalk and there are two things that prevents me to begin using it for “real” projects at my work and it will be very helpful to get some clarifications and/or advice:

    1. Understanding how to use Glorp sessions from Seaside.
    At this moment I just created a singleton to get the session, is that the right way? I mean I have no idea how multiple requests are handled by Seaside and if that approach works for multiple queries from different users,etc.

    2. Deployment
    My web applications are used internally and for some clients, there is no need for huge scaling, a few dozen concurrent users tops. Will only one Squeak instance be sufficient? If not, should I follow the posts made about the deployment architecture used in DabbleDB and used that kind of setup with Apache,etc?

  12. Ramon Leon on October 9th, 2007

    See my MagritteGlorp package on SqueakSource, it has the necessary code to integrate Seaside with Glorp including a custom seaside session class, a connection pool, and a few other things. You don’t actually need to use Magritte, you can write your Glorp descriptor normally and use the Seaside Glorp integration.

    For that small a load, a single squeak image will probably work just fine.

  13. And on October 9th, 2007

    Thanks! I will study that code and try to ‘get it’.

    I downloaded your image…wow! What a difference from the default Squeak image, fantastic!

  14. Ramon Leon on October 9th, 2007

    I have a much better one now, a great new UI package was released that really cleans Squeak up. I’ll update my image here soon so anyone can download it. Email me or leave a comment here if you have any questions about the package.

  15. Mikey on November 28th, 2007

    Yeah, this is a very very cool blog. ;-)
    I just added you to my favorites.

    Thanx,
    Mikey

  16. Carlos on March 8th, 2008

    Ramon, very nice post. I found a demo in the Cincom site that illustrates this article in VW steb by step. Seems that there is some UI code, maybe built by UIPainter. I am interested in how a glorp-based model i editable-viewable with a classic GUI. Regards, Carlos

  17. Ramon Leon on March 8th, 2008

    I don’t really know anything about Visual Works, I’m just a Squeaker, sorry.

Posting your comment.


  • Sponsors

  • Tags

    Databases General Linux Lisp Magritte Performance Profiling Programming Ruby Seaside Smalltalk Sql Squeak Updates
  • Categories

    • .Net (5)
    • Databases (9)
    • General (5)
    • Linux (2)
    • Lisp (3)
    • Magritte (2)
    • Programming (62)
    • Ruby (6)
    • Seaside (42)
    • Smalltalk (72)
    • Sql (2)
    • Stuff I Just Like (6)
    • Updates (7)
  • Blogs

    • (gem)Stone Soup
    • Avi Bryant
    • Boris Popov
    • defmacro
    • Giles Bowkett
    • Goran Krampe
    • James Robertson
    • Lukas Renggli
    • Martin Fowler
    • Paul Graham
    • Ralph Johnson
    • Randal Schwartz
    • Vassili Bykov
    • Weekly Squeak
  • Favorite Tools

    • Apache
    • Cygwin
    • FireFox
    • Scriptaculous
    • Seaside
    • Squeak
    • Squeak Dev Image
    • Ubuntu Linux
    • WordPress
  • Meta

    • Log in
    • Entries RSS
    • Comments RSS
    • WordPress.org

Copyright © 2008 On Smalltalk