<img src=“https://codeclimate.com/github/reggieb/qwester.png” />

Qwester

A rails engine used to add questionnaires to rails applications

Installation

Add this to your Gemfile

gem 'qwester'

Migrations

Qwester includes a set of migrations to build the database tables to match qwester’s models. To use these migrations within the host application, run this rake task:

rake qwester:install:migrations

This will copy the migrations to the host application’s db/migrate folder. They will then be run next time the db:migrate task is run:

rake db:migrate

If you only wish to run the qwester migrations use a scope option:

rake db:migrate SCOPE=qwester

Mounting the engine

To mount qwester within your rails application, add this to config/routes.rb:

mount Qwester::Engine => "/questionnaires"

Questionnaire

To create a new questionnaire, first create a set of questions that you wish to appear within the questionnaire. Then create a questionnaire and associate the questions with that questionnaire. Questions can be used in multiple questionnaires, and can be ordered within each questionnaire. That is, the first question in one questionnaire, can be displayed as the second question in another questionnaire.

Questions and Answers

Each question has a number of answers associated with it. The default set of answers is given by:

Qwester::Answer.standard_values

However, these can be modified and added to as you require. That is, each question has a unique set of answers that can be modified as required.

Answer selection list helper

The helper method qwester_answers_selection_list will output a set of form elements matching the answers for the given question, as an unordered list. The form elements will be radio buttons unless the question is marked as ‘multi-answer’, in which case check boxes will be used.

<%= qwester_answers_selection_list(question) %>

The controller method update_qwester_answer_store can be used to manage the data submitted from these form elements. It will add the answers to the current answer store.

Answer store

Submitted answers are stored in an AnswerStore object. On creation, each answer_store is assigned a session_id that can then be used within session, to identify the current user’s answer_store. In this way, questionnaire submissions can be tracked across multiple submissions.

By default the current answer_store.session_id is stored in session. If you wish to use a different session key, set Qwester.session_key in the qwester initializer:

Qwester.session_key = :your_preferred_key

Preservation

To preserve an answer store, call its preserve method:

preserved_answer_store = answer_store.preserve

This will create a copy of the original answer_store. This copy will have its own unique session_id. This session_id can then be used restore the answer_store at a later date. The answer_store#preserved field stores the datetime when it was preserved.

preserved_answer_store.preserved? == true
preserved_answer_store.preserved  --> datetime of preservation

The answers and questionnaires are copied over to the preserved answers store

answer_store.questionnaires == preserved_answer_store.questionnaires
answer_store.answers        == preserved_answer_store.answers

Note that it is the answer store copy that is preserved, and not the original

answer_store.preserved? == false

A preserved answer store therefore acts as a snap shot of an answer_store.

Restoration

Restoring a preserved answer_store, creates a new copy of the answer store. This restored copy can then be used without alteration of the preserved snap shot. The restored copy will also have its own session_id.

restored_answer_store = preserved_answer_store.restore
restored_answer_store.preserved?     == false
restored_answer_store.questionnaires == preserved_answer_store.questionnaires
restored_answer_store.answers        == preserved_answer_store.answers

Clean up answer stores

A rake task is available that will remove all the unpreserved answer stores that are more than a day old.

rake qwester:destroy_unpreserved_answer_stores RAILS_ENV=production

Rule set

Groups of answers can be matched to rule sets using:

RuleSet.matching(answers)

which will return an array of rule sets that match those answers.

Each rule set has an url associated with it, and this url should lead a user to a resource either within or outside the app.

RuleSet uses array_logic to manage the logic used to compare each rule set with the array of answers.

Presentation

Questionnaires can be grouped into Presentations, and these are used to control which questionnaires are displayed (presented to the user) at any time.

All questionnaires are display at the engine root unless:

  • A presentation is set as ‘default’

  • An existing presentation’s name is added to session (an array)

Restricting the questionnaires displayed

If you do not want to display all the available questionnaires, create a presentation, set it as ‘default’ and add to it the questionnaires you wish to display.

Using a questionnaire to control access to other questionnaires

Say you have three presentations of questionnaires, and you want to display one initially. Then you want to use the answers submitted from that questionnaire, to control which of the other two sets of questionnaires are displayed next.

To do this set the initial presentation as default. Then create two rule sets, one for each of the other presentations. Update each rule set to match the answer pattern that should be submitted for the associated pattern to be displayed, and set rule_set#presentation to the name of the presentation you wish displayed when the rule is matched.

Then when the first set of questionnaires are submitted, the rules will be checked, and if one of the two rules sets matches the submitted answers, the associated presentation’s questionnaires will be displayed.

Simple rule matching for presentations

The rule matching is simple and if two presentation rules match, the system will not try to work out which one it should display. It will just show the last one it finds. So some care is required when setting up presentations with matching rule sets to avoid overlaps and clashes.

Once only match

Once a presentation has been matched, it cannot be returned to later. That is if there are two presentations: ‘one’ and ‘two’, you cannot have a work flow that goes from one to two and then back to one, unless you reset or otherwise manipulate session. Instead you should clone ‘one’ as a new presentation e.g. ‘three’, and then go from one to two to three.

Answer weighting

Answers have a weighting field (float) to give selected answers more weight when comparing answers. You can also define an alias for this field, to give it a name that is more appropriate to your application.

For example, you want to use qwester to create a multiple choice test, and each answer needs to be assigned a ‘score’. First you will need to assign :score as the weighting alias. In an initializer add this:

Qwester::Answer.weighting_alias = :score

This will add a ‘score’ instance method to Answers, that will return the weighting for that answer.

Then apply a score/weighting to each correct answer in the database.

You can then set up a rule to work with the score method. For example:

rule_set.rule = 'sum(:score) >= 10'

See array_logic for a list of functions available.

The default value of weighting is zero.

Dummy

A test app is present within this engine, and provides an example of how Qwester can be used within a Rails app. See test/dummy.

However, look at the notes in Gemfile. You may need to uncomment two gem declarations in the gemfile, before jquery and active_admin work correctly in the test/dummy environment.

Testing

Note that the test datebase is not updated when running the root tests.

To create the test database run the following at test/dummy

rake db:schema:load RAILS_ENV=test

To keep the test database in step with new migrations, at test/dummy run

rake db:migrate RAILS_ENV=test

To run all the tests, run rake test at the engine root.

Integration with ActiveAdmin

Qwester contains a set of ActiveAdmin register files, that allow Qwester models to be managed from within the parent app’s active_admin space. Of course ActiveAdmin needs to be installed and working in the parent rails application, for this to work.

To use the Qwester ActiveAdmin register files, add this to the active_admin initializer in your application.

config.load_paths << Qwester.active_admin_load_path

See test/dummy/config/initializers/active_admin.rb for an example

Local modifications of qwester active admin pages

If you wish to over-ride some of Qwester’s active admin registers you will need to reorder the active_admin load_paths. In this case, use this form of the load_paths declaration:

config.load_paths = [Qwester.active_admin_load_path] + config.load_paths

One side-effect of this that I have been unable to solve, is that if you modify an over-riding register file while the app is running, the load order is altered and the qwester register file over-rides the local app’s register file until the app is restarted. Therefore, you have to restart the app before modifications to over-riding register files take effect. In practice this only affects the development environment, as in both test and production the app is restarted after a change.

Active admin menus

Links to the admin pages for Qwester models will appear in a ‘Qwester’ sub-menu. If you wish to change the name of the menu parent group, add this to an initializer:

Qwester.active_admin_menu = 'menu name'

Alternatively, if you want the Qwester models not to be grouped add this to an initializer:

Qwester.active_admin_menu = 'none'

See test/dummy/config/initializers/qwester.rb for an example