on
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!