<img src=“https://codeclimate.com/github/reggieb/qwester.png” />
A rails engine used to add questionnaires to rails applications
Add this to your Gemfile
gem 'qwester'
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
To mount qwester within your rails application, add this to config/routes.rb:
mount Qwester::Engine => "/questionnaires"
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.
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.
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.
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
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.
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
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
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.
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)
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.
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.
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 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.
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.
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.
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.
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
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.
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