Magma SeasideHelper ToDo List Tutorial
Last updated at 4:34 pm UTC on 1 April 2009
Intended as a supplement to the todo-list seaside tutorial (Chapter8)
Sample code with magma examples is available at Installer ss project:'ToDoTutorial'; install: 'STTutTodoApp'.
Saving all data in an object-oriented database: Magma
Another option to make your data persistent is to use an object-oriented database like Magma.
Installing the Magma Database
Magma is written entirely in Squeak, so there is no need to install a separate server application. Both Magma and Seaside can run in the same image. To install (into a 3.10 based image) just execute the following. (It may take a while, but it is an unattended script so read on.)
Installer squeaksource project: 'Installer'; install: 'Installer-Core'.
install: 'Magma seasideHelper';
Configuring your application to use Magma
In the configuration for the 'todo' application you will need to add WAMagmaConfiguration to the configuration "ancestry". Select it from the drop down menu and click "Add". As soon as you do this, a new group of configuration options will appear.
This configuration allows you to choose the database location, either a local file system directory or a remote host and port. It also provides a choice of "Magma Helper Class".
Each choice reveals a detailed explanation as to how the helper provides database session to seaside. Here is an example:
WAMagmaSoloAuto is specifically designed as the simplest and most transparent of the Magma-Seaside helper options for situations in which there is only one Seaside server. A single shared Magma session and object model. Data commiting is automatic
The recomended choice is WAMagmaSoloAuto so...
The default configuration is ready to use!
Magma is very easy to use with seaside. To use the database session helper described above, we simply send #magma to the current session. Typically the code self session magma will work within all seaside components. In the example below 'self magma' is returning the helper.
To use Magma in this application following the pattern of the other database examples, we need to create an interfacing class to which we can add our specialized querying methods. Our StMagmaDatabase subclasses WADictionaryRoot, a class provided with 'magma seasideHelper' as a persistable dictionary for you to subclass for as a root object for your persisted data models.
MagmaDictionaryRoot subclass: #StMagmaDatabase
self db: (self magma rootAs: StMagmaDatabase)
The tutorial application accesses the database via the #db accessor. We have defined this using the #rootAs: helper method, which will do everything we need to initialize the database, and to provide us the interface that we want to it. This #rootAs: method returns an instance of StMagmaDatabase which is persisted, any objects reachable from this root are also automatically persisted when a database commit transaction occurs. With the "seaside Helper" framework database commits are automatically applied.
Initializing the Database
To initialize our users collection we use the #initialize method of our database class. We also need a method to return the users collection from the database, to add a new user, and to find a user.
| users |
users := OrderedCollection new.
self at: #users put: users.
^ self at: #users
^ self users add: newUser
^ self users
detect: [:each | each email = anEmail]
ifNone:[ nil ]
Notice that we have not needed any database specific code!
Storing Lots of Data
If our user-base is going to grow to a significant size it makes sense to re-implement the above using some database-like features, so let us introduce a MagmaCollection, a collection class designed to enable concurrent access to large indexed data sets. When persisted in a Magma database MagmaCollections no longer have to retain all of their contents in memory.
| users |
users := MagmaCollection new.
users addIndex: (MaSearchStringIndex attribute: #email) keySize: 128; beAscii; yourself
self at: #users put: users
^ (self users where: [ :each | each email equals: anEmail ] ) firstOrNil
This index will have an internal resolution of 27 meaningful characters, enough for probably 99% of e-mail
addresses. Read the magma indexes section of the MagmaCollection for a more in deep explanation of index usage and considerations. Also, you can read this thread for a discussion over this code fragment.
"But", I hear you exclaim, "my database is populated with users, I cant change it now!"
In the sample code the two examples above are defined as two different database classes, StMagmaDatabase, and StMagmaCollectionDatabase, having the same #modelClass (see class side).
To change your application to use the MagmaCollection implementation change the StSession-#initialize method.
self db: (self magma rootAs: StMagmaCollectionDatabase)
The fact that these two classes have the same #modelClass means that they are both persisted in the same slot in the database root dictionary. If #rootAs: retrieves a different class to that requested it performs an automatic migration.
It's as simple as that... or is it? Well almost, the following code is needed to assist the migration.
self users addAll: old users asOrderedCollection.
To use your database in the Smalltalk workspace, simply send the application name to WAMagma e.g.
WAMagma todo explore
WAMagma todo commit: [ ... ]
Note that within commit: blocks WACurrentSession value is valid if your code depends upon having a seaside session with the correct application configuration.