questionnaire_engine 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 (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