I really like Smalltalk’s ifNil: and ifNotNil:, and up to now, I could not use these in Ruby. Fortunately, Bob Hutchison came to the rescue with A Little Unnecessary Smalltalk Envy.

I immediately copied that to XLsuite and wrote a couple of tests. Here is sample of what the code feels like:


1 >> Party.find_by_email_address("sam@gamgee.net").if_not_nil do |party|
2 ?> puts "Found Sam!"
3 >> end
4 => nil

Oh well, no Sam in my database. The code is here:

test/unit/smalltalk_test.rb

1 require File.dirname(FILE) + "/../test_helper"
2
3 class SmalltalkTest < Test::Unit::TestCase
4 setup do
5 block</span> = <span class="co">Proc</span>.new { <span class="pc">true</span> } <span class="no"> 6</span> <span class="r">end</span> <span class="no"> 7</span> <span class="no"> 8</span> context <span class="s"><span class="dl">&quot;</span><span class="k">The nil object</span><span class="dl">&quot;</span></span> <span class="r">do</span> <span class="no"> 9</span> should <span class="s"><span class="dl">&quot;</span><span class="k">yield when calling #if_nil on it</span><span class="dl">&quot;</span></span> <span class="r">do</span> <span class="no"><strong>10</strong></span> assert <span class="pc">nil</span>.if_nil(&amp;<span class="iv">block)
11 end
12
13 should "not yield when calling #if_not_nil on it" do
14 deny nil.if_not_nil(&block</span>) <span class="no"><strong>15</strong></span> <span class="r">end</span> <span class="no">16</span> <span class="r">end</span> <span class="no">17</span> <span class="no">18</span> context <span class="s"><span class="dl">&quot;</span><span class="k">A non nil object</span><span class="dl">&quot;</span></span> <span class="r">do</span> <span class="no">19</span> should <span class="s"><span class="dl">&quot;</span><span class="k">not yield when calling #if_nil on it</span><span class="dl">&quot;</span></span> <span class="r">do</span> <span class="no"><strong>20</strong></span> deny <span class="s"><span class="dl">&quot;</span><span class="dl">&quot;</span></span>.if_nil(&amp;<span class="iv">block)
21 end
22
23 should "yield when calling #if_not_nil on it" do
24 assert "".if_not_nil(&@block)
25 end
26
27 should "pass itself to #if_not_nil" do
28 obj = "abc"
29 assert_same obj, obj.if_not_nil {|o| o}
30 end
31 end
32 end

lib/smalltalk.rb

1 # Copied and adapted from
2 # http://recursive.ca/hutch/2007/11/22/a-little-unnecessary-smalltalk-envy/
3 # Bob Huntchison
4 class Object
5 # yield self when it is non nil.
6 def if_not_nil(&block)
7 yield(self) if block
8 end
9
10 # yield to the block if self is nil
11 def if_nil(&block)
12 end
13 end
14
15 class NilClass
16 # yield self when it is non nil.
17 def if_not_nil(&block)
18 end
19
20 # yield to the block if self is nil
21 def if_nil(&block)
22 yield if block
23 end
24 end

I have worked some more on my home budget planner application. The current version is implemented on Seaside, and it’s doing great. I haven’t had as much time as I wanted, but it’s coming along nicely. I have some formal training in accounting practices, and I treat my house as being a business: we have income and expense accounts, equity for ourselves, and assets (our bank account) as well as liability accounts (credit cards and bank loans).



BudgetApp’s administration tab. Click for larger view.

This is the administration tab. This is where you add and change accounts. Not much more to show here.



BudgetApp’s budget tab. Click for larger view.

The budget tab. This is where you actually set your budget targets for the month. Historical data is kept around, and you can immediately see when your budget is under the actual value.



BudgetApp’s real tab. Click for larger view.

This is the least polished of the tabs yet, and ironically, this is where most of the work is going to be done. I’ll need to use the application for a bit before I can determine the exact interface I want. I’m thinking of having a couple of panels that will allow the user to say what kind of transaction occurred: paid, bought, reimbursed, transferred, etc.

That’s the state of affairs at revision 9 on the Monticello repository.

Differences with Rails

I haven’t actually started doing any work on the Rails side of things, but there is one thing I did notice: I find it easier to segregate my work in change sets in the file world versus when working in the image-world. For example, revision 8 includes changes to a couple of classes, and none are related: stylistic changes in the budget and admin tabs, plus my initial stab at the real tab. Had I been using Rails, I would have committed a couple of files here and there multiple times, and that would be it.

I am aware of Monticello’s “add to current change set” and “remove from current change set”, but have not dared using them yet. I’m not exactly sure what these options will do, and most importantly, I am afraid of losing work. That probably won’t happen, but there’s this nagging feeling deep down…

Anyway, next step is to generate real transactions from the real tab. More on this next week !

UPDATE 2007-10-23: Changed repository URLs.

Me and my wife have a funny relationship with money: it never stays in our hands. I would guess the majority of people have the same problem. Back in the days, I started by making ourselves a budget using OpenOffice.org spreadsheet. That was fine, until I realized my wife was always changing the numbers. She used it to record a budget of sorts, but when she actually paid the utilities, she’d change the numbers.

At about the same time, I read about Big Visible Charts. I took a piece of 2 ft × 4 ft of paper, and started at the top:

Month of November 2007

That worked OK, until we were both tired of doing all the calculations by hand… The computer is the perfect tool for the job. So now, I’m back to square one, but this time, I am armed with a lot more knowledge. I want a solution that will:

  • record budgets (planned income and expenditures);
  • record actuals (actual income and expenditures);
  • report planned vs actual values, to see where we’re over-budget (it’s the restaurants!).

After learning how to make a simple todo application on Seaside, I think this application is just a little bit meatier that it’s not going to be too hard to do. At the same time, I will use this opportunity to contrast both Seaside and Rails, to help the community at large to see the differences between both of the frameworks.

Both applications are released under the MIT License.

I have setup two repositories:

You can already grab the code from the Monticello repository: I am done coding the models on Seaside. I checked in the skeleton Rails application, and will add a couple of pieces shortly. Expect to see this series pretty regularly in the coming weeks.

Via The Weekly Squeak, I found a link to a new Seaside tutorial. The Software Architecture Group of the Hasso-Platter-Institut implemented a todo application tutorial Seaside.

The tutorial is very complete:

Extern resources like images, css or javascript do belong to a proper website as well. Chapter seven contains the possibilities you have with Seaside and their pros and cons. Up to now, the whole application is only practicable for one time, afterwards all values of the user are forgotten. This problem of the persistence is treated by the eight chapter which, next to it, presents three different possibilities in detail. … In the nineth part the focus is put on an additional library which makes it possible to implement Ajax in Seaside Websites. Script.aculo.us with the integration by Lukas Renggli offers an easy and simple way to create your own Website in the style of Web 2.0.

Go and read it. Very good !

Ramon Leon sent me an E-Mail:

Attached are a few more idiomatic changes, the package is commented.

Please see the Monticello repository if you are interested.

James Robertson of Cincom Smalltalk features my TodoApp on Seaside in 218 lines in his Smalltalk Daily series. The episode is available at smalltalk_daily-10-15-07.html

I would like to publicly thank James for taking the time to do the screencast, and assure him that the way he pronounces my last name is quite correct.

In case you aren’t following my Monticello repository, the TodoApp is now at revision 5, including a bunch of fixes Ramon Leon suggested. TodoApp also stands at 233 lines, instead of the original 218. I have a couple of refactorings in mind, so the line count will probably go down.

The application’s models

We’ll need a Todo, TodoUser and TodoUserDatabase. Each user will keep a copy of it’s todos, and the database will give us methods to find and register new users. Let’s start with the user’s database:


1 Object subclass: #TodoUserDatabase
2 instanceVariableNames: ‘database’
3 classVariableNames: ‘’
4 poolDictionaries: ’’
5 category: ‘Todo-Model’

In case you never noticed, this is a message named #subclass:instanceVariableNames:classVariableNames:poolDictionaries:category, and the receiver is Object. Through the magic of code formatting, this looks like a class declaration, but it’s just another message.

The database object needs a way to initialize itself. We have two ways to do it: either at instance initialization time, or on first access time. Let’s go the latter way, which is what seems most prevalent in Smalltalk / Seaside code I’ve read:


1 database
2 ^ database ifNil: [database := OrderedCollection new]

Put this method in protocol private. Then, we need a way to add and remove users:


1 addUser: aUser
2 (self findWithLogin: aUser login)
3 ifNil: [self database add: aUser. ^ aUser]
4 ifNotNil: [self raiseDuplicateLoginName]
5
6 removeUser: aUser
7 database remove: aUser

These go in protocol accessing. Notice #addUser: calls #raiseDuplicateLoginName. Let’s define that immediately:


1 rraiseDuplicateLoginName
2 Error raiseSignal: ’Can’’t have two users with the same login’

Put this method in protocol error handling. #addUser: calls another helper method: #findWithLogin:. The implementation looks like this:


1 findWithLogin: aLogin
2 ^ self database detect: [:each | each login = aLogin] ifNone: [nil]

Again, put this method in protocol accessing. We’re done with the database side of things. We can now switch to the user itself.

TodoUser model


1 Object subclass: #TodoUser
2 instanceVariableNames: ‘login password todos’
3 classVariableNames: ‘’
4 poolDictionaries: ’’
5 category: ‘Todo-Model’

As the class comment, enter this text:

Instances of myself represent a user with a login and password, as well as a collection of Todo instances.

In protocol accessing, we define basic accessor methods:


1 login
2 ^ login
3
4 login: anObject
5 login := anObject
6
7 isSamePassword: aPassword
8 ^ password = aPassword
9
10 password: anObject
11 password := anObject
12
13 todos
14 ^ todos ifNil: [todos := OrderedCollection new]

Again, note how the todos instance variable is initialized if it wasn’t previously initialized. Equivalent Ruby code uses the ||= operator.

Then, we need to add and remove todos from the user:


1 addTodo: aTodo
2 self todos add: aTodo
3
4 removeTodo: aTodo
5 self todos remove: aTodo

Pretty simple, as things go. Put these in the accessing protocol. The final model is the Todo itself.

Todo model

Let’s declare the class:


1 Object subclass: #Todo
2 instanceVariableNames: ‘createdAt completed description completedAt’
3 classVariableNames: ‘’
4 poolDictionaries: ’’
5 category: ‘Todo-Model’

And the class’ comment:

Instances of myself represent a task that should be done, a todo. Todos are pretty simple: they have a description and a flag that identifies the completion status. Todos also keep track of when they were instantiated and completed.

This time around, we need an #initialize method:


1 initialize
2 createdAt := DateAndTime now

Put this in the initialization protocol. Next, in protocol accessing, we add a couple of basic accessors:


1 createdAt
2 ^ createdAt
3
4 description
5 ^ description
6
7 description: aString
8 description := aString
9
10 isCompleted
11 ^ completedAt notNil
12
13 completedAt
14 ^ completedAt
15
16 completedAt: aDateTime
17 completedAt := aDateTime
18
19 markCompleted
20 completedAt := DateAndTime now.

The Seaside UI: controllers and views

To kick things off, I define a new Seaside WAComponent subclass which will be our root component:


1 WAComponent subclass: #TodoComponent
2 instanceVariableNames: ‘’
3 classVariableNames: ’UserDatabase’
4 poolDictionaries: ‘’
5 category: ’Todo-UI-Seaside’

We define ourselves a class variable named UserDatabase which will hold an instance of TodoUserDatabase. Let’s give ourselves two accessor methods to the database: one class side and the other instance side. Put this one in accessing, class side:


1 userDatabase
2 ^ UserDatabase ifNil: [UserDatabase := TodoUserDatabase new]

Again, we see the same pattern: initialize unless already initialized. Back on the instance side, add this method in the accessing protocol:


1 userDatabase
2 ^ self class userDatabase

This is a simple call to the class side version of the method with the same name.

TodoAuthDecorator

To implement authentication, we must wrap the application within a decorator that will take care of these details for it. The decorator’s job is simple: authenticate or register a new user, and when authenticated, show the application instead of the authentication / registration form. I based this implementation on Seaside’s WABasicAuthentication, but mine uses a form instead of the HTTP Basic Access Authentication method. I could have subclassed WABasicAuthentication, but I wanted to learn how to do it manually before I reused code.

Seaside’s decorators have an owner, which is the decorated component. The decorator has a chance to let the component render itself or not, which is what happens later in #renderContentOn:.

Let’s start by declaring the class:


1 WADecoration subclass: #TodoAuthDecorator
2 instanceVariableNames: ‘login password passwordConfirmation authenticated authenticationMessage registrationMessage’
3 classVariableNames: ‘’
4 poolDictionaries: ’’
5 category: ‘Todo-UI-Seaside’

login, password and passwordConfirmation are used to hold the login and password during registration and authentication. I use authenticated as a simple boolean to determine if authentication was successful or not. authenticationMessage and registrationMessage are messages that will be shown to the user (think of Rail’s flash). I begin in the initialization protocol with:


1 initialize
2 authenticated := false

All Seaside decorators that want a chance to render around their owner must provide a #renderContentOn: method. This goes in the rendering protocol:


1 renderContentOn: html
2 authenticated
3 ifTrue: [self renderOwnerOn: html]
4 ifFalse: [self renderAuthenticationFormOn: html]

When authenticated, we render our owner (the decorated component), else we call #renderAuthenticationFormOn:, which looks like this:


1 renderAuthenticationFormOn: html
2 html heading: ‘Todo List’.
3 (html div) id: ‘auth’; with: [
4 (html div) class: ‘column returning’; with: [
5 self renderReturningUserFormOn: html].
6 (html div) class: ‘column new’; with: [
7 self renderNewUserFormOn: html]].
8 html div class: ‘clear’; with: [html space]

Most of this is setting up nested DIVs to create a two-column layout. Styling is handled by the #style method:


1 style
2 ^ ’
3 #auth-area a#logout { float: right}
4 #auth .column { width: 49%; float: left; }
5 #auth form label, #auth form input { display: block; }
6 .clear { clear: both; }
7

#renderAuthenticationFormOn: uses a couple of helper methods:


1 renderNewUserFormOn: html
2 html form: [
3 html heading: ‘I am a new user’ level: 2.
4 registrationMessage ifNotNilDo: [:msg|
5 html heading: msg level: 3].
6 html div: [
7 (html label) for: ‘new-login’; with: ‘Login’.
8 (html textInput) id: ‘new-login’; on: #login of: self].
9 html div: [
10 (html label) for: ‘new-password’; with: ‘Password’.
11 (html passwordInput) id: ‘new-password’; on: #password of: self].
12 html div: [
13 (html label) for: ‘new-password-confirmation’; with: ‘Confirm password’.
14 (html passwordInput) id: ‘new-password-confirmation’; on: #passwordConfirmation of: self].
15 (html submitButton) on: #register of: self]
16
17 renderReturningUserFormOn: html
18 html heading: ‘I am a returning user’ level: 2.
19 authenticationMessage ifNotNilDo: [:msg|
20 html heading: msg level: 3].
21 html form: [
22 html div: [
23 (html label) for: ‘login’; with: ‘Login’.
24 (html textInput) id: ‘login’; on: #login of: self].
25 html div: [
26 (html label) for: ‘password’; with: ‘Password’.
27 (html passwordInput) id: ‘password’; on: #password of: self].
28 (html submitButton) on: #authenticate of: self]

WATag’s #on:of: message is pretty powerful: it sends the specified message to the specified object. WATextInputTag also sends a mutator message to the object when doing form submissions. The returning user case calls #authenticate on self, which is implemented as follows:


1 authenticate
2 | user |
3 user := self userDatabase findWithLogin: login.
4 user ifNil: [
5 self failAuthentication: ‘Unable to authenticate using these credentials.’]
6 ifNotNil: [
7 (user isSamePassword: password)
8 ifTrue: [
9 authenticationMessage := nil.
10 self owner user: user.
11 authenticated := true]
12 ifFalse: [
13 self failAuthentication: ‘Invalid credentials for user.’]]

This goes in the actions protocol. When registering, we instead call #register:


1 register
2 | user |
3 user := self userDatabase findWithLogin: login.
4 user
5 ifNotNil: [
6 self failRegistration: ‘Login already taken’]
7 ifNil: [
8 password = passwordConfirmation
9 ifTrue: [
10 authenticationMessage := nil.
11 user := TodoUser new.
12 user login: login; password: password.
13 self userDatabase addUser: user.
14 self authenticate]
15 ifFalse: [
16 self failRegistration: ‘Password and confirmation do not match’]]

If the user doesn’t already exist (as identified through the login), and the password and the confirmation match, we register the new user and immediately authenticate him. Once the user is authenticated, we finally let the decorated component (the decorator’s owner) render itself:


1 renderOwnerOn: html
2 (html div) id: ‘auth-area’; with: [
3 (html anchor) id: ‘logout’; on: #logout of: self.
4 html heading: login capitalized, ’’’s Todo List’].
5 super renderOwnerOn: html

Put this in the rendering protocol. Here, we provide a logout link for authenticated users, as well as show who’s list this is. Then, we call our superclass’ #renderOwnerOn: to let the decorated component render itself. The logout link callsback the #logout method (in the actions protocol):


1 logout
2 self owner user: nil.
3 self clearAuthenticationInfo

Above, we called a couple of convenience methods, which obviously go in the convenience protocol:


1 clearAuthenticationInfo
2 login := nil.
3 password := nil.
4 passwordConfirmation := nil.
5 authenticated := false
6
7 failAuthentication: aMessage
8 authenticationMessage := aMessage.
9 password := nil.
10 passwordConfirmation := nil.
11
12 failRegistration: aMessage
13 registrationMessage := aMessage.
14 password := nil.
15 passwordConfirmation := nil.

Lastly, a couple of accessor methods:


1 login
2 ^ login
3
4 login: aLogin
5 login := aLogin
6
7 password
8 ^ password
9
10 password: aPassword
11 password := aPassword
12
13 passwordConfirmation
14 ^ passwordConfirmation
15
16 passwordConfirmation: aPassword
17 passwordConfirmation := aPassword
18
19 userDatabase
20 ^ TodoComponent userDatabase

TodoApp: our first real component

TodoApp is our root application component. It should thus register itself as a Seaside root. Let’s begin by declaring the class:


1 TodoComponent subclass: #TodoApp
2 instanceVariableNames: ‘viewers user’
3 classVariableNames: ‘’
4 poolDictionaries: ’’
5 category: ‘Todo-UI-Seaside’

Then, class side, in protocol testing, we implement #canBeRoot:


1 canBeRoot
2 ^ true

Still class side, in protocol initialization, add:


1 initialize
2 super initialize.
3 self registerAsApplication: #todo

This registers TodoApp as an application in Seaside, under /seaside/todo. Back on the instance side, in protocol initialization, we implement:


1 initialize
2 viewers := OrderedCollection new.
3 self addDecoration: (TodoAuthDecorator new).
4 self registerForBacktracking.

The root component knows that it needs authentication, so it immediately adds a decoration to itself. Seaside requires a component to register itself for backtracking if it’s collection of children will change during it’s lifecycle. Since the user will add and remove todos, our collection of TodoViewer instances will change.

Components render themselves, so put this in protocol rendering:


1 renderContentOn: html
2 html orderedList: [
3 self children do: [:each |
4 html listItem: [html render: each]]].
5 html paragraph: [
6 (html anchor) on: #newTodo of: self]

We render our collection of children, which is the viewers instance variable. Again, we have a callback when creating a new todo. In protocol call/answer, we add:


1 newTodo
2 | editor |
3 editor := TodoEditor new
4 todo: (Todo new);
5 yourself.
6 (self call: editor) ifNotNilDo: [:todo | self addTodo: todo]

Again, we have a couple of accessors which are pretty simple:


1 addTodo: aTodo
2 user addTodo: aTodo.
3 viewers add: (TodoViewer new todo: aTodo)
4
5 children
6 ^ viewers
7
8 user
9 ^ user
10
11 user: aUser
12 user := aUser.
13 viewers := OrderedCollection new.
14 user
15 ifNotNil: [
16 user todos do: [:each |
17 viewers add: (TodoViewer new todo: each)]]

#addTodo: adds the todo to the user instance, and also registers a new TodoViewer instance. #user: takes care of cleanup in case of logout (aUser isNil), and registers new TodoViewer instances when logging in (aUser notNil).

TodoViewer: A simple model viewer

This class is pretty simple. It’s job is to display a todo and allow it to be marked completed. Let’s declare the class:


1 TodoComponent subclass: #TodoViewer
2 instanceVariableNames: ‘todo’
3 classVariableNames: ‘’
4 poolDictionaries: ’’
5 category: ‘Todo-UI-Seaside’

We begin by rendering the component:


1 renderContentOn: html
2 (html paragraph)
3 class: (todo isCompleted ifTrue: [‘complete’]);
4 with: [
5 self renderDescriptionOn: html.
6 self renderCompletionStatusOn: html]

The call to #class: sets up an HTML class on the paragraph element, to help styling. A couple more rendering methods:


1 renderDescriptionOn: html
2 html html: todo description.
3 html space.
4
5 renderCompletionStatusOn: html
6 todo isCompleted
7 ifFalse: [
8 (html anchor) callback: [todo markCompleted]; with: ’It’’s done!‘]
9 ifTrue: [
10 html span: todo completedAt displayString]
11
12 style
13 ^’
14 .complete { color: #999; }
15 .complete span { font-size: smaller; }
16 }’

And the usual accessors:


1 todo
2 ^ todo
3
4 todo: anObject
5 todo := anObject

TodoEditor: Create or edit a Todo instance

The current version of the todo app doesn’t use TodoEditor to edit existing todos, but it does use it for creating new instances. As usual, let’s declare the class:


1 TodoComponent subclass: #TodoEditor
2 instanceVariableNames: ‘todo’
3 classVariableNames: ‘’
4 poolDictionaries: ’’
5 category: ‘Todo-UI-Seaside’

Next, we render the component:


1 renderContentOn: html
2 html form: [
3 html div: [
4 (html label) for: ‘description’; with: ‘Description’.
5 (html textInput) id: ‘description’; on: #description of: todo].
6 (html submitButton) on: #save of: self.
7 html space.
8 (html anchor) on: #cancel of: self]

Here is where things get interesting: #on:of: is used to set a callback on the todo instance. Seaside will call #description: of the todo instance to set the value on form post. We don’t need a temporary variable to hold the description in the component: the todo instance takes care of that.

In protocol call/answer, we add the following methods, which are called from #renderContentOn:


1 save
2 self answer: todo
3
4 cancel
5 self answer: nil

And the final accessors:


1 todo
2 ^ todo
3
4 todo: aTodo
5 todo := aTodo

Total line count: 233 lines, thanks to:


1 (Smalltalk allClasses
2 select:[:each | each category beginsWith: ‘Todo-’])
3 inject: 0 into: [:sum :each | sum + each linesOfCode]

Print this to get the total.

UPDATE 2007-10-15: I counted the lines initially by doing a fileOut and using standard command line tools: cat and wc. Seems I was slightly off.

Todos (pun intended)

There are a couple of things I’d like this todo app to be able to do:

  • Use an inline editor to change the description;
  • Use Scriptaculous to add a couple of effects;
  • Use Ajax to mark completed items;
  • Format dates and times;
  • Maybe set the user’s timezone and show dates and times using the user’s timezone;
  • Date/time formats per-user;
  • Enhance security by not storing plain-text versions of passwords in user instances.

This is a toy project. I might never again touch this application.

Recap

One point I’d like to be clear on:

Don’t do that!

I store an unencrypted copy of the password in TodoUser instances. That should never be done. As many others before me, I tried to simplify the code as much as possible. There probably lurks a Password model object in there.

Also, I am no expert on Smalltalk, Squeak or Seaside. There are probably a couple of things I could have done differently, and I hope some people out there might be interested in helping me learn more about Seaside.

I hope you enjoyed this article. There might be more of these coming later. Send me E-Mail at francois@teksol.info or write a comment.

Search

Your Host

A picture of me

I am François Beausoleil, a Ruby on Rails and Scala developer. During the day, I work on Seevibes, a platform to measure social interactions related to TV shows. At night, I am interested many things. Read my biography.

Top Tags

Books I read and recommend

Links

Projects I work on

Projects I worked on