Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Question behaviours

From MoodleDocs
Revision as of 12:43, 25 November 2009 by Tim Hunt (talk | contribs)

This page explains how to go about writing a new Question Interaction Model for the new Moodle question engine.

Previous section: Design

Note: This page is a work-in-progress. Feedback and suggested improvements are welcome. Please join the discussion on moodle.org or use the page comments.


To learn to write Question Interaction Models, you are highly encouraged to read the code of some of the existing interaction models in the Moodle code base. The code for most interaction models is shorter than these instructions!

This document assumes that you understand the data model, where a question attempt comprises a number of steps, and each step has some properties like a state and a mark, and an array of submitted data.


File layout

A question interaction model plugin lives in a folder in question/interaction. The layout inside that folder follows the typical layout for a Moodle plugin. For example, inside question/interaction/mymodel/ we would have:

model.php
This contains the definition of the qim_mymodel class, which should extend the question_interaction_model base class.
renderer.php
This contains the definition of the qim_mymodel_renderer class, which should extend the qim_renderer base class.
simpletest/...
Contains the unit tests for this interaction model. You are strongly encouraged to write thorough unit tests to ensure the correctness of your interaction model.
lang/en_utf8/qim_mymodel.php
English language strings for this interaction model. You can, of course, include other languages too. The language file must define at least the string giving the model a name, for example $string['mymodel'] = 'My model';.

Note that Question Interaction Models are not expected to have their own database tables or capabilities. Therefore, there should not be a db sub-folder.

In future, we will probably add the ability for an Interaction Model to have a settings.php file, so that it can have configuration options.


Question model class

One instance of this class will be created for each question_attempt that uses this model.

This documentation just lists the metods that you are likely to need to override. See the PHPdocumentor comments for a complete API documentation.


Class declaration

The class name for the model class must be the plugin name (e.g. mymodel) prefixed by qim_. You must extend the question_interaction_model base class, or another model class.

class qim_mymodel extends question_interaction_model {

   // ...

}

You should not need to override the constructor.


Fields

When an interaction model class is created, the fields qa and question are initialised to point to the question attempt and question that this model is being used by.

From the base class code:

   // From the base class.
   /** @var question_attempt */
   protected $qa;
   /** @var question_definition */
   protected $question;
   public function __construct(question_attempt $qa) {
       $this->qa = $qa;
       $this->question = $qa->get_question();
   }


IS_ARCHETYPAL

Some interaction models define models that should be offered as options in the user interface. For example CBM, interactive mode, and so on. Other models are for internal use. For example qim_informationitem, qim_interactiveadaptedformyqtype. This is indicated by defining a constant IS_ARCHETYPAL in the class. True means that this model will be offered as an option in the UI.

   const IS_ARCHETYPAL = true; // or false


get_min_fraction

Returns the smallest possible fraction that this model, applied to this question, may give. Normally this will be the min fraction or the question type, however some models (for example CBM) manipulate the question scores, and so need to return an adjusted min_fraction.

For example, from qim_deferredfeedback:

   public function get_min_fraction() {
       return $this->question->get_min_fraction();
   }

From qim_qim_deferredcbm:

   public function get_min_fraction() {
       return question_cbm::adjust_fraction(
               parent::get_min_fraction(), question_cbm::HIGH);
   }


get_expected_data

Some interaction models display additional form controls. When the question from is submitted, these submitted values are read using the standard optional_param function. The question engine needs to know what parameters to ask for, and with what types.

The get_expected_data function should return an array of expected parameters, with the corresponding param type. Note that the array of expected parameters may depend on the current state of the question attempt. For example, from qim_deferredcbm

   public function get_expected_data() {
       if (question_state::is_active($this->qa->get_state())) {
           return array('certainty' => PARAM_INT);
       }
       return array();
   }


init_first_step

You are unlikely to need to override this method. This is called when the question attempt is actually stared. It gives the model a chance to do any necessary initialisation, including passing on the request to the question type. This is, for example, how the choices in a multiple choices question are shuffled.

From the base class:

   public function init_first_step(question_attempt_step $step) {
       $this->question->init_first_step($step);
   }


process_action

This is the most important method. It controls what happens when someone does something with the question (other than starting it, which is handled by init_first_step.)

When the method is called, a pending attempt step is passed in. This method can either return false, to indicate nothing interesting happened, and the pending step should be discarded. Or, it can update that step, and return true, to have the new step retained.

Typically, the method is implemented by examining the current state of the attempt, and the pending step, to decide what sort of action this is, and then delegating to a more specific method.

For example, from qim_deferredfeedback

   public function process_action(question_attempt_step $pendingstep) {
       if ($pendingstep->has_im_var('comment')) {
           return $this->process_comment($pendingstep);
       } else if ($pendingstep->has_im_var('finish')) {
           return $this->process_finish($pendingstep);
       } else {
           return $this->process_save($pendingstep);
       }
   }

Note that there are some special actions that every model must be able to handle:

im_var comment => 'A manual comment'
This may also have im_var mark and im_var maxmark, if the question is graded. This is the action that is fired when a user manually grades a question.
im_var finish => 1
This is the action that is generated when the user submits and finishes a whole usage. For example when they click 'Submit all and finish' in a quiz.

The base class implements the process_comment method in a way that should be suitable for most models. It also provides a process_save implementation that may be suitable for most models.


adjust_display_options

TODO


Renderer class

TODO


Unit tests

For a general introduction, see the Moodle unit testing documentation.

Most of the interaction models are currently tested by working one or more test questions through a sequence of example inputs. To do this they subclass qim_walkthrough_test_base which provides a lot of helper methods to facilitate writing tests like that.

The best way to start is probably to look at the tests for some of the standard interaction models.


See also

In the next section, Developing a Question Type I describe what a developer will need to do to create a Question Type plugin for the new system.

Template:CategoryDeveloper