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.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +9 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +194 -0
  5. data/Rakefile +30 -0
  6. data/app/models/survey/answer.rb +34 -0
  7. data/app/models/survey/attempt.rb +60 -0
  8. data/app/models/survey/option.rb +34 -0
  9. data/app/models/survey/question.rb +24 -0
  10. data/app/models/survey/survey.rb +49 -0
  11. data/config/locales/en.yml +36 -0
  12. data/config/locales/pt-PT.yml +48 -0
  13. data/config/locales/pt.yml +48 -0
  14. data/lib/generators/survey/install_generator.rb +20 -0
  15. data/lib/generators/survey/survey_generator.rb +102 -0
  16. data/lib/generators/templates/active_admin.rb +46 -0
  17. data/lib/generators/templates/attempts_plain.rb +69 -0
  18. data/lib/generators/templates/attempts_views/_form.html.erb +18 -0
  19. data/lib/generators/templates/attempts_views/new.html.erb +11 -0
  20. data/lib/generators/templates/helper.rb +44 -0
  21. data/lib/generators/templates/migration.rb +56 -0
  22. data/lib/generators/templates/rails_admin.rb +3 -0
  23. data/lib/generators/templates/survey_plain.rb +54 -0
  24. data/lib/generators/templates/survey_views/_form.html.erb +61 -0
  25. data/lib/generators/templates/survey_views/_option_fields.html.erb +9 -0
  26. data/lib/generators/templates/survey_views/_question_fields.html.erb +15 -0
  27. data/lib/generators/templates/survey_views/edit.html.erb +2 -0
  28. data/lib/generators/templates/survey_views/index.html.erb +16 -0
  29. data/lib/generators/templates/survey_views/new.html.erb +2 -0
  30. data/lib/survey.rb +15 -0
  31. data/lib/survey/active_record.rb +28 -0
  32. data/lib/survey/engine.rb +6 -0
  33. data/lib/survey/version.rb +3 -0
  34. metadata +156 -0
@@ -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
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "rdoc"
6
+
7
+ group :test do
8
+ gem 'sqlite3'
9
+ end
@@ -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.
@@ -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)
@@ -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