survey 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +9 -0
- data/MIT-LICENSE +20 -0
- data/README.md +194 -0
- data/Rakefile +30 -0
- data/app/models/survey/answer.rb +34 -0
- data/app/models/survey/attempt.rb +60 -0
- data/app/models/survey/option.rb +34 -0
- data/app/models/survey/question.rb +24 -0
- data/app/models/survey/survey.rb +49 -0
- data/config/locales/en.yml +36 -0
- data/config/locales/pt-PT.yml +48 -0
- data/config/locales/pt.yml +48 -0
- data/lib/generators/survey/install_generator.rb +20 -0
- data/lib/generators/survey/survey_generator.rb +102 -0
- data/lib/generators/templates/active_admin.rb +46 -0
- data/lib/generators/templates/attempts_plain.rb +69 -0
- data/lib/generators/templates/attempts_views/_form.html.erb +18 -0
- data/lib/generators/templates/attempts_views/new.html.erb +11 -0
- data/lib/generators/templates/helper.rb +44 -0
- data/lib/generators/templates/migration.rb +56 -0
- data/lib/generators/templates/rails_admin.rb +3 -0
- data/lib/generators/templates/survey_plain.rb +54 -0
- data/lib/generators/templates/survey_views/_form.html.erb +61 -0
- data/lib/generators/templates/survey_views/_option_fields.html.erb +9 -0
- data/lib/generators/templates/survey_views/_question_fields.html.erb +15 -0
- data/lib/generators/templates/survey_views/edit.html.erb +2 -0
- data/lib/generators/templates/survey_views/index.html.erb +16 -0
- data/lib/generators/templates/survey_views/new.html.erb +2 -0
- data/lib/survey.rb +15 -0
- data/lib/survey/active_record.rb +28 -0
- data/lib/survey/engine.rb +6 -0
- data/lib/survey/version.rb +3 -0
- metadata +156 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 88c7e715f08bd4533d85638100a971c263eac2c5
|
4
|
+
data.tar.gz: e944022847b8b42b9ac60a5e9ceaf3d22bfbc20d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 33b982f786f59f8d90d9f1322d86fa4869dc1d8027a09014b515555c3617fdced414247e30baa55e59168699257695a82e74fb772395110077aa0ce069939736
|
7
|
+
data.tar.gz: 5c58ab3d53e5969e3dce66ada574cfe2b22dad1ac7217904a289176415162c6e8ff61fff63d7969e465f3c577288082fad68c55b42a140582a26cd42b628a69b
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 Runtime Revolution
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
# Survey
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/runtimerevolution/survey.png?branch=master)](https://travis-ci.org/runtimerevolution/survey)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/runtimerevolution/survey.png)](https://codeclimate.com/github/runtimerevolution/survey)
|
5
|
+
### Surveys on Rails...
|
6
|
+
|
7
|
+
Survey is a Rails Engine that brings quizzes, surveys and contests into your Rails
|
8
|
+
application. Survey models were designed to be flexible enough in order to be extended and
|
9
|
+
integrated with your own models. Survey was initially extracted from a real application that handles contests and quizzes.
|
10
|
+
|
11
|
+
## Documentation
|
12
|
+
|
13
|
+
You can view the Survey documentation in RDoc format here:
|
14
|
+
|
15
|
+
http://rubydoc.info/github/runtimerevolution/survey/frames
|
16
|
+
|
17
|
+
## Main Features:
|
18
|
+
- Surveys can limit the number of attempts for each participant
|
19
|
+
- Questions can have multiple answers
|
20
|
+
- Answers can have different weights
|
21
|
+
- Base Scaffold Support for Active Admin, Rails Admin and default Rails Controllers
|
22
|
+
- Base calculation for scores
|
23
|
+
- Easy integration with your project
|
24
|
+
|
25
|
+
## Installation
|
26
|
+
|
27
|
+
Add survey to your Gemfile:
|
28
|
+
```ruby
|
29
|
+
gem 'survey', :git => 'git://github.com/runtimerevolution/survey.git'
|
30
|
+
|
31
|
+
```
|
32
|
+
Then run bundle to install the Gem:
|
33
|
+
```sh
|
34
|
+
bundle install
|
35
|
+
```
|
36
|
+
Now generate and run migrations:
|
37
|
+
```sh
|
38
|
+
rails generate survey:install
|
39
|
+
|
40
|
+
bundle exec rake db:migrate
|
41
|
+
```
|
42
|
+
|
43
|
+
## Getting started with Survey
|
44
|
+
|
45
|
+
## Survey inside your models
|
46
|
+
To make a model aware of you just need to add `has_surveys` on it:
|
47
|
+
```ruby
|
48
|
+
class User < ActiveRecord::Base
|
49
|
+
has_surveys
|
50
|
+
|
51
|
+
#... (your code) ...
|
52
|
+
end
|
53
|
+
```
|
54
|
+
There is the concept of participant, in our example we choose the User Model.
|
55
|
+
Every participant can respond to surveys and every response is registered as a attempt.
|
56
|
+
By default, survey logic assumes an infinite number of attempts per participant
|
57
|
+
but if your surveys need to have a maximum number of attempts
|
58
|
+
you can pass the attribute `attempts_number` when creating them.
|
59
|
+
```ruby
|
60
|
+
# Each Participant can respond 4 times this survey
|
61
|
+
Survey::Survey.new(:name => "Star Wars Quiz", :description => "A quiz about Star Wars", :attempts_number => 4)
|
62
|
+
```
|
63
|
+
## Surveys used in your controllers
|
64
|
+
In this example we are using the current_user helper
|
65
|
+
but you can do it in the way you want.
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
class ContestsController < ApplicationController
|
69
|
+
|
70
|
+
helper_method :survey, :participant
|
71
|
+
|
72
|
+
# create a new attempt to this survey
|
73
|
+
def new
|
74
|
+
@attempt = survey.attempts.new
|
75
|
+
# build a number of possible answers equal to the number of options
|
76
|
+
survey.questions.size.times { @attempt.answers.build }
|
77
|
+
end
|
78
|
+
|
79
|
+
# create a new attempt in this survey
|
80
|
+
# an attempt needs to have a participant assigned
|
81
|
+
def create
|
82
|
+
@attempt = survey.attempts.new(params[:attempt])
|
83
|
+
# ensure that current user is assigned with this attempt
|
84
|
+
@attempt.participant = participant
|
85
|
+
if @attempt.valid? and @attempt.save
|
86
|
+
redirect_to contests_path
|
87
|
+
else
|
88
|
+
render :action => :new
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def participant
|
93
|
+
@participant ||= current_user
|
94
|
+
end
|
95
|
+
|
96
|
+
def survey
|
97
|
+
@survey ||= Survey::Survey.active.first
|
98
|
+
end
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
## Survey inside your Views
|
103
|
+
|
104
|
+
### Controlling Survey avaliability per participant
|
105
|
+
To control which page participants see you can use method `avaliable_for_participant?`
|
106
|
+
that checks if the participant already spent his attempts.
|
107
|
+
```erb
|
108
|
+
<% if @survey.avaliable_for_participant?(@participant) %>
|
109
|
+
<%= render 'form' %>
|
110
|
+
<% else %>
|
111
|
+
Uupss, <%= @participant.name %> you have reach the maximum number of
|
112
|
+
attempts for <%= @survey.name %>
|
113
|
+
<% end %>
|
114
|
+
|
115
|
+
<% # in _form.html.erb %>
|
116
|
+
<%= form_for [:contests, @attempt] do |f| %>
|
117
|
+
<%= f.fields_for :answers do |builder| %>
|
118
|
+
<ul>
|
119
|
+
<% @survey.questions.each do |question| %>
|
120
|
+
<li>
|
121
|
+
<p><%= question.text %></p>
|
122
|
+
<%= builder.hidden_field :question_id, :value => question.id %>
|
123
|
+
<% question.options.each do |option| %>
|
124
|
+
<%= builder.check_box :option_id, {}, option.id, nil %>
|
125
|
+
<%= option.text %> <br/ >
|
126
|
+
<% end -%>
|
127
|
+
</li>
|
128
|
+
<% end -%>
|
129
|
+
</ul>
|
130
|
+
<% end -%>
|
131
|
+
<%= f.submit "Submit" %>
|
132
|
+
<% end -%>
|
133
|
+
```
|
134
|
+
|
135
|
+
### Scaffolds and CRUD frameworks
|
136
|
+
If you are using Rails Admin or Active Admin, you can generate base CRUD screens for Survey with:
|
137
|
+
```sh
|
138
|
+
rails generate survey active_admin
|
139
|
+
|
140
|
+
rails generate survey rails_admin
|
141
|
+
```
|
142
|
+
If you want a simple way to get started you can use the `plain` option which is a simple Rails scaffold to generate the controller and views related with survey logic.
|
143
|
+
By default when you type `rails g survey plain` it generates a controller in the `admin` namespace but you can choose your own namespace as well:
|
144
|
+
```sh
|
145
|
+
rails generate survey plain namespace:contests
|
146
|
+
```
|
147
|
+
|
148
|
+
By default when you generates your controllers using the `plain` command the task
|
149
|
+
generates the associated routes as well.
|
150
|
+
Afterwards if you want to generate more routes, you can using the command:
|
151
|
+
|
152
|
+
```sh
|
153
|
+
rails generate survey routes namespace:admin
|
154
|
+
```
|
155
|
+
|
156
|
+
## How to use it
|
157
|
+
Every user has a collection of attempts for each survey that he respond to. Is up to you to
|
158
|
+
make averages and collect reports based on that information.
|
159
|
+
What makes Survey useful is that all the logic behind surveys is now abstracted and well integrated,
|
160
|
+
making your job easier.
|
161
|
+
|
162
|
+
## Hacking with Survey through your Models:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
# select the first active Survey
|
166
|
+
survey = Survey::Survey.active.first
|
167
|
+
|
168
|
+
# select all the attempts from this survey
|
169
|
+
survey_answers = survey.attempts
|
170
|
+
|
171
|
+
# check the highest score for current user
|
172
|
+
user_highest_score = survey_answers.for_participant(@user).high_score
|
173
|
+
|
174
|
+
#check the highest score made for this survey
|
175
|
+
global_highest_score = survey_answers.high_score
|
176
|
+
```
|
177
|
+
# Compability
|
178
|
+
### Rails
|
179
|
+
Survey supports Rails 3 and 4. For use in Rails 4. Rails 4 support is recent, so some minor issues may still be present,
|
180
|
+
please report them.
|
181
|
+
|
182
|
+
### Active Admin
|
183
|
+
Only support versions of Active Admin higher than 0.3.1.
|
184
|
+
|
185
|
+
# Roadmap
|
186
|
+
|
187
|
+
- Add a form builder or a helper to improve the creation of Survey forms.
|
188
|
+
- Add polymorphic relations to help the survey be extended with subclasses.
|
189
|
+
- Allow adding new fields without breaking the existent logic.
|
190
|
+
|
191
|
+
# License
|
192
|
+
Copyright © 2013 [Runtime Revolution](http://www.runtime-revolution.com), released under the MIT license.
|
193
|
+
|
194
|
+
[![githalytics.com alpha](https://cruel-carlota.pagodabox.com/59be37fe81712a1a4dadc798325a30ee "githalytics.com")](http://githalytics.com/runtimerevolution/survey)
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rake'
|
10
|
+
require 'rdoc/task'
|
11
|
+
|
12
|
+
require 'rake/testtask'
|
13
|
+
|
14
|
+
Rake::TestTask.new(:test) do |t|
|
15
|
+
t.libs << 'lib'
|
16
|
+
t.libs << 'test'
|
17
|
+
t.pattern = 'test/**/*_test.rb'
|
18
|
+
t.verbose = true
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => :test
|
22
|
+
|
23
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
24
|
+
rdoc.rdoc_dir = 'rdoc'
|
25
|
+
rdoc.title = 'Survey'
|
26
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
27
|
+
rdoc.rdoc_files.include('README.md')
|
28
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
29
|
+
rdoc.rdoc_files.include('app/**/*.rb')
|
30
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Survey::Answer < ActiveRecord::Base
|
2
|
+
|
3
|
+
self.table_name = "survey_answers"
|
4
|
+
|
5
|
+
acceptable_attributes :attempt, :option, :correct, :option_id, :question, :question_id
|
6
|
+
|
7
|
+
# associations
|
8
|
+
belongs_to :attempt
|
9
|
+
belongs_to :option
|
10
|
+
belongs_to :question
|
11
|
+
|
12
|
+
# validations
|
13
|
+
validates :option_id, :question_id, :presence => true
|
14
|
+
validates :option_id, :uniqueness => { :scope => [:attempt_id, :question_id] }
|
15
|
+
|
16
|
+
# callbacks
|
17
|
+
after_create :characterize_answer
|
18
|
+
|
19
|
+
def value
|
20
|
+
points = (self.option.nil? ? Survey::Option.find(option_id) : self.option).weight
|
21
|
+
correct?? points : - points
|
22
|
+
end
|
23
|
+
|
24
|
+
def correct?
|
25
|
+
self.correct ||= self.option.correct?
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def characterize_answer
|
31
|
+
update_attribute(:correct, option.correct?)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class Survey::Attempt < ActiveRecord::Base
|
2
|
+
|
3
|
+
self.table_name = "survey_attempts"
|
4
|
+
|
5
|
+
acceptable_attributes :winner, :survey, :survey_id,
|
6
|
+
:participant,
|
7
|
+
:participant_id,
|
8
|
+
:answers_attributes => ::Survey::Answer::AccessibleAttributes
|
9
|
+
|
10
|
+
# relations
|
11
|
+
belongs_to :survey
|
12
|
+
belongs_to :participant, :polymorphic => true
|
13
|
+
has_many :answers, :dependent => :destroy
|
14
|
+
accepts_nested_attributes_for :answers,
|
15
|
+
:reject_if => ->(q) { q[:question_id].blank? || q[:option_id].blank? }
|
16
|
+
|
17
|
+
# validations
|
18
|
+
validates :participant_id, :participant_type, :presence => true
|
19
|
+
validate :check_number_of_attempts_by_survey
|
20
|
+
|
21
|
+
#scopes
|
22
|
+
scope :wins, -> { where(:winner => true) }
|
23
|
+
scope :looses, -> { where(:winner => false) }
|
24
|
+
scope :scores, -> { order("score DESC") }
|
25
|
+
scope :for_survey, ->(survey) { where(:survey_id => survey.id) }
|
26
|
+
scope :exclude_survey, ->(survey) { where("NOT survey_id = #{survey.id}") }
|
27
|
+
scope :for_participant, ->(participant) {
|
28
|
+
where(:participant_id => participant.try(:id), :participant_type => participant.class)
|
29
|
+
}
|
30
|
+
|
31
|
+
# callbacks
|
32
|
+
before_create :collect_scores
|
33
|
+
|
34
|
+
def correct_answers
|
35
|
+
return self.answers.where(:correct => true)
|
36
|
+
end
|
37
|
+
|
38
|
+
def incorrect_answers
|
39
|
+
return self.answers.where(:correct => false)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.high_score
|
43
|
+
return scores.first.score
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def check_number_of_attempts_by_survey
|
49
|
+
attempts = self.class.for_survey(survey).for_participant(participant)
|
50
|
+
upper_bound = self.survey.attempts_number
|
51
|
+
|
52
|
+
if attempts.size >= upper_bound && upper_bound != 0
|
53
|
+
errors.add(:survey_id, "Number of attempts exceeded")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def collect_scores
|
58
|
+
self.score = self.answers.map(&:value).reduce(:+) || 0
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Survey::Option < ActiveRecord::Base
|
2
|
+
|
3
|
+
self.table_name = "survey_options"
|
4
|
+
|
5
|
+
acceptable_attributes :text, :correct, :weight
|
6
|
+
|
7
|
+
#relations
|
8
|
+
belongs_to :question
|
9
|
+
|
10
|
+
# validations
|
11
|
+
validates :text, :presence => true, :allow_blank => false
|
12
|
+
|
13
|
+
# scopes
|
14
|
+
scope :correct, -> { where(:correct => true) }
|
15
|
+
scope :incorrect, -> { where(:correct => false) }
|
16
|
+
|
17
|
+
# callbacks
|
18
|
+
before_create :default_option_weight
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
return self.text
|
22
|
+
end
|
23
|
+
|
24
|
+
def correct?
|
25
|
+
return (self.correct == true)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def default_option_weight
|
31
|
+
self.weight = 1 if correct? && self.weight == 0
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Survey::Question < ActiveRecord::Base
|
2
|
+
|
3
|
+
self.table_name = "survey_questions"
|
4
|
+
|
5
|
+
acceptable_attributes :text, :survey, :options_attributes => Survey::Option::AccessibleAttributes
|
6
|
+
|
7
|
+
# relations
|
8
|
+
belongs_to :survey
|
9
|
+
has_many :options, :dependent => :destroy
|
10
|
+
accepts_nested_attributes_for :options,
|
11
|
+
:reject_if => ->(a) { a[:text].blank? },
|
12
|
+
:allow_destroy => true
|
13
|
+
|
14
|
+
# validations
|
15
|
+
validates :text, :presence => true, :allow_blank => false
|
16
|
+
|
17
|
+
def correct_options
|
18
|
+
return options.correct
|
19
|
+
end
|
20
|
+
|
21
|
+
def incorrect_options
|
22
|
+
return options.incorrect
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Survey::Survey < ActiveRecord::Base
|
2
|
+
|
3
|
+
self.table_name = "survey_surveys"
|
4
|
+
|
5
|
+
acceptable_attributes :name, :description,
|
6
|
+
:finished,
|
7
|
+
:active,
|
8
|
+
:attempts_number,
|
9
|
+
:questions_attributes => Survey::Question::AccessibleAttributes
|
10
|
+
|
11
|
+
# relations
|
12
|
+
has_many :attempts, :dependent => :destroy
|
13
|
+
has_many :questions, :dependent => :destroy
|
14
|
+
accepts_nested_attributes_for :questions,
|
15
|
+
:reject_if => ->(q) { q[:text].blank? },
|
16
|
+
:allow_destroy => true
|
17
|
+
|
18
|
+
# scopes
|
19
|
+
scope :active, -> { where(:active => true) }
|
20
|
+
scope :inactive, -> { where(:active => false) }
|
21
|
+
|
22
|
+
# validations
|
23
|
+
validates :attempts_number, :numericality => { :only_integer => true, :greater_than => -1 }
|
24
|
+
validates :description, :name, :presence => true, :allow_blank => false
|
25
|
+
validate :check_active_requirements
|
26
|
+
|
27
|
+
# returns all the correct options for current surveys
|
28
|
+
def correct_options
|
29
|
+
return self.questions.map(&:correct_options).flatten
|
30
|
+
end
|
31
|
+
|
32
|
+
# returns all the incorrect options for current surveys
|
33
|
+
def incorrect_options
|
34
|
+
return self.questions.map(&:incorrect_options).flatten
|
35
|
+
end
|
36
|
+
|
37
|
+
def avaliable_for_participant?(participant)
|
38
|
+
current_number_of_attempts = self.attempts.for_participant(participant).size
|
39
|
+
upper_bound = self.attempts_number
|
40
|
+
return !((current_number_of_attempts >= upper_bound) && (upper_bound != 0))
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# a surveys only can be activated if has one or more questions
|
46
|
+
def check_active_requirements
|
47
|
+
errors.add(:active, "Survey without questions cannot be activated") if self.active && self.questions.empty?
|
48
|
+
end
|
49
|
+
end
|