OnlineTester: An Arcane Adventure. Part 2: Models and their use

Tagged:  •    •  

The previous post was about infrastructure, now we'll take a short peek into the model classes, before a more thorough walkthrough through Iliad details in the next post(s).

The application files

Finally we come to the few files I've really written.

-    3241 application.st
d         doc/
 -       1582 einTest.st
d         models/
 -       1861 OTAufgabe.st
 -       1583 OTFrage.st
 -       1262 OTInfo.st
 -       2356 OTTest.st
d         widgets/
 -              642 OTAufgabeWidget.st
 -             1378 OTFrageWidget.st
 -              714 OTRegisterWidget.st
 -             1656 OTTestWidget.st

Test description

The sample doc/einTest.st is a quite readable, I've been told, representation of the sample test I'm using. It's in German, but who cares about what's between the single quotes, anyways?

Eval [ |ot drei vier|
   drei := #( 'weiß nicht' 'ja' 'nein' ).
   vier := #( 'weiß nicht' 'ja' 'nein' 'unbestimmt' ).

Let me interrupt the Eval. There are two sets of possible answers to be used here: "drei" with three choices (don't know, yes, no) and "vier" with four choices (as before plus "undecidable").

  ot := OnlineTester.OTTest name: '1. Online-Test' datum: '06.07.2009' klasse: '6 a-e'.
   ot
     oben: '<h3>Viel Erfolg!</h3><p>Hier steht <em>XHTML</em> mit 
       irgendwelchen Infos und Bildern.</p>
       <div style="text-align: center"><img src="/img/bild1.png"/></div>';
     unten: '<p>Hier steht auch <em>XHTML</em>.</p>';
     antworten: drei;
     vorgabe: 1.

The test is created with some "vital" information: name, date, name of class taking the test. Next we provide some introductory (oben:) and closing (unten:) text that is visible during the whole test and define the default choice set and point value of a question.

  ot aufgabe
     text: 'Was zählt zur Hardware eines Computers?';
     frage: 'Festplatte' antwort: 1;
     frage: 'Betriebssystem' antwort: 2;
     ...

The final exercise uses four options and is set up like this:

  ot aufgabe
     antworten: vier;
     text: 'Nach dem Einschalten ...';
     frage: 'lädt der Rechner das im ROM abgelegte Bootprogramm' antwort: 1;
     ...
 ]

An exercise has some introductory text to build a frame of references for its choices. We also give the correct answer, as index into the choice collection to avoid redundant typing. Easy to read and write, with copy and paste it's basically filling out a template. Client: "I can do this."
Somewhen this will be wrapped into a nice GUI eliminating a lot of hassle, but it suffices for now.

Models

Let's have a look at some not-totally-boring code in the models before looking at the app itself and its widgets. This means that I won't say anything about OTInfo (which just wraps a Dictionary) or OTFrage, which describes a single question line of an exercise and follows the same patterns as OTAufgabe, see below.
The models will gain some weight, as they will need to support totalling the results and eventually some statistics, but right now they are trivial, mostly standard accessors.

Object subclass: OTTest [ 
   <comment: 'I build online tests.
   
   info        about this test
   aufgaben    the list of exercises to complete
   antworten   the default choices for answers
   vorgabe     the index of the preselected answer
   '>
 
   | info aufgaben antworten vorgabe |
 
   OTTest class [ 
     | current |
 
     name: aNameString datum: aDateString klasse: aString [ 
       <category: 'instance creation'>
       <comment: 'Create, store and answer a new test with the designated properties.
         Assignments down the line refer to this test.'>
       current := self basicNew 
         initialize;
         name: aNameString;
         datum: aDateString;
         klasse: aString;
         yourself.
       ^ current
     ]
 
     fileIn: aFilename [ 
       <category: 'instance creation'>
       <comment: 'Provide an easy way to describe and load a test.'>
       ^ FileStream fileIn: aFilename
     ]
 
     current [ 
       <category: 'accessing'>
       <comment: 'Answer the test most recently created test.'>
       current ifNil: [ self fileIn: 'doc/einTest.st' ].
       ^ current
     ]
   ]

On the class side, we find a constructor storing the most recently created test into a class instance variable, since it is going to be worked on for a while now. The second method, "fileIn:" is used to read the test description I show in the preceding post already. Finally, the method "current" takes care of loading the sample code if no other test has been specified.
The instance side has mostly standard accessors, of interest are only the following methods related to the collection of exercises.

  initialize [
     <category: 'initializing'>
     info := OTInfo new.
     aufgaben := OrderedCollection new
   ]
   aufgabe [ 
     <category: 'instance creation'>
     ^ aufgaben add: ( OTAufgabe for: self)
   ]
   aufgaben [
     <category: 'accessing'>
     ^ aufgaben
   ]
 ]

The next model are the exercises, class OTAufgabe:

Object subclass: OTAufgabe [ 
   <comment: 'I create tasks for an OTTest and collect instances of OTFrage.
   If the test description does not provide specific instructions for antworten
   or vorgabe, my instances will use the information provided with their OTTest.
   
   test        my test
   text        the introduction building a frame of reference for my questions
   fragen      the collection my OTFrage instances
   antworten   the default choices for answers
   vorgabe     the index of the preselected answer
   '>
 
   | test text fragen antworten vorgabe |
 
   OTAufgabe class [
     for: aTest [ 
       <category: 'instance creation'>
       ^ self new test: aTest
     ]
   ]

The constructor method creates a link back to the OTTest instance which has sent the message, since this OTTest object knows about some default values.

  antworten [ 
     <category: 'accessing'>
     ^ antworten ifNil: [ antworten := test antworten ]
   ]
   vorgabe [ 
     <category: 'accessing'>
     ^ vorgabe ifNil: [ vorgabe := test vorgabe ]
   ]

We provide two methods for creating a new question of this exercise, where the more general implementation follows a similar call pattern to link the question back to here for accessing these defaults.

  frage: aString antwort: aValue [ 
     <category: 'instance creation'>
     self frage: aString antwort: aValue wert: 1
   ]
   frage: aString antwort: aValue wert: anInteger [ 
     <category: 'instance creation'>
     fragen add: (
       ( OTFrage for: self) text: aString; antwort: aValue; wert: anInteger
     )
   ]
 ]

Coming up

I have yet to talk about application and widget code, but it's getting a bit late for the more thorough walkthrough I have in mind. Later, gentle readers.

User login