Login

SandstoneDb GOODS adaptor

SandstoneDb was written mostly as a rails'ish API for a simple object database for use in small office and prototype applications (plus I needed a db for this blog). Which object database wasn't really important to me at the time, it was the API that I wanted, so I made the actual object store backing it pluggable and initially wrote two different store adaptors for it. The first was a memory store which was little more than a dictionary of dictionaries against which I wrote all the unit tests. The second was a prevayler style file based store that used SmartRefStream serialization and loaded everything from disk on startup; this provided a crash proof Squeak images which wouldn't lose data.

I figured eventually, for fun I might get around to writing adaptors for some of the other object database back-ends that are in use: GOODS and Omnibase. I never really got around to it; however, Nico Schwarz has written a GOODS adaptor for SandstoneDb. This will let you hook up multiple squeak images to a single store and should scale better than the file store that SandstoneDb defaults to.

Go check it out and let him know what you think of it. This is just the kind of project that'll help programmers new to Seaside get going and get accustomed to using an object database rather than a relational one. It looks like his first blog post as well, so swing by and leave a comment to encourage more posts, we need more bloggers spreading the word!

On Twitter

OK, so I'm finally going to try out this Twitter thing. I still don't see why everyone is so obsessed about it but what the heck, they are, so maybe it is cool. Maybe some micro blogging will get me back in the mood to do some real blogging. If any of you guys are twitterers, come follow me so I have someone to tweet to.

Started working on a GLASS project, so maybe I'll tweet about that, and eventually blog about it as well (so far it fracking rocks).

Stateless Sitemap in Seaside

Originally I generated the sitemap for onsmalltalk as a file on disk and let Apache serve it up. There's nothing wrong with this approach but it'd be cooler if I had Seaside generate and render it on demand and serve as a good excuse to talk about serving up content statelessly in Seaside.

Seaside is a session based web framework, but there's nothing really session specific about a sitemap and I really don't want a new session created when a request for a sitemap is made. There's a lot of overhead in doing that and sometimes you just want to serve up stuff statelessly. When a request comes in, the application mounted on the base URL handles the request by plucking the session id out of the cookie or the URL and either creates a new session or finds the existing one needed to handle the current request. Once found, the request is pumped through the current session which runs through a similar procedure looking for a continuation to invoke.

Since I want to avoid all that and just handle the request at the application level I'll override #handleRequest: in my custom WAApplication subclass, check the URL of the current request and either render the sitemap and end the request by immediately returning a response, or allow processing to continue normally into the session lookup done by the call to super.

handleRequest: aRequest 
    (aRequest url endsWith: '/sitemap.xml') ifTrue: 
        [ ^WAResponse new
              beXML;
              cacheFor: 1 hour;
              contents: (self siteMapFrom: aRequest) asString readStream;
              yourself ]
    ^super handleRequest: aRequest

siteMapFrom: aRequest 
    ^ (SBSiteMapGenerator blogRoot: ('http://{1}/' format: {  (aRequest host)  })) 
        generateFromItems: {  (SBPost new)  } , (SBBlog onSmalltalk publicPosts) , SBTag findAll.

If you have things in your application that can be done statelessly, this is a good place to hook into the framework and take care of that stuff at the application level. Sometimes you don't need all that fancy Seaside stuff and you just want to work directly with HTTP requests and responses.

Two small methods and the sitemap is now generated dynamically, and statelessly directly from Seaside, removing the need to manually invoke the sitemap generation as I had previously been doing to the file system.

Oh, one small extension that I've put on WAResponse and use occasionally...

cacheFor: aDuration 
    self headerAt: 'Expires' put: (TimeStamp now + aDuration) httpFormat

1 February 2009 > Squeak Image Updated... To Pharo!

Just a quick notification that I updated my pharo image.

It's based on Damien Cassou's latest Pharo Dev Image. I switched to Pharo a couple of months ago and so far it rocks. Best Squeak image I've had to date and it's really nice to see the cleanup and UI work they're doing that Squeak was so desperately in need of.

Keep up the great work guys! Pharo is coming along nicely and looks more and more professional every day.

Generating a Site Map for OnSmalltalk

OK, so any website that wants to be indexed well by Google (and those other guys) should be generating an XML sitemap for the search engines to index. A sitemap is nothing fancy, though it can get more complex if you choose to take advantage of more of its features; I prefer a simple version with everything marked as updated weekly.

I also prefer to invoke the generation of the sitemap manually and to generate it as a static file that Apache can serve up rather than having Seaside build one dynamically (though I'll probably change my mind later). My blog has an admin panel with a menu option to generate site map which invokes...

generateSiteMap
    | siteMap |
    siteMap := SBSiteMapGenerator blogRoot: 'http://onsmalltalk.com/'.
    siteMap generateFromItems: {  (SBPost new)  } , 
        (SBPost findAll: [ :e | e isPublished ]) , SBTag findAll.
    (siteMap pingGoogleWithMap: 'http://onsmalltalk.com/sitemap.xml') 
        ifTrue: [ self message: 'Map generated and Google notified successfully.' ]
        ifFalse: [ self message: 'Map generated but Google notification failed.' ]

The first item in the list, the empty new post, creates and item without a slug which represents the root of the site. I don't bother pinging the other search engines, the vast majority of my traffic comes from Google, the rest will find me eventually. So let's run through the generation of this sitemap, it's only a few methods. The class declaration...

Object subclass: #SBSiteMapGenerator
    instanceVariableNames: 'document root blogRoot'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'OnSmalltalkBlog-Config'

A couple of accessors for the blog root...

blogRoot
    ^ blogRoot

blogRoot: aRoot
    blogRoot := aRoot

And a constructor that uses it...

blogRoot: aRootUrl 
    ^ self new
        blogRoot: aRootUrl;
        yourself

Since I'm going to write the sitemap to disk, I'll need to know where to put it, and I'll want it configurable...

siteMapPath
    ^ (FileDirectory
        on: (SSConfig at: #blogWebRoot default: FileDirectory default fullName))
        fullNameFor: 'sitemap.xml'

Now a method to generate the document, add the items to it, and write the file to disk...

generateFromItems: someItems
    document := XMLDocument new
        version: '1.0';
        encoding: 'UTF-8';
        yourself.
    root := (XMLElement named: 'urlset').
    root attributeAt: 'xmlns' put: 'http://www.sitemaps.org/schemas/sitemap/0.9'.
    root attributeAt: 'xmlns:xsi' put: 'http://www.w3.org/2001/XMLSchema-instance'.
    root attributeAt: 'xsi:schemaLocation' put: 'http://www.sitemaps.org/schemas/sitemap/0.9 
     http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd'.
    document addElement: root.
    someItems do: [ :e | self addItem: e ].
    FileStream forceNewfileNamed: self siteMapPath
        do: [ :f | f nextPutAll: document asString ]

For each item, I'll want to generate an entry. The item is expected to respond to two methods, #createdOn, and #slug. All of my posts and tags respond to these so I can just toss then into a single list of items...

addItem: anItem
    | url location lastModification isoString changeFreq |
    url := root addElement: (XMLElement named: 'url').
    location := url addElement: (XMLElement named: 'loc').
    location addContent: (XMLStringNode string: self blogRoot , anItem slug).
    changeFreq := url addElement: (XMLElement named: 'changefreq').
    changeFreq addContent: (XMLStringNode string: 'weekly').
    lastModification := url addElement: (XMLElement named: 'lastmod').
    isoString := String streamContents: 
        [ :stream | anItem updatedOn printOn: stream withLeadingSpace: false ].
    lastModification addContent: (XMLStringNode string: isoString).

With the file generated, we're ready to let Google know we've updated it...

pingGoogleWithMap: aMap 
    ^ (WAUrl new
        hostname: 'www.google.com';
        addToPath: 'webmasters/tools/ping';
        addParameter: 'sitemap' value: aMap;
        yourself) asString asUrl retrieveContents content 
        includesSubString: 'Sitemap Notification Received'

And that's it, Google knows the site's been changed and all of its valid URLs, and most of the time, is crawling the site within minutes, if not instantly.

I've got to say, I'm not missing Wordpress at all; it's a lot more fun just building your own blog.

<< 1 2 3 4 5 6 7 8 9 10 >>
about me|my pharo image|good books|popular posts|atom|rss