survey 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](https://travis-ci.org/runtimerevolution/survey)
|
4
|
+
[](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
|
+
[](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
|