Actions

Question object types

From LimeSurvey Manual

Question object types is about making questions in LS more object-oriented and modular, and letting users create and upload their own object types.


Basically, every place in the code which is doing a switch on question type should be replaced by a polymorphic call to an object, e.g. $object->renderFrontend();


Old wiki page: https://manual.limesurvey.org/Question_Objects

Use-cases

  • Colour picker - user click on a picture with colours, and the position in the picture defines the colour.
  • "... the surgeon would indicate the location of a fracture by drawing on a diagram of the knee."
  • Having more than one question in one "library" file, where some of the questions can be activated via a bought activation code.
  • Gender question as being a subtype of yes-no. Yes/No : subtype of "fixed list" ?

Features

TODO

Open issues

  • Is array it's own object type, or is it a property of a question? Could we have an array of any question typ, e.g. yes/no questions?
  • Cross-cutting concerns:
    • What if the user wants to add a comment field to all questions in the survey? Use a plugin? Or time limit, which can be added to all questions, but shouldn't take up space if it's not used. Shnoulle/Sam recommends EAV table; Olle thinks it's too schema-less and that question types could modify the database instead. Actually all this part was in QuestionAttribute.
      • Sam:
        • I think we can identify some properties that apply to (almost) all question types, these should be in the question table (think EM expression, or mandatory or always hidden).
        • Then there are properties that are specific for a question type, for example the maximum / minimum number for a text question, or the step size for a slider. Storing these in the main table as separate column will make create lots of columns with very sparse data. Alternatives are EAV or serialization. Note: LS core will never do anything with this data except retrieve / store it, so serialization is a valid option.
        • Then there are generic question settings that someone might want to add support for, for example time_limit. There should be some way for plugins to add these settings to all questions and then handle their values when the time comes. -- This is something new and needs to be further explored.
    • Example of some part that can not be Question Type object, but must extend Question type
      • Updating EM/core system : example for hidden or random_group, validation (em_validation, numeric, min , max ...)
      • Updating HTML produced only maximum_chars, validation can update HTML part too : min+max+step on numeric for example (usage of type="number")
      • Add some JS/css or some HTML element (addContent, registerScript ....) time_limit
  • Composability: Should it be possible to combine two questions into one, or pick-and-choose among "question elements"?
  • Should we also change the way we save answer data? If yes, in which step - first or last? Which database design should we use instead?
  • Can we inherit views?
  • Interaction with plugins, e.g. Stata XML export? No other way around it than using switch in plugin, or each question type should itself supply a method the plugin can use?

Implementation

Object hierarchy.


Methods

This is a list of functions that question types could implement. They could be included in a "base interface" or abstract base class.


Function name Meaning Mandatory
renderFrontend Former qanda code; render question for survey taker Y
renderDataEntryForm Render form for manual insertion of data. NB: Same as renderFrontend? Y
getSettings Return settings (array) that can be used by a settings widget for edit form in backend Y
renderCustomSettings Possibly custom HTML settings?
getExportData Way of exporting : data + "syntax of data" : SPSS/Stata/triple-S/etc ... Some "settings" can update this too (interger value for numeric for example)
getStatisticsData Return statistics data that will be fed to a renderer (like HTML, PDF, etc) Y


Implemented as Yii module, with views, models and controllers (http://www.yiiframework.com/doc/guide/1.1/en/basics.module#creating-module).


Replace switch-cases in:

  • Frontend render (render answers, qanda)
  • Backend render (edit question, file questions.php)
  • Statistics
  • SQL and database queries, included extra field the question might define
  • Expression manager

Ideally, each switch case in the code should have a unit test or functional test to test it with.

Current question types

From statistics.php:

  • 1 - Array Dual Scale
  • 5 - 5 Point Choice
  • A - Array (5 Point Choice)
  • B - Array (10 Point Choice)
  • C - Array (Yes/No/Uncertain)
  • D - Date
  • E - Array (Increase, Same, Decrease)
  • F - Array (Flexible Labels)
  • G - Gender
  • H - Array (Flexible Labels) by Column
  • I - Language Switch
  • K - Multiple Numerical Input
  • L - List (Radio)
  • M - Multiple choice
  • N - Numerical Input
  • O - List With Comment
  • P - Multiple choice with comments
  • Q - Multiple Short Text
  • R - Ranking
  • S - Short Free Text
  • T - Long Free Text
  • U - Huge Free Text
  • X - Boilerplate Question
  • Y - Yes/No
  • ! - List (Dropdown)
  • : - Array (Flexible Labels) multiple drop down
  • ; - Array (Flexible Labels) multiple texts
  • | - File Upload


Categories:

  • Single-choice questions
  • Arrays
  • Mask questions
  • Text questions
  • Multiple-choice questions

Prototype/proof-of-concept

As a first cycle of development (iteration), one could implement a completely new question type (say, drawing board) with the new system on top of the old one. The purpose would be to get a better feeling of the problem domain, what changes are needed, if the current approach is possible, and so on.


After the prototype is done, the existent questions can be exported to the new system one by one. This represents a "horizontal" development style, where the complete problem is solved for _one_ case in the first cycle, in contrast to exporting the entire qanda to the new system, then the entire statistics module, etc. A horizontal style would give faster results and introduce lesser mechanical work, focusing on design and creativity which is needed in the beginning.


Example from qanda:


if (is_object($ia))
{
  $ia->renderFrontend();
}
else
{
    switch ($ia[4])  // $ia[4] is question type
    {
        case 'X': //BOILERPLATE QUESTION
            $values = do_boilerplate($ia);
            break;
        // and so on for all question types
    }
}


When all questions have been exported to the new system, this snippet will just be:


$ia->renderFrontend();

Current LS3 implementation

Note, this is the current state and some of these are mostly because it is a refactoring from LS2.

1. Uses an interface.

2. Defines what columns it needs via a fieldname => column type map.

3. Defines what EM expressions apply to it.

4. Defines a render functionality that renders the content.

5. Defines a list of classes that should be in the wrapping div.

Storing custom question attributes

Assumptions:
1. All questions are always (partly) stored in the `Questions` table.
2. There exist question attributes that are unknown (we don't know their names, their types and how many there are).
3. Known question attributes (like: mandatory) are always stored normalized as a column in the `Questions` table, the list below applies only to custom attributes.


Options for storing custom question attributes:

1. Single Table Inheritance (Add columns to `questions` table).
- Risk of collisions
- Harder to import backups into environments that slightly differ.
- Creates a very sparse table.
+ Fast for retrieving
+ Easy to support full model validation in Yii
+ Easy to interpret database / SQL exports.
o Full support for advanced queries / indexes (LS does not do advanced queries on this table)


2. Use EAV.
- Schemaless
- No data types (everything is a string)
+ Current approach.
+ Easy to interpret database / SQL exports.
o Little support for advanced queries (LS does not do advanced queries on this table)
+ Easy to support full model validation in Yii
- Requires transactions if we want to have atomic writes.

3. Use EBLOB - store question attributes in a serialized form like JSON / XML.
- Schemaless
o More datatypes than EAV: string, int, float, bool, NULL. Fewer datatypes than STI or CTI.
+ Fast for retrieving
+ Easy to support full model validation in Yii
o Little / slow support for advanced queries (LS does not do advanced queries on this table)
o Write amplification (Yii writes the whole record regardless of what columns changed)
- Unfriendly for direct database editing.


4.Class table inheritance (Separate table per question type containing a PK that is a FK into `Questions`)
+ Strict schema
+ Easy to interpret database / SQL exports.
- Requires transactions if we want to have atomic writes.
- Complicated to implement in Yii.
- Reading requires at least one query per question type, depending on implemention up to 1 query per question.