Iliad Framework, the users-dao intermezzo

In the previous post we build a login form. In that form we included a “Register” link and so we must provide a way to make our users entries persistent.

Using Smalltalk the image is already persistent, saved every time you need reloaded from the image file. To cut a long story short if you don’t need ACID transactions and a strong mechanism to backup and restore data you can use the image as your database.

This may look strange (and do look strange) to non-smalltalkers but this wonderful post explains very well why using your image as a persistence layer isn’t a crazy idea.

For our didactic purpose we can safely use the image.

We need to store users so we start by creating an PnUser class:

Object subclass: #PnUser
	instanceVariableNames: 'realname surname email md5pwd'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'LeonardoBlog'!

As you see this is a really simple class. You can automatically generate the accessors method using the Pharo browser. You will obtain something like this:

!PnUser methodsFor: 'accessing'!
email
	^ email

password:pwd
	md5pwd :=  (MD5 hashMessage:pwd) hex.

realname: anObject
	realname := anObject

email: anObject
	email := anObject

realname
	^ realname

surname: anObject
	surname := anObject

surname
	^ surname

email
	^ email

email: anObject
	email := anObject

realname
	^ realname

surname: anObject
	surname := anObject

surname
	^ surname

realname: anObject
	realname := anObject

Instead of storing the password we store the MD5 hash code and so we have these methods:

!PnUser methodsFor: 'accessing'!
password:pwd
	md5pwd :=  (MD5 hashMessage:pwd) hex.

!PnUser methodsFor: 'accessing'!
hasPassword:aString
	^ (MD5 hashMessage:aString) hex = md5pwd .

The MD5 class has all the code needed to compute the MD5 hash code of a string. Pharo has a lot of utilities to ease the life of developers.

Ok. Now we need a class to store users informations. We call it PnUserDAO and we will use a Dictionary to store users informations: the key will be the email and the value the PnUser object.

Let’s start with the class definition:

Object subclass: #PnUserDAO
	instanceVariableNames: 'usersDictionary'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'LeonardoBlog'!

When we initialize a DAO we create a new Dictionary where we will store the users:

!PnUserDAO methodsFor: 'initialization'!
initialize
	super initialize.
	usersDictionary := Dictionary new.

The first thing we need is a method to store a new user:

!PnUserDAO methodsFor: 'accessing'!
register:anUser
	usersDictionary at: anUser email put: anUser

Then we need a method to retrieve an user from the email:

!PnUserDAO methodsFor: 'accessing'!
userForEmail: anEmail
	^ usersDictionary at: anEmail ifAbsent: nil.

We created a DAO and now we need to create a test case, just to see if everything is working. Before every test we create a new DAO with a test user and after the test we delete the DAO just created:

TestCase subclass: #PnUserDAOTest
	instanceVariableNames: 'dao'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'LeonardoBlog-Tests'!

!PnUserDAOTest methodsFor: 'running'!
setUp
	| testUser |
	super setUp.
	dao := PnUserDAO new.
	testUser := PnUser new
		surname: 'Test user';
		realname: 'Test name';
		password: 'test';
		email: 'test@test.eu'.
	dao register: testUser.

!PnUserDAOTest methodsFor: 'running'!
tearDown
	super tearDown.
	dao := nil.

Now we can test the register: method:

!PnUserDAOTest methodsFor: 'tests'!
testUserCreation
	|u|
	u := PnUser new email: 'leonardo@leo.it'; realname:'Leonardo'; surname:'Test'.
	dao register:u.
	self assert:(dao userForEmail: 'leonardo@leo.it') isNotNil.

The login method needs a way to check for an user and a password:

!PnUserDAO methodsFor: 'authentication'!
userForEmail: anEmail password:pwd
	|user|
	user := self userForEmail: anEmail.
	user ifNil: [ ^ nil ].
	(user hasPassword: pwd) ifTrue: [ ^ user ] ifFalse: [ ^ nil].

Let’s test it:

!PnUserDAOTest methodsFor: 'tests'!
testAuthentication
	self assert:(dao userForEmail:'test@test.eu' password:'test' ) isNotNil.
	self assert:(dao userForEmail:'test@test.eu' password:'testnot' ) isNil.

As we will offer an user deregistration procedure we need a unregister: method:

!PnUserDAO methodsFor: 'accessing'!
unregister: anEmail
	usersDictionary removeKey: anEmail ifAbsent: [ ].
	^ self

We test it:

!PnUserDAOTest methodsFor: 'tests'!
testRemovingUnknownUser
	dao unregister: 'nonexitent'

!PnUserDAOTest methodsFor: 'tests'!
testRemovingKnownUser
	|u|
	u := PnUser new email: 'leonardo@leo.it'; realname:'Leonardo'; surname:'Test'.
	dao register:u.
	self assert:(dao userForEmail: 'leonardo@leo.it') isNotNil.
	dao unregister:'leonardo@leo.it'.
	self assert:(dao userForEmail: 'leonardo@leo.it') isNil.

Now we create a method to remove all known users:

!PnUserDAO methodsFor: 'util'!
deleteAllUsers
	usersDictionary removeAll

This is the test:

!PnUserDAOTest methodsFor: 'tests'!
testDeleteAllUsers
	self assert:(dao userForEmail:'test@test.eu' password:'test' ) isNotNil .
	dao deleteAllUsers .
	self assert:(dao userForEmail:'test@test.eu' password:'test' ) isNil .

Well… if everything is ok you have all units test working correctly and you can be happy!

PnUserDAO test cases

The next post is here.