Ruby-based DSL for Authoring Quiz Questions
This is a simple gem that takes a set of questions (a "quiz") written in RuQL ("Ruby quiz language" or "Ruby question language" - a DSL embedded in Ruby), and produces one of several possible output formats.
Some types of questions or question elements that can be expressed in RuQL cannot be expressed by some LMSs, and some LMS-specific question features cannot be expressed in RuQL.
Registered ESaaS instructors can access this repo of questions, grouped by chapter of the ESaaS textbook.
Installation
gem install ruql
to install this from RubyGems. It works with Ruby 1.9 or later.
You'll also need to install one or more formatters to produce quiz output.
Installation: Current Formatters
- ruql-html: produces HTML 5 output using a default or user-supplied HTML template
- ruql-canvas: creates a quiz in Canvas LMS using its REST API
- ruql-gradescope: allows formatting RuQL quizzes for use with Gradescope.
- ruql-prairielearn: creates PrairieLearn questions out of each RuQL question
- ruql-olx: creates a quiz in edX's Open Learning XML
- ruql-json: allows formatting RuQL quizzes as simple JSON objects. This capability is mostly used by Course Question Bank, which is deprecated.
Running RuQL
Create your quiz.rb
file as described below, then run:
ruql formatter-name quiz.rb
Where formatter-name
is from the list above, eg html
for the
ruql-html
formatter.
ruql formatter-name -h
lists the options supported by that
formatter, and ruql -h
shows RuQL's global options.
Creating Quiz Questions in RuQL
RuQL supports a few different types of short-answer questions and can output them in a variety of formats compatible with different Learning Management Systems or in printable form.
RuQL is a DSL embedded in Ruby, so you can include expressions in questions, for example, to generate simple variants of a question along with its solution.
In addition to the basic parts of a question (prompt text, answers, etc.), each question can also have an optional list of one or more tags, and an optional comment. These attributes are available to formatters, but most formatters ignore them. They can be used to provide information about the question's topic(s) or other info useful to a question-management app such as Course Question Bank.
A question can also optionally have a group. Questions with the same group name (within the scope of a single quiz) are grouped in a "pool" from which a single question is selected at quiz generation time. For "static" formatters such as HTML, RuQL will pick a random question. For some LMSs, like Canvas, the questions are put into a "quiz group" and any given student gets a randomly chosen one at runtime.
Preparing a quiz
A quiz is an .rb
file with a collection of questions:
quiz 'Example quiz name here' do
# (questions here)
end
You create a quiz by putting the quiz in its own file and copying-and-pasting the questions you want into it. (Yes, that's ugly. Soon, questions will have unique IDs and you'll be able to create a quiz by reference.)
Multiple-choice questions with a single correct answer
You can provide a generic explanation
clause, and/or override it with
specific explanations to accompany right or wrong answers.
Choices are rendered in the order in which
they appear in the RuQL markup, but capable LMSs or formatters can be
told to randomize them. This example also shows the use of tags to
include metadata about a question's topic, and the use of groups to
place two questions into a group from which one will be chosen at
random for the quiz.
choice_answer do
tags 'US states', 'geography'
group 'states-1'
text "What is the largest US state?"
explanation "Not big enough." # for distractors without their own explanation
answer 'Alaska'
distractor 'Hawaii'
distractor 'Texas', :explanation => "That's pretty big, but think colder."
end
choice_answer do
tags 'US cities', 'geography'
group 'states-1'
text "What is the largest US city?"
answer 'New York'
distractor 'Los Angeles'
distractor 'Chicago'
end
Specifying :raw => true
allows HTML markup in the question to be
passed through unescaped, such as for <pre>
or <code>
blocks.
choice_answer :raw => true do
text %Q{What does the following code do:
<pre>
puts "Hello world!"
</pre>
}
distractor 'Throws an exception', :explanation => "Don't be an idiot."
answer 'Prints a friendly message'
end
Multiple-choice "select all that apply" questions
These use the same syntax as single-choice questions, but multiple
answer
clauses are allowed:
select_multiple do
text "Which are American political parties?"
answer "Democrats"
answer "Republicans"
answer "Greens", :explanation => "Yes, they're a party!"
distractor "Tories", :explanation => "They're British"
distractor "Social Democrats"
end
True or false questions
Internally, true/false questions are treated as a special case of multiple-choice questions with a single correct answer, but there's a shortcut syntax for them.
truefalse 'The week has 7 days.', true
truefalse 'The earth is flat.', false, :explanation => 'No, just looks that way'
Short-answer fill-in-the-blanks questions
Put three or more hyphens in a row where you want the "blanks" to be,
and provide a string or regexp to check the answer; all regexps are
case-insensitive unless :case_sensitive => true
is passed.
fill_in :points => 2 do
text 'The capital of California is ---.'
answer 'sacramento'
end
Optional distractors can capture common incorrect answers. As with all
question types, an optional :explanation
can accompany a correct
answer or a distractor; its usage varies with the LMS, but a typical use
is to display a hint if the wrong answer is given, or to display
explanatory text accompanying the correct answer.
fill_in do
text 'The visionary cofounder of Apple and NeXT is ---'
answer /^ste(ve|phen)\s+jobs$/
distractor /^steve\s+wozniak/, :explanation => 'Almost, but not quite.'
end
You can have multiple blanks per question and pass an array of regexps
or strings to check them. Passing :order => false
means that the
order in which blanks are filled doesn't matter. The number of elements
in the array must exactly match the number of blanks.
fill_in do
text 'The --- brown fox jumped over the lazy ---'
answer [/fox/, /dog/], :explanation => 'This sentence contains all of the letters of the English Alphabet'
end
fill_in do
text 'The three stooges are ---, ---, and ---.'
answer %w(larry moe curly), :order => false
end
Questions with one or more dropdown-menu choices
A question can consist of one or more dropdown menus and interstitial text (which is rendered verbatim without newlines,
so use :raw => true
to embed <br>
tags if you want breaks). A choice selector needs
two arguments: the 0-based index of the correct choice, and an array of strings of all the choices.
The label
method provides interstitial text that separates the dropdowns.
dropdown do
text "Arguably the world's first theme park was"
choice 0, ['Disneyland', 'Mickey World', 'Teenage Mutant Ninja Turtles Park']
label "located in"
choice 2, ['Beijing', 'Paris', 'California']
end
Additional arguments and options
The following arguments and options have different behavior (or no effect on behavior) depending on what format questions are emitted in:
-
All question types accept a
:name => 'something'
argument, which some output generators use to create a displayable name for the question or to identify it within a group of questions. -
The optional
tag
clause is followed by a string or array of strings, and associates the given tag(s) with the question, in anticipation of future tools that can use this information. -
The optional
comment
clause is followed by a string and allows a free-text comment to be added to a question. -
The optional
group
clause, which takes a single string, names a "pool" of questions of which this question is a part. Some formatters such as the Canvas importer will turn this into a "quiz question group" so that one question from the pool will be randomly chosen to show the student. See each formatter's documentation for how/whether it uses this property.
Adding your own formatter
This documentation is incomplete
If you're creating the foobar
formatter, the gem should
be named ruql-foobar
and have the following directory
structure (heavily recommend using Bundler):
ruql-foobar:
.
├── CODE_OF_CONDUCT.md
├── Gemfile
├── README.md
├── Rakefile
├── bin
│ ├── console
│ └── setup
├── lib
│ └── ruql
│ ├── foobar
│ │ ├── foobar.rb
│ │ └── version.rb
│ └── foobar.rb
├── result.json
├── ruql-foobar.gemspec
├── spec
│ ├── ruql
│ │ └── foobar_spec.rb
│ └── spec_helper.rb
└── templates
└── quiz.json
The main entry point should be in
ruql-foobar/lib/ruql/foobar/foobar.rb
and include the following at a
minimum:
module Ruql
class Foobar
def initialize(quiz, options={})
# initialize yourself, given a Quiz object and command-line
# options in Getoptlong format
end
def self.allowed_options
opts = [['--foobar-option1', Getoptlong::REQUIRED_ARGUMENT],
['--foobar-option2', Getoptlong::BOOLEAN_ARGUMENT]]
help_text = "Some text describing what the options do"
return [help_text, opts]
end
def render_quiz
# render your quiz however you like. See the descriptions of
# the Quiz class and various subclasses of Question and Answer.
end
end
end