Alexandre Quiterio

Our new Rails Gem - Survey

Survey is a Rails Engine that brings quizzes, surveys and contests into your Rails application. Survey was initially extracted from a real application that includes contests and quizzes.
While we were building two of our latest client projects we found the need to have a reusable and generic way to integrate questionnaires, surveys and competitions into different application use cases.

Ever had the feeling you were creating something that already exists over and over again? In this case we did, so we extracted Survey from those projects. Like most good development shops we are both producers and consumers of open source, and we also love to write it in order to deliver nicer software quickly.

We wrote Survey because none of the existing gems did what we needed. Some of them are developer oriented, and had to be configured with YML files or in code, but we needed something backed by models that our end users could directly manage in the application's back-office.

How does it work?

Survey is organized as a typical questionnaire domain. On the top of the hierarchy we have the Survey class itself, with associated questions. Answers are related to surveys via Attempts, to model that more than one attempt is possible in some scenarios. The correct answer to a question is modeled via an Option class that relates Questions and Answers, allowing for different relations other than correctness in some custom scenarios. Depending on what you are dealing with you can assign a correct answer to the question and decide if the answers are valued with weights or not. In this model, Attempt objects have a collection of answers made by the participants. We wanted the model to be flexible enough that it can be linked with an existing object model and interact with it without imposing too much constraints.

The following UML diagram will tell you the infrastructure:

survey diagram

How can you use it?

So what kind of stuff can you make with survey? How can it make creating surveys easier ? Let’s begin:
Dealing with models - The easy way to create a relation between your target model (let’s say for example, model User) and Survey is to use the has_surveys ActiveRecord::Extension. Just that! Now the User will have a connection to Survey model and every user has a maximum number of attempts and a history log from the Survey Perspective. Survey will also track every new attempt to submit surveys, or deny it if the user already raise the maximum number of attempts to answer it.

1
2
3
class User < ActiveRecord::Base
  has_surveys
end

Ok, now our User class has a valid relationship with Survey. We can keep moving forward and think how we would create a sample survey:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# Let's build our own survey
my_survey = Survey::Survey.new do |survey|
  survey.name = "Star Wars Quiz" 
  survey.description = "The following questions are related with first movie of Star Wars"
  survey.attempts_number = 3
  survey.active = true
end

# Let's add some questions and options
question_1 = Survey::Question.new do |question|
  question.text = 'How many droids help Luke in the first movie?'
  # by default when we don't specify the weight of a option
  # its value is equal to one
  question.options = [
    Survey::Option.new(:text => '1 droid',  :correct => false),
    Survey::Option.new(:text => '2 droids', :correct => true),
    Survey::Option.new(:text => '3 droids', :correct => false)
  ]
end

question_2 = Survey::Question.new do |question|
  question.text = 'Who is the evil guy?'
  question.options = [
    Survey::Option.new(:text => 'Darth Vader', :weight => 100),
    Survey::Option.new(:text => 'Obi Wan',     :weight => 0),
    Survey::Option.new(:text => 'Jabba',       :weight => 50)
  ]
end

my_survey.questions = [question_1, question_2]
my_survey.save!

# Let's answer it with User 1
attempt = Attempt.new(:survey => my_survey, :participant => user_1)
answer_1 = Survey::Answer.new(:option => question_1.options.first )
answer_2 = Survey::Answer.new(:option => question_2.options.first )
attempt.answers = [answer_1, answer_2]
attempt.save
# Let's answer it with User 2
attempt = Attempt.new(:survey => my_survey, :participant => user_2)
answer_1 = Survey::Answer.new(:option => question_1.options.first )
answer_2 = Survey::Answer.new(:option => question_2.options.last )
attempt.answers = [answer_1, answer_2]
attempt.save

# Let's pull some metrics

survey = Survey::Survey.active.first

# select all the attempts from this survey
survey_answers = survey.attempts

# check the highest score for user 2
user_2_highest_score  = survey_answers.for_participant(user_2).high_score
puts user_2_highest_score
=> 51

#check the highest score made for this survey
global_highest_score = survey_answers.high_score
puts global_highest_score
=> 101

One great thing to do is to use the data collected by the survey and create metrics with it. For example you can see how many users already spent their valid number of attempts or for example, see how many correct answers the user has, make a sum of answer weights, or something else entirely. You can define your own strategies to collect metrics around the user's answers. Also, any model, not only the User model, can respond to surveys.

Scaffold framework integration

Survey offers you a basic structure of different controllers and views that can be generated using a Rails generator. If you are using RailsAdmin or ActiveAdmin templates are included to make building backoffice pages for Survey easy. Depending on your needs, you can use one of the following rake tasks.

For ActiveAdmin:

1
rails generate survey active_admin

If you want a traditional rails controller you can execute the following rake task with a namespace contests (this variable is optional):

1
Rails generate survey plain namespace:contests

These generators will make building your Survey backoffice easier.

What is it good for?

Survey is a great choice when you have the concept of questionnaire or contest in your domain model. It is also good when you are using RailsAdmin or ActiveAdmin because it has default templates that you can customize according to your needs, in other words you don’t need to make everything from beginning, because Survey takes care of the basics for you.
Survey has the ability to assign different weights to the questionnaire’s questions; this is quite good when you are dealing with tests or feedback surveys.
Survey controls the number of attempts made by each participant. You can define a maximum number of attempts per questionnaire and restrict the submission to those who have a valid number of attempts. Here's one simple and straightforward way of doing it:

1
Survey::Survey.new(:name => "Star Wars Quiz", :attempts_number => 4)

Survey in the general case can be valuable if you want to collect metrics from surveys afterwards. Although, because many times surveys have their particular form and style, also relevant metrics diverge from project to project, so Survey provides the basic data, and you have to use that data to extract your own goals and metrics.

More Information

For more information about how to use the Survey Gem please visit the README file on the Github Repository.

What's Next

We wanted to enable end users to create surveys and other questionnaires without constantly depending on developers. Maybe some day Survey will grow, and someone will use it to build other cool open source projects :)

Survey was built on Rails by the Runtime Revolution team. Find us on Twitter if you have questions, ideas, and grab the source code on GitHub right now! Keep an eye on our Survey Roadmap and if you have new ideas let us know.

If you find Survey interesting, also check out other Runtime Revolution's Open-Source Projects.

Comments