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:

<span class="nc">Object</span><span class="k"> subclass: </span><span class="ss">#PnUser</span>
<span class="k">    instanceVariableNames: </span><span class="s">'realname surname email md5pwd'</span>
<span class="k">    classVariableNames: </span><span class="s">''</span>
<span class="k">    poolDictionaries: </span><span class="s">''</span>
<span class="k">    category: </span><span class="s">'LeonardoBlog'</span><span class="k">!</span>

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:

<span class="k">!</span><span class="nc">PnUser</span><span class="k"> methodsFor: </span><span class="s">'accessing'</span><span class="k">!</span>
<span class="nf">email</span>
    <span class="o">^</span> <span class="nv">email</span>

<span class="nf">password:</span><span class="nv">pwd</span>
    <span class="nf">md5pwd</span> <span class="o">:=</span>  (<span class="nc">MD5</span> <span class="nf">hashMessage:</span><span class="nv">pwd</span>) <span class="nf">hex</span><span class="p">.</span>

<span class="nf">realname:</span> <span class="nv">anObject</span>
    <span class="nv">realname</span> <span class="o">:=</span> <span class="nv">anObject</span>

<span class="nf">email:</span> <span class="nv">anObject</span>
    <span class="nf">email</span> <span class="o">:=</span> <span class="nv">anObject</span>

<span class="nf">realname</span>
    <span class="err">^</span> <span class="nf">realname</span>

<span class="nf">surname:</span> <span class="nv">anObject</span>
    <span class="nf">surname</span> <span class="o">:=</span> <span class="nv">anObject</span>

<span class="nf">surname</span>
    <span class="err">^</span> <span class="nf">surname</span>

<span class="nf">email</span>
    <span class="err">^</span> <span class="nf">email</span>

<span class="nf">email:</span> <span class="nv">anObject</span>
    <span class="nf">email</span> <span class="o">:=</span> <span class="nv">anObject</span>

<span class="nf">realname</span>
    <span class="err">^</span> <span class="nf">realname</span>

<span class="nf">surname:</span> <span class="nv">anObject</span>
    <span class="nf">surname</span> <span class="o">:=</span> <span class="nv">anObject</span>

<span class="nf">surname</span>
    <span class="err">^</span> <span class="nf">surname</span>

<span class="nf">realname:</span> <span class="nv">anObject</span>
    <span class="nf">realname</span> <span class="o">:=</span> <span class="nv">anObject</span>

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

<span class="k">!</span><span class="nc">PnUser</span><span class="k"> methodsFor: </span><span class="s">'accessing'</span><span class="k">!</span>
<span class="nf">password:</span><span class="nv">pwd</span>
    <span class="nv">md5pwd</span> <span class="o">:=</span>  (<span class="nc">MD5</span> <span class="nf">hashMessage:</span><span class="nv">pwd</span>) <span class="nf">hex</span><span class="p">.</span>

<span class="k">!</span><span class="nc">PnUser</span><span class="k"> methodsFor: </span><span class="s">'accessing'</span><span class="k">!</span>
<span class="nf">hasPassword:</span><span class="nv">aString</span>
    <span class="o">^</span> (<span class="nc">MD5</span> <span class="nf">hashMessage:</span><span class="nv">aString</span>) <span class="nf">hex</span> <span class="nf">=</span> <span class="nv">md5pwd</span> <span class="p">.</span>

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:

<span class="nc">Object</span><span class="k"> subclass: </span><span class="ss">#PnUserDAO</span>
<span class="k">    instanceVariableNames: </span><span class="s">'usersDictionary'</span>
<span class="k">    classVariableNames: </span><span class="s">''</span>
<span class="k">    poolDictionaries: </span><span class="s">''</span>
<span class="k">    category: </span><span class="s">'LeonardoBlog'</span><span class="k">!</span>

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

<span class="k">!</span><span class="nc">PnUserDAO</span><span class="k"> methodsFor: </span><span class="s">'initialization'</span><span class="k">!</span>
<span class="nf">initialize</span>
    <span class="bp">super</span> <span class="nf">initialize</span><span class="p">.</span>
    <span class="nv">usersDictionary</span> <span class="o">:=</span> <span class="nc">Dictionary</span> <span class="nb">new</span><span class="p">.</span>

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

<span class="k">!</span><span class="nc">PnUserDAO</span><span class="k"> methodsFor: </span><span class="s">'accessing'</span><span class="k">!</span>
<span class="nf">register:</span><span class="nv">anUser</span>
    <span class="nv">usersDictionary</span> <span class="nf">at:</span> <span class="nv">anUser</span> <span class="nf">email</span> <span class="nf">put:</span> <span class="nv">anUser</span>

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

<span class="k">!</span><span class="nc">PnUserDAO</span><span class="k"> methodsFor: </span><span class="s">'accessing'</span><span class="k">!</span>
<span class="nf">userForEmail:</span> <span class="nv">anEmail</span>
    <span class="o">^</span> <span class="nv">usersDictionary</span> <span class="nf">at:</span> <span class="nv">anEmail</span> <span class="nf">ifAbsent:</span> <span class="bp">nil</span><span class="p">.</span>

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:

<span class="nc">TestCase</span><span class="k"> subclass: </span><span class="ss">#PnUserDAOTest</span>
<span class="k">    instanceVariableNames: </span><span class="s">'dao'</span>
<span class="k">    classVariableNames: </span><span class="s">''</span>
<span class="k">    poolDictionaries: </span><span class="s">''</span>
<span class="k">    category: </span><span class="s">'LeonardoBlog-Tests'</span><span class="k">!</span>

<span class="k">!</span><span class="nc">PnUserDAOTest</span><span class="k"> methodsFor: </span><span class="s">'running'</span><span class="k">!</span>
<span class="nf">setUp</span>
    <span class="o">|</span><span class="nv"> testUser </span><span class="o">|</span>
    <span class="bp">super</span> <span class="nf">setUp</span><span class="p">.</span>
    <span class="nv">dao</span> <span class="o">:=</span> <span class="nc">PnUserDAO</span> <span class="nb">new</span><span class="p">.</span>
    <span class="nv">testUser</span> <span class="o">:=</span> <span class="nc">PnUser</span> <span class="nb">new</span>
        <span class="nf">surname:</span> <span class="s">'Test user'</span><span class="p">;</span>
        <span class="nf">realname:</span> <span class="s">'Test name'</span><span class="p">;</span>
        <span class="nf">password:</span> <span class="s">'test'</span><span class="p">;</span>
        <span class="nf">email:</span> <span class="s">'test@test.eu'</span><span class="p">.</span>
    <span class="nv">dao</span> <span class="nf">register:</span> <span class="nv">testUser</span><span class="p">.</span>

<span class="k">!</span><span class="nc">PnUserDAOTest</span><span class="k"> methodsFor: </span><span class="s">'running'</span><span class="k">!</span>
<span class="nf">tearDown</span>
    <span class="bp">super</span> <span class="nf">tearDown</span><span class="p">.</span>
    <span class="nv">dao</span> <span class="o">:=</span> <span class="bp">nil</span><span class="p">.</span>

Now we can test the register: method:

<span class="k">!</span><span class="nc">PnUserDAOTest</span><span class="k"> methodsFor: </span><span class="s">'tests'</span><span class="k">!</span>
<span class="nf">testUserCreation</span>
    <span class="o">|</span><span class="nv">u</span><span class="o">|</span>
    <span class="nv">u</span> <span class="o">:=</span> <span class="nc">PnUser</span> <span class="nb">new</span> <span class="nf">email:</span> <span class="s">'leonardo@leo.it'</span><span class="p">;</span> <span class="nf">realname:</span><span class="s">'Leonardo'</span><span class="p">;</span> <span class="nf">surname:</span><span class="s">'Test'</span><span class="p">.</span>
    <span class="nv">dao</span> <span class="nf">register:</span><span class="nv">u</span><span class="p">.</span>
    <span class="bp">self</span> <span class="nf">assert:</span>(<span class="nv">dao</span> <span class="nf">userForEmail:</span> <span class="s">'leonardo@leo.it'</span>) <span class="nf">isNotNil</span><span class="p">.</span>

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

<span class="k">!</span><span class="nc">PnUserDAO</span><span class="k"> methodsFor: </span><span class="s">'authentication'</span><span class="k">!</span>
<span class="nf">userForEmail:</span> <span class="nv">anEmail</span> <span class="nf">password:</span><span class="nv">pwd</span>
    <span class="o">|</span><span class="nv">user</span><span class="o">|</span>
    <span class="nv">user</span> <span class="o">:=</span> <span class="bp">self</span> <span class="nf">userForEmail:</span> <span class="nv">anEmail</span><span class="p">.</span>
    <span class="nv">user</span> <span class="nf">ifNil:</span> [ <span class="o">^</span> <span class="bp">nil</span> ]<span class="p">.</span>
    (<span class="nv">user</span> <span class="nf">hasPassword:</span> <span class="nv">pwd</span>) <span class="nb">ifTrue:</span> [ <span class="o">^</span> <span class="nv">user</span> ] <span class="nb">ifFalse:</span> [ <span class="o">^</span> <span class="bp">nil</span>]<span class="p">.</span>

Let’s test it:

<span class="k">!</span><span class="nc">PnUserDAOTest</span><span class="k"> methodsFor: </span><span class="s">'tests'</span><span class="k">!</span>
<span class="nf">testAuthentication</span>
    <span class="bp">self</span> <span class="nf">assert:</span>(<span class="nv">dao</span> <span class="nf">userForEmail:</span><span class="s">'test@test.eu'</span> <span class="nf">password:</span><span class="s">'test'</span> ) <span class="nf">isNotNil</span><span class="p">.</span>
    <span class="bp">self</span> <span class="nf">assert:</span>(<span class="nv">dao</span> <span class="nf">userForEmail:</span><span class="s">'test@test.eu'</span> <span class="nf">password:</span><span class="s">'testnot'</span> ) <span class="nf">isNil</span><span class="p">.</span>

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

<span class="k">!</span><span class="nc">PnUserDAO</span><span class="k"> methodsFor: </span><span class="s">'accessing'</span><span class="k">!</span>
<span class="nf">unregister:</span> <span class="nv">anEmail</span>
    <span class="nv">usersDictionary</span> <span class="nf">removeKey:</span> <span class="nv">anEmail</span> <span class="nf">ifAbsent:</span> [ ]<span class="p">.</span>
    <span class="o">^</span> <span class="bp">self</span>

We test it:

<span class="k">!</span><span class="nc">PnUserDAOTest</span><span class="k"> methodsFor: </span><span class="s">'tests'</span><span class="k">!</span>
<span class="nf">testRemovingUnknownUser</span>
    <span class="nv">dao</span> <span class="nf">unregister:</span> <span class="s">'nonexitent'</span>

<span class="nf">!</span><span class="nc">PnUserDAOTest</span> <span class="nf">methodsFor:</span> <span class="s">'tests'</span><span class="nf">!</span>
<span class="nf">testRemovingKnownUser</span>
    <span class="o">|</span><span class="nv">u</span><span class="o">|</span>
    <span class="nv">u</span> <span class="o">:=</span> <span class="nc">PnUser</span> <span class="nb">new</span> <span class="nf">email:</span> <span class="s">'leonardo@leo.it'</span><span class="p">;</span> <span class="nf">realname:</span><span class="s">'Leonardo'</span><span class="p">;</span> <span class="nf">surname:</span><span class="s">'Test'</span><span class="p">.</span>
    <span class="nv">dao</span> <span class="nf">register:</span><span class="nv">u</span><span class="p">.</span>
    <span class="bp">self</span> <span class="nf">assert:</span>(<span class="nv">dao</span> <span class="nf">userForEmail:</span> <span class="s">'leonardo@leo.it'</span>) <span class="nf">isNotNil</span><span class="p">.</span>
    <span class="nv">dao</span> <span class="nf">unregister:</span><span class="s">'leonardo@leo.it'</span><span class="p">.</span>
    <span class="bp">self</span> <span class="nf">assert:</span>(<span class="nv">dao</span> <span class="nf">userForEmail:</span> <span class="s">'leonardo@leo.it'</span>) <span class="nf">isNil</span><span class="p">.</span>

Now we create a method to remove all known users:

<span class="k">!</span><span class="nc">PnUserDAO</span><span class="k"> methodsFor: </span><span class="s">'util'</span><span class="k">!</span>
<span class="nf">deleteAllUsers</span>
    <span class="nv">usersDictionary</span> <span class="nf">removeAll</span>

This is the test:

<span class="k">!</span><span class="nc">PnUserDAOTest</span><span class="k"> methodsFor: </span><span class="s">'tests'</span><span class="k">!</span>
<span class="nf">testDeleteAllUsers</span>
    <span class="bp">self</span> <span class="nf">assert:</span>(<span class="nv">dao</span> <span class="nf">userForEmail:</span><span class="s">'test@test.eu'</span> <span class="nf">password:</span><span class="s">'test'</span> ) <span class="nf">isNotNil</span> <span class="p">.</span>
    <span class="nv">dao</span> <span class="nf">deleteAllUsers</span> <span class="p">.</span>
    <span class="bp">self</span> <span class="nf">assert:</span>(<span class="nv">dao</span> <span class="nf">userForEmail:</span><span class="s">'test@test.eu'</span> <span class="nf">password:</span><span class="s">'test'</span> ) <span class="nf">isNil</span> <span class="p">.</span>

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

PnUserDAO test cases