OnlineTester: An Arcane Adventure. Part 2: Models and their use
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
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.
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 ) ] ]
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.