questionnaire_engine 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +9 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +234 -0
  5. data/Rakefile +30 -0
  6. data/app/models/survey/answer.rb +50 -0
  7. data/app/models/survey/attempt.rb +74 -0
  8. data/app/models/survey/option.rb +44 -0
  9. data/app/models/survey/options_type.rb +35 -0
  10. data/app/models/survey/predefined_value.rb +23 -0
  11. data/app/models/survey/question.rb +52 -0
  12. data/app/models/survey/questions_type.rb +27 -0
  13. data/app/models/survey/section.rb +47 -0
  14. data/app/models/survey/survey.rb +63 -0
  15. data/config/locales/en.yml +54 -0
  16. data/config/locales/pt-PT.yml +48 -0
  17. data/config/locales/pt.yml +48 -0
  18. data/lib/generators/survey/install_generator.rb +35 -0
  19. data/lib/generators/survey/survey_generator.rb +77 -0
  20. data/lib/generators/templates/active_admin.rb +85 -0
  21. data/lib/generators/templates/attempts_plain.rb +36 -0
  22. data/lib/generators/templates/attempts_views/_form.html.erb +61 -0
  23. data/lib/generators/templates/attempts_views/new.html.erb +10 -0
  24. data/lib/generators/templates/helper.rb +46 -0
  25. data/lib/generators/templates/migration.rb +56 -0
  26. data/lib/generators/templates/migration_add_head_number_to_options_table.rb +6 -0
  27. data/lib/generators/templates/migration_add_mandatory_to_questions_table.rb +6 -0
  28. data/lib/generators/templates/migration_add_types_to_questions_and_options.rb +13 -0
  29. data/lib/generators/templates/migration_create_predefined_values_table.rb +14 -0
  30. data/lib/generators/templates/migration_section.rb +22 -0
  31. data/lib/generators/templates/migration_update_survey_tables.rb +22 -0
  32. data/lib/generators/templates/rails_admin.rb +8 -0
  33. data/lib/generators/templates/survey_plain.rb +60 -0
  34. data/lib/generators/templates/survey_views/_form.html.erb +72 -0
  35. data/lib/generators/templates/survey_views/_option_fields.html.erb +26 -0
  36. data/lib/generators/templates/survey_views/_question_fields.html.erb +42 -0
  37. data/lib/generators/templates/survey_views/_section_fields.html.erb +29 -0
  38. data/lib/generators/templates/survey_views/edit.html.erb +2 -0
  39. data/lib/generators/templates/survey_views/index.html.erb +14 -0
  40. data/lib/generators/templates/survey_views/new.html.erb +2 -0
  41. data/lib/survey.rb +5 -0
  42. data/lib/survey/active_record.rb +15 -0
  43. data/lib/survey/engine.rb +6 -0
  44. data/lib/survey/version.rb +3 -0
  45. metadata +142 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 16dbdcc59cfde5bcfa9e8745dda6c10b06b489f5
4
+ data.tar.gz: 67abc2d738cffa82dd8e64e720b896b42da834dc
5
+ SHA512:
6
+ metadata.gz: 08343605d7259b82442a5e7d6d30843043564d74e4006dc4685b7ba020eea7482f897e6caca96c0eb35b9c4bfd83a121e7e65a24e42259a4724e44edbb9ef81b
7
+ data.tar.gz: 870fec89ff9946f4b5227f87d99a6fdc6a47bf9d2005ab707a2a1214616d362af7483295cbd8186a9b9571864e6ef978527dcb03f63799cd943cc1676fa4210e
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
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,234 @@
1
+ # Questionnaire
2
+
3
+ [![Code Climate](https://codeclimate.com/github/dr-click/survey.png)](https://codeclimate.com/github/dr-click/questionnaire)
4
+ ### Questionnaire on Rails...
5
+
6
+ Questionnaire is a Rails Engine that brings multi types of quizzes, surveys and contests into your Rails
7
+ application. Questionnaire models were designed to be flexible enough in order to be extended and
8
+ integrated with your own models. Questionnaire was initially extracted from a real application that handles contests and quizzes.
9
+
10
+ ## Documentation
11
+
12
+ You can view the Questionnaire documentation in RDoc format here:
13
+
14
+ http://rubydoc.info/github/dr-click/questionnaire/master/frames
15
+
16
+ ## Main Features:
17
+ - Questionnaire can limit the number of attempts for each participant, can have multiple sections
18
+ - Sections can have multiple questions
19
+ - Questions can have multiple answers
20
+ - Answers can have different weights and types (multi choices, single choice, number, text)
21
+ - Can use 2 languages (Main language field, Localized field) for Surveys, Sections, Questions and Answers attributes
22
+ - Base Scaffold Support for Active Admin, Rails Admin and default Rails Controllers
23
+ - Base calculation for scores
24
+ - Easy integration with your project
25
+
26
+ ## Installation
27
+
28
+ Add survey to your Gemfile:
29
+ ```ruby
30
+ gem 'questionnaire_engine', '0.1', :require=>"survey"
31
+
32
+ ```
33
+ or
34
+ ```ruby
35
+ gem 'questionnaire', github: 'dr-click/questionnaire', branch: 'master', :require=>"survey"
36
+
37
+ ```
38
+ Then run bundle to install the Gem:
39
+ ```sh
40
+ bundle install
41
+ ```
42
+ Now generate and run migrations:
43
+ ```sh
44
+ rails generate survey:install
45
+
46
+ bundle exec rake db:migrate
47
+ ```
48
+
49
+ ## Getting started with Survey
50
+
51
+ ## Survey inside your models
52
+ To make a model aware of you just need to add `has_surveys` on it:
53
+ ```ruby
54
+ class User < ActiveRecord::Base
55
+ has_surveys
56
+
57
+ #... (your code) ...
58
+ end
59
+ ```
60
+ There is the concept of participant, in our example we choose the User Model.
61
+ Every participant can respond to surveys and every response is registered as a attempt.
62
+ By default, survey logic assumes an infinite number of attempts per participant
63
+ but if your surveys need to have a maximum number of attempts
64
+ you can pass the attribute `attempts_number` when creating them.
65
+ ```ruby
66
+ # Each Participant can respond 4 times this survey
67
+ Survey::Survey.new(:name => "Star Wars Quiz", :attempts_number => 4)
68
+ ```
69
+ ## Questionnaire used in your controllers
70
+ In this example we are using the current_user helper
71
+ but you can do it in the way you want.
72
+
73
+ ```ruby
74
+ class ContestsController < ApplicationController
75
+
76
+ helper_method :survey, :participant
77
+
78
+ # create a new attempt to this survey
79
+ def new
80
+ @survey = Survey::Survey.active.last
81
+ @attempt = @survey.attempts.new
82
+ @attempt.answers.build
83
+ @participant = current_user # you have to decide what to do here
84
+ end
85
+
86
+ # create a new attempt in this survey
87
+ # an attempt needs to have a participant assigned
88
+ def create
89
+ @survey = Survey::Survey.active.last
90
+ @attempt = @survey.attempts.new(attempt_params)
91
+ @attempt.participant = current_user
92
+ if @attempt.valid? and @attempt.save
93
+ redirect_to view_context.new_attempt_path, alert: I18n.t("attempts_controller.#{action_name}")
94
+ else
95
+ flash.now[:error] = @attempt.errors.full_messages.join(', ')
96
+ render :action => :new
97
+ end
98
+ end
99
+
100
+ #######
101
+ private
102
+ #######
103
+
104
+ # Rails 4 Strong Params
105
+ def attempt_params
106
+ if Rails::VERSION::MAJOR < 4
107
+ params[:survey_attempt]
108
+ else
109
+ params.require(:survey_attempt).permit(answers_attributes: [:id, :question_id, :option_id, :option_text, :option_number, :predefined_value_id, :_destroy, :finished])
110
+ end
111
+ end
112
+
113
+ end
114
+ ```
115
+
116
+ ## Survey inside your Views
117
+
118
+ ### Controlling Survey avaliability per participant
119
+ To control which page participants see you can use method `avaliable_for_participant?`
120
+ that checks if the participant already spent his attempts.
121
+ ```erb
122
+ <h3><%= flash[:alert]%></h3>
123
+ <h3><%= flash[:error]%></h3>
124
+
125
+ <% if @survey.avaliable_for_participant?(@participant) %>
126
+ <%= render 'form' %>
127
+ <% else %>
128
+ <p>
129
+ <%= @participant.name %> spent all the possible attempts to answer this Survey
130
+ </p>
131
+ <% end -%>
132
+
133
+ <% # in _form.html.erb %>
134
+ <h1><%= @survey.name %></h1>
135
+ <p><%= @survey.description %></p>
136
+ <%= form_for(@attempt, :url => attempt_scope(@attempt)) do |f| %>
137
+ <%= f.fields_for :answers do |builder| %>
138
+ <ul>
139
+ <% seq = 0 %>
140
+ <% @survey.sections.each do |section| %>
141
+ <p><span><%= "#{section.head_number} : " if section.head_number %></span><%= section.name%></p>
142
+ <p><%= section.description if section.description %></p>
143
+ <% section.questions.each do |question| %>
144
+ <% seq += 1 %>
145
+ <li>
146
+ <p><span><%= "#{question.head_number} : " if question.head_number %></span><%= question.text %></p>
147
+ <p><%= question.description if question.description %></p>
148
+ <% question.options.each do |option| %>
149
+
150
+
151
+ <% if option.options_type_id == Survey::OptionsType.multi_choices %>
152
+ <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
153
+ <%= check_box_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
154
+ <% seq += 1 %>
155
+ <% elsif option.options_type_id == Survey::OptionsType.single_choice %>
156
+ <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
157
+ <%= radio_button_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
158
+ <% elsif option.options_type_id == Survey::OptionsType.number %>
159
+ <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
160
+ <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
161
+ <%= number_field_tag "survey_attempt[answers_attributes][#{seq}][option_number]", "", :style => "width: 40px;" %>
162
+ <% seq += 1 %>
163
+ <% elsif option.options_type_id == Survey::OptionsType.text %>
164
+ <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
165
+ <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
166
+ <%= text_field_tag "survey_attempt[answers_attributes][#{seq}][option_text]", "" %>
167
+ <% seq += 1 %>
168
+ <% end %>
169
+
170
+ <%= option.text %> <br/>
171
+ <% end -%>
172
+ </li>
173
+ <% end -%>
174
+ <% end -%>
175
+ </ul>
176
+ <% end -%>
177
+ <%= f.submit "Submit" %>
178
+ <% end -%>
179
+ ```
180
+
181
+ ### Scaffolds and CRUD frameworks
182
+ If you are using Rails Admin or Active Admin, you can generate base CRUD screens for Survey with:
183
+ ```sh
184
+ rails generate survey active_admin
185
+
186
+ rails generate survey rails_admin
187
+ ```
188
+ 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.
189
+ 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:
190
+ ```sh
191
+ rails generate survey plain namespace:contests
192
+ ```
193
+
194
+ By default when you generates your controllers using the `plain` command the task
195
+ generates the associated routes as well.
196
+ Afterwards if you want to generate more routes, you can using the command:
197
+
198
+ ```sh
199
+ rails generate survey routes namespace:admin
200
+ ```
201
+
202
+ ## How to use it
203
+ Every user has a collection of attempts for each survey that he respond to. Is up to you to
204
+ make averages and collect reports based on that information.
205
+ What makes Survey useful is that all the logic behind surveys is now abstracted and well integrated,
206
+ making your job easier.
207
+
208
+ ## Hacking with Survey through your Models:
209
+
210
+ ```ruby
211
+ # select the first active Survey
212
+ survey = Survey::Survey.active.first
213
+
214
+ # select all the attempts from this survey
215
+ survey_answers = survey.attempts
216
+
217
+ # check the highest score for current user
218
+ user_highest_score = survey_answers.for_participant(@user).high_score
219
+
220
+ #check the highest score made for this survey
221
+ global_highest_score = survey_answers.high_score
222
+ ```
223
+ # Compability
224
+ ### Rails
225
+ Survey supports Rails 3 and 4. For use in Rails 4 without using protected_attributes gem.
226
+ Rails 4 support is recent, so some minor issues may still be present, please report them.
227
+
228
+ ### Active Admin
229
+ Only support versions of Active Admin higher than 0.3.1.
230
+
231
+ # License
232
+ - Modified by [Dr-Click](http://github.com/dr-click)
233
+ - Copyright © 2013 [Runtime Revolution](http://www.runtime-revolution.com), released under the MIT license.
234
+ - This repository was forked from the original one : https://github.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,50 @@
1
+ class Survey::Answer < ActiveRecord::Base
2
+
3
+ self.table_name = "survey_answers"
4
+ belongs_to :attempt
5
+ belongs_to :option
6
+ belongs_to :predefined_value
7
+ belongs_to :question
8
+
9
+ validates :option_id, :question_id, :presence => true
10
+ validates :predefined_value_id, :presence => true , :if => Proc.new{|a| a.question && a.question.mandatory? && a.question.predefined_values.count > 0 && !([Survey::OptionsType.text, Survey::OptionsType.large_text].include?(a.option.options_type_id)) }
11
+ validates :option_text, :presence => true , :if => Proc.new{|a| a.option && ( a.question && a.question.mandatory? && a.question.predefined_values.count == 0 && [Survey::OptionsType.text, Survey::OptionsType.multi_choices_with_text, Survey::OptionsType.single_choice_with_text, Survey::OptionsType.large_text].include?(a.option.options_type_id)) }
12
+ validates :option_number, :presence => true , :if => Proc.new{|a| a.option && ( a.question && a.question.mandatory? && [Survey::OptionsType.number, Survey::OptionsType.multi_choices_with_number, Survey::OptionsType.single_choice_with_number].include?(a.option.options_type_id)) }
13
+
14
+ #rails 3 attr_accessible support
15
+ if Rails::VERSION::MAJOR < 4
16
+ attr_accessible :option, :attempt, :question, :question_id, :option_id, :predefined_value_id, :attempt_id, :option_text, :option_number
17
+ end
18
+
19
+ before_create :characterize_answer
20
+ before_save :check_single_choice_with_field_case
21
+
22
+ def value
23
+ unless self.option == nil
24
+ self.option.weight
25
+ else
26
+ Survey::Option.find(option_id).weight
27
+ end
28
+ end
29
+
30
+ def correct?
31
+ self.correct or self.option.correct?
32
+ end
33
+
34
+ #######
35
+ private
36
+ #######
37
+
38
+ def characterize_answer
39
+ if option.correct?
40
+ self.correct = true
41
+ end
42
+ end
43
+
44
+ def check_single_choice_with_field_case
45
+ if [Survey::OptionsType.multi_choices, Survey::OptionsType.single_choice].include?(self.option.options_type_id)
46
+ self.option_text = nil
47
+ self.option_number = nil
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,74 @@
1
+ class Survey::Attempt < ActiveRecord::Base
2
+
3
+ self.table_name = "survey_attempts"
4
+
5
+ # relations
6
+
7
+ has_many :answers
8
+ belongs_to :survey
9
+ belongs_to :participant, :polymorphic => true
10
+
11
+ #rails 3 attr_accessible support
12
+ if Rails::VERSION::MAJOR < 4
13
+ attr_accessible :participant_id, :survey_id, :answers_attributes, :survey, :winner, :participant
14
+ end
15
+
16
+ # validations
17
+ validates :participant_id, :participant_type,
18
+ :presence => true
19
+
20
+ accepts_nested_attributes_for :answers,
21
+ :reject_if =>
22
+ ->(q) { q[:question_id].blank? || q[:option_id].blank? },
23
+ allow_destroy: true
24
+
25
+ #scopes
26
+
27
+ scope :for_survey, ->(survey) {
28
+ where(:survey_id => survey.try(:id))
29
+ }
30
+
31
+ scope :exclude_survey, ->(survey) {
32
+ where("NOT survey_id = #{survey.try(:id)}")
33
+ }
34
+
35
+ scope :for_participant, ->(participant) {
36
+ where(:participant_id => participant.try(:id),
37
+ :participant_type => participant.class)
38
+ }
39
+
40
+ scope :wins, -> { where(:winner => true) }
41
+ scope :looses, -> { where(:winner => false) }
42
+ scope :scores, -> { order("score DESC") }
43
+
44
+ # callbacks
45
+
46
+ validate :check_number_of_attempts_by_survey, :on => :create
47
+ before_create :collect_scores
48
+
49
+ def correct_answers
50
+ self.answers.where(:correct => true)
51
+ end
52
+
53
+ def incorrect_answers
54
+ self.answers.where(:correct => false)
55
+ end
56
+
57
+ def self.high_score
58
+ scores.first.score
59
+ end
60
+
61
+ private
62
+
63
+ def check_number_of_attempts_by_survey
64
+ attempts = self.class.for_survey(survey).for_participant(participant)
65
+ upper_bound = self.survey.attempts_number
66
+ if attempts.size >= upper_bound and upper_bound != 0
67
+ errors.add(:questionnaire_id, "Number of attempts exceeded")
68
+ end
69
+ end
70
+
71
+ def collect_scores
72
+ self.score = self.answers.map(&:value).reduce(:+)
73
+ end
74
+ end
@@ -0,0 +1,44 @@
1
+ class Survey::Option < ActiveRecord::Base
2
+
3
+ self.table_name = "survey_options"
4
+ #relations
5
+ belongs_to :question
6
+
7
+ #rails 3 attr_accessible support
8
+ if Rails::VERSION::MAJOR < 4
9
+ attr_accessible :text, :correct, :weight, :question_id, :locale_text, :options_type_id
10
+ end
11
+
12
+ # validations
13
+ validates :text, :presence => true, :allow_blank => false, :if => Proc.new{|o| [Survey::OptionsType.multi_choices, Survey::OptionsType.single_choice, Survey::OptionsType.single_choice_with_text, Survey::OptionsType.single_choice_with_number, Survey::OptionsType.multi_choices_with_text, Survey::OptionsType.multi_choices_with_number, Survey::OptionsType.large_text].include?(o.options_type_id) }
14
+ validates :options_type_id, :presence => true
15
+ validates_inclusion_of :options_type_id, :in => Survey::OptionsType.options_type_ids, :unless => Proc.new{|o| o.options_type_id.blank?}
16
+
17
+ scope :correct, -> {where(:correct => true) }
18
+ scope :incorrect, -> {where(:correct => false) }
19
+
20
+ before_create :default_option_weigth
21
+
22
+ def to_s
23
+ self.text
24
+ end
25
+
26
+ def correct?
27
+ self.correct == true
28
+ end
29
+
30
+ def text
31
+ I18n.locale == I18n.default_locale ? super : locale_text.blank? ? super : locale_text
32
+ end
33
+
34
+ #######
35
+ private
36
+ #######
37
+
38
+ def default_option_weigth
39
+ if self.correct and self.weight == 0
40
+ self.weight = 1
41
+ end
42
+ end
43
+
44
+ end