Archive for the tag 'Ruby'

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.

Composeable Views in Seaside

So I was talking to a Ruby on Rails buddy of mine today, and he asked me if Seaside could do views composed from other views like Rails. I thought I’d throw up a quick sample in case any one else wonders how this is done. It’s a very common thing to do, so common, I’d never considered writing about it until today.

It’s very basic, contains a header, footer, main control, and a result control to show how lists of items can each be their own views. MVC is so ordinary in Seaside I forget how novel it is to some other frameworks.

I start off creating a simple header control with one method…

Header>>renderContentOn: html
    html div: 'Header'

Then a footer control doing the same, though each could be much more complex…

Footer>>renderContentOn: html
    html div: 'Footer'

Ok, now I’ll create a class for showing the results of a query, with each result being a component of its own…

ClassResult>>modelClass: aModel
    modelClass := aModel

ClassResult>renderContentOn: html
    html heading: modelClass name level: 3.
    html div: ((modelClass allSubclasses
        collect: [:each | each name]) join: ‘, ‘)

I just printed the class name, and under it, all its subclasses names separated by a comma.

Now for the main root component that will be composed of these subcomponents…

ClassUsage class>>canBeRoot
    ^true

ClassUsage>>initialize
    "setup sub components in instance variables, for the results,
    I'm grabbing all the subclasses of WAComponent that have subclasses
    themselves and then sorting them by how many subclasses they have,
    and then using them as the model in a view, and storing the list of
    views for later rendering."

    super initialize.
    header := Header new.
    footer := Footer new.
    results := ((WAComponent allSubclasses
                reject: [:each | each allSubclasses isEmpty])
                    asSortedCollection: [:a :b |
                        a allSubclasses size > b allSubclasses size])
                    collect: [:each | ClassResult new modelClass: each]

ClassUsage>>children
    “return all sub components as a list”

    ^{ header. footer } , results asOrderedCollection

ClassUsage>>renderContentOn: html
    html render: header.
    (html div)
        class: #body;
        with: [results do: [:each | html render: each]].
    html render: footer

That’s about it. I setup the subcomponents in the initialize, and then by explicitly not calling super renderContentOn, I then have the option of when and where to render subcomponents.

Mapping Seaside Blog to PostgreSQL with Glorp

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.

05 Dec 2006 > Squeak Image Updated

Just a quick notification that I’ve updated my squeak image. I do this occasionally to keep my base up to date with the latest and greatest of the frameworks I use.

In this update I loaded Albatross, a great new testing framework for Seaside that lets you write SUnit Tests against Seaside apps like this little test for the blog I built in a previous post…

testAddPost
    | body |
    body := Random sentenceMaxWords: 200.
    browser
        click: 'New Post';
        enter: Random sentence for: 'Title:';
        enter: body for: 'Body:';
        click: 'Save'.
    self assert: (browser textExists: body)

This test automates the browser just like Ruby’s Watir or Selenium so all your Ajax code will be tested, but IMHO, has a much nicer test API, thanks to Smalltalk.

I also fixed a bug in the refactoring browser in my image that was causing a DNU on renaming a method. I fixed up my two refactoring additions to the browser for creating accessors with Magritte descriptors on the class side. The collaboration accessor now produces simpler and more useable code, I removed some unnecessary experimental code I was generating and forgot to take out. Made a couple small changes to my garbageCollect method to be compatible with 3.9, and finally, I updated all libraries to the current versions I’m using in my own development and production images.

I’m still using a 3.8.1 image, but I’ll probably be upgrading to 3.9 soon, I’ve got a working image that contains my enhancements, which took a bit of hacking and merging to get going.

I use a custom port of Monticello and rely heavily on its dependency mechanism to properly load dependent packages. The default mechanism doesn’t work correctly IMHO, and I haven’t yet had the time to tackle learning Monticello configurations, nor have I found any good documentation on it. The version of Monticello in 3.9 is different from 3.8, and not as good IMHO. I can no longer see my dependencies because the packages no long have tree controls listing the dependencies. The tool bar is messed up and gets huge when you maximize a window, a side effect of some changes in ToolBuilder I think, but I could be wrong.

The KeyBinder can no longer be configured to use right click to edit key bindings because right clicking now brings up some other menu globally. Now I have to deal with the KeyBinder popping up every time I accidentally hover over it, an annoyance but something I can live with.

I hacked through a broken installer and got Andreas’s Win32 Native fonts working, which to me, is the one thing that really makes Squeak feel right and usable. A couple hacks to SystemWindow to get the border width down to a reasonable 2px and a few more to fix those annoying floating corner grips which get out of whack when you mess with the border width.

All in all, I’m a bit annoyed by 3.9, but I want to be able to use Traits, Göran’s namespaces, and Pragmas, so I’m going to have to bit the bullet and upgrade soon. When I do, and have a good working image done my style, I’ll post it here.

« Previous PageNext Page »