ask_it 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +11 -0
  3. data/LICENSE.md +21 -0
  4. data/README.md +287 -0
  5. data/Rakefile +29 -0
  6. data/app/controllers/ask_it/attempts_controller.rb +36 -0
  7. data/app/controllers/ask_it/surveys_controller.rb +58 -0
  8. data/app/models/ask_it/answer.rb +99 -0
  9. data/app/models/ask_it/attempt.rb +108 -0
  10. data/app/models/ask_it/option.rb +65 -0
  11. data/app/models/ask_it/options_type.rb +39 -0
  12. data/app/models/ask_it/predefined_value.rb +24 -0
  13. data/app/models/ask_it/question.rb +73 -0
  14. data/app/models/ask_it/question_type.rb +32 -0
  15. data/app/models/ask_it/section.rb +66 -0
  16. data/app/models/ask_it/survey.rb +77 -0
  17. data/config/locales/en.yml +54 -0
  18. data/config/locales/pt-PT.yml +48 -0
  19. data/config/locales/pt.yml +48 -0
  20. data/lib/ask_it/active_record.rb +25 -0
  21. data/lib/ask_it/engine.rb +8 -0
  22. data/lib/ask_it/railtie.rb +13 -0
  23. data/lib/ask_it/version.rb +5 -0
  24. data/lib/ask_it.rb +20 -0
  25. data/lib/generators/ask_it/install_generator.rb +57 -0
  26. data/lib/generators/ask_it/survey_generator.rb +117 -0
  27. data/lib/generators/templates/active_admin.rb +87 -0
  28. data/lib/generators/templates/controllers/attempts_controller.rb +11 -0
  29. data/lib/generators/templates/controllers/surveys_controller.rb +31 -0
  30. data/lib/generators/templates/helpers/ask_it/surveys_helper.rb +56 -0
  31. data/lib/generators/templates/migration.rb.tt +59 -0
  32. data/lib/generators/templates/migration_add_head_number_to_options_table.rb.tt +8 -0
  33. data/lib/generators/templates/migration_add_mandatory_to_questions_table.rb.tt +8 -0
  34. data/lib/generators/templates/migration_add_types_to_questions_and_options.rb.tt +15 -0
  35. data/lib/generators/templates/migration_create_predefined_values_table.rb.tt +16 -0
  36. data/lib/generators/templates/migration_section.rb.tt +24 -0
  37. data/lib/generators/templates/migration_update_survey_tables.rb.tt +24 -0
  38. data/lib/generators/templates/rails_admin.rb +10 -0
  39. data/lib/generators/templates/views/attempts/_form.html.erb +61 -0
  40. data/lib/generators/templates/views/attempts/new.html.erb +10 -0
  41. data/lib/generators/templates/views/surveys/_form.html.erb +78 -0
  42. data/lib/generators/templates/views/surveys/_option_fields.html.erb +26 -0
  43. data/lib/generators/templates/views/surveys/_question_fields.html.erb +42 -0
  44. data/lib/generators/templates/views/surveys/_section_fields.html.erb +29 -0
  45. data/lib/generators/templates/views/surveys/edit.html.erb +2 -0
  46. data/lib/generators/templates/views/surveys/index.html.erb +14 -0
  47. data/lib/generators/templates/views/surveys/new.html.erb +2 -0
  48. data/lib/survey.rb +7 -0
  49. data/lib/tasks/changelog.rake +10 -0
  50. metadata +372 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1a3bc70a98811e199af9ece6bd847d80c8c2ee0d7c6f28b52b76f4633a782158
4
+ data.tar.gz: 0360a7f42de9cea7e72e36491377fa81168da1b6032f1f4f51acf9788f07d588
5
+ SHA512:
6
+ metadata.gz: 9859e6a7b84f31286137b4c1e40ef3768b8f84e7fdd5e3d50a3658a2fd3dce7fdff89b2792924834f314843af8d4627b5b04be6aae81f803347e489ef5c4fd69
7
+ data.tar.gz: 234ef09f60f5ce8dfe04e07acde93a3e1862b07094a2fee9a3de988cbcc642814b1ec486c6a0b084ebd6053723f75dda358f1a22289d4188329320d88528186a
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ ruby '>=2.7.8'
6
+
7
+ gemspec
8
+
9
+ gem 'rdoc'
10
+
11
+ gem 'sqlite3', '~> 1.7.3'
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT LICENSE
2
+
3
+ Copyright (c) 2024 Build It
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,287 @@
1
+ # Ask It
2
+
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/74596429080573c6b707/maintainability)](https://codeclimate.com/github/growth-constant/ask_it/maintainability)
4
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/74596429080573c6b707/test_coverage)](https://codeclimate.com/github/growth-constant/ask_it/test_coverage)
5
+
6
+ Ask It is a Rails Engine that brings multi types of quizzes, surveys and contests into your Rails
7
+ application. Ask It models were designed to be flexible enough in order to be extended and
8
+ integrated with your own models. Ask It was initially extracted from a real application that handles contests and quizzes.
9
+
10
+ ## Table of Contents
11
+
12
+ - [Documentation](#documentation)
13
+ - [Main Features](#main-features)
14
+ - [Installation](#installation)
15
+ - [Getting Started](#getting-started)
16
+ - [Adding Survey Participants](#adding-survey-participants)
17
+ - [Survey Associations](#survey-associations)
18
+ - [Survey in Views](#survey-in-views)
19
+ - [Scaffolds and CRUD Frameworks](#scaffolds-and-crud-frameworks)
20
+ - [How to Use It](#how-to-use-it)
21
+ - [Compatibility](#compatibility)
22
+ - [License](#license)
23
+
24
+ ## Documentation
25
+
26
+ You can view the Ask It documentation in RDoc format here:
27
+
28
+ http://rubydoc.info/github/dr-click/questionnaire/master/frames
29
+
30
+ ## Main Features:
31
+ - Ask It can limit the number of attempts for each participant, can have multiple sections
32
+ - Sections can have multiple questions
33
+ - Questions can have multiple answers
34
+ - Answers can have different weights and types (multi choices, single choice, number, text)
35
+ - Can use 2 languages (Main language field, Localized field) for Surveys, Sections, Questions and Answers attributes
36
+ - Base Scaffold Support for Active Admin, Rails Admin and default Rails Controllers
37
+ - Base calculation for scores
38
+ - Easy integration with your project
39
+
40
+ ## Installation
41
+
42
+ Add Ask It to your Gemfile for Rails 6 and 7:
43
+
44
+ ```ruby
45
+ gem 'ask_it'
46
+ ```
47
+ or
48
+ ```ruby
49
+ gem 'ask_it'
50
+
51
+ ```
52
+
53
+ Now generate and run migrations:
54
+ ```sh
55
+ rails generate ask_it:install
56
+
57
+ bundle exec rake db:migrate
58
+ ```
59
+
60
+ ## Getting started with Ask It
61
+
62
+ ## Survey inside your models
63
+ To make a model aware of you just need to add `has_surveys` on it:
64
+ ```ruby
65
+ class User < ActiveRecord::Base
66
+ has_surveys
67
+
68
+ #... (your code) ...
69
+ end
70
+ ```
71
+ There is the concept of participant, in our example we choose the User Model.
72
+ Every participant can respond to surveys and every response is registered as a attempt.
73
+ By default, survey logic assumes an infinite number of attempts per participant
74
+ but if your surveys need to have a maximum number of attempts
75
+ you can pass the attribute `attempts_number` when creating them.
76
+
77
+ ```ruby
78
+ # Each Participant can respond 4 times this survey
79
+ AskIt::Survey.new(:name => "Star Wars Quiz", :attempts_number => 4)
80
+ ```
81
+ ## Ask It used in your controllers
82
+
83
+ In this example we are using the current_user helper
84
+ but you can do it in the way you want.
85
+
86
+ ```ruby
87
+ class ContestsController < ApplicationController
88
+
89
+ helper_method :survey, :participant
90
+
91
+ # create a new attempt to this survey
92
+ def new
93
+ @survey = AskIt::Survey.active.last
94
+ @attempt = @survey.attempts.new
95
+ @attempt.answers.build
96
+ @participant = current_user # you have to decide what to do here
97
+ end
98
+
99
+ # create a new attempt in this survey
100
+ # an attempt needs to have a participant assigned
101
+ def create
102
+ @survey = AskIt::Survey.active.last
103
+ @attempt = @survey.attempts.new(attempt_params)
104
+ @attempt.participant = current_user
105
+ if @attempt.valid? and @attempt.save
106
+ redirect_to view_context.new_attempt_path, alert: I18n.t("attempts_controller.#{action_name}")
107
+ else
108
+ flash.now[:error] = @attempt.errors.full_messages.join(', ')
109
+ render :action => :new
110
+ end
111
+ end
112
+
113
+ #######
114
+ private
115
+ #######
116
+
117
+ # Rails 4 Strong Params
118
+ def attempt_params
119
+ if Rails::VERSION::MAJOR < 4
120
+ params[:survey_attempt]
121
+ else
122
+ params.require(:survey_attempt).permit(answers_attributes: [:id, :question_id, :option_id, :option_text, :option_number, :predefined_value_id, :_destroy, :finished])
123
+ end
124
+ end
125
+
126
+ end
127
+ ```
128
+
129
+ ## Survey Associations
130
+ To add a survey to a particular model (model has_many :surveys), use the `has_many_surveys` helper
131
+ ```ruby
132
+ class Lesson < ActiveRecord::Base
133
+ has_many_surveys
134
+
135
+ #... (your code) ...
136
+ end
137
+ ```
138
+ Then, create a module mixin that adds `belongs_to` to the survey model based on your class name
139
+ ```ruby
140
+ # app/models/survey/belongs_to_lesson.rb
141
+ module Survey
142
+ module BelongsToLesson
143
+ extend ActiveSupport::Concern
144
+ included do
145
+ belongs_to :lesson
146
+ end
147
+ end
148
+ end
149
+ ```
150
+ This will dynamically add the association to the survey model. However, you will need to generate a migration in order to add the foreign key to the `survey_surveys` table like so:
151
+ ```ruby
152
+ class AddLessonIdToSurveySurveys < ActiveRecord::Migration
153
+ def change
154
+ add_column :survey_surveys, :lesson_id, :integer
155
+ end
156
+ end
157
+ ```
158
+
159
+
160
+ ## Survey inside your Views
161
+
162
+ ### Controlling Survey avaliability per participant
163
+ To control which page participants see you can use method `avaliable_for_participant?`
164
+ that checks if the participant already spent his attempts.
165
+ ```erb
166
+ <h3><%= flash[:alert]%></h3>
167
+ <h3><%= flash[:error]%></h3>
168
+
169
+ <% if @survey.avaliable_for_participant?(@participant) %>
170
+ <%= render 'form' %>
171
+ <% else %>
172
+ <p>
173
+ <%= @participant.name %> spent all the possible attempts to answer this Survey
174
+ </p>
175
+ <% end -%>
176
+
177
+ <% # in _form.html.erb %>
178
+ <h1><%= @survey.name %></h1>
179
+ <p><%= @survey.description %></p>
180
+ <%= form_for(@attempt, :url => attempt_scope(@attempt)) do |f| %>
181
+ <%= f.fields_for :answers do |builder| %>
182
+ <ul>
183
+ <% seq = 0 %>
184
+ <% @survey.sections.each do |section| %>
185
+ <p><span><%= "#{section.head_number} : " if section.head_number %></span><%= section.name%></p>
186
+ <p><%= section.description if section.description %></p>
187
+ <% section.questions.each do |question| %>
188
+ <% seq += 1 %>
189
+ <li>
190
+ <p><span><%= "#{question.head_number} : " if question.head_number %></span><%= question.text %></p>
191
+ <p><%= question.description if question.description %></p>
192
+ <% question.options.each do |option| %>
193
+
194
+
195
+ <% if option.options_type_id == AskIt::OptionsType.multi_choices %>
196
+ <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
197
+ <%= check_box_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
198
+ <% seq += 1 %>
199
+ <% elsif option.options_type_id == AskIt::OptionsType.single_choice %>
200
+ <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
201
+ <%= radio_button_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
202
+ <% elsif option.options_type_id == AskIt::OptionsType.number %>
203
+ <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
204
+ <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
205
+ <%= number_field_tag "survey_attempt[answers_attributes][#{seq}][option_number]", "", :style => "width: 40px;" %>
206
+ <% seq += 1 %>
207
+ <% elsif option.options_type_id == AskIt::OptionsType.text %>
208
+ <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
209
+ <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
210
+ <%= text_field_tag "survey_attempt[answers_attributes][#{seq}][option_text]", "" %>
211
+ <% seq += 1 %>
212
+ <% end %>
213
+
214
+ <%= option.text %> <br/>
215
+ <% end -%>
216
+ </li>
217
+ <% end -%>
218
+ <% end -%>
219
+ </ul>
220
+ <% end -%>
221
+ <%= f.submit "Submit" %>
222
+ <% end -%>
223
+ ```
224
+
225
+ ### Scaffolds and CRUD frameworks
226
+ If you are using Rails Admin or Active Admin, you can generate base CRUD screens for Survey with:
227
+ ```sh
228
+ rails generate survey active_admin
229
+
230
+ rails generate survey rails_admin
231
+ ```
232
+ 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.
233
+ 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:
234
+ ```sh
235
+ rails generate survey plain namespace:contests
236
+ ```
237
+
238
+ By default when you generates your controllers using the `plain` command the task
239
+ generates the associated routes as well.
240
+ Afterwards if you want to generate more routes, you can using the command:
241
+
242
+ ```sh
243
+ rails generate survey routes namespace:admin
244
+ ```
245
+
246
+
247
+ ## How to use it
248
+ Every user has a collection of attempts for each survey that he respond to. Is up to you to
249
+ make averages and collect reports based on that information.
250
+ What makes Survey useful is that all the logic behind surveys is now abstracted and well integrated,
251
+ making your job easier.
252
+
253
+ ## Hacking with Survey through your Models:
254
+
255
+ ```ruby
256
+ # select the first active Survey
257
+ survey = AskIt::Survey.active.first
258
+
259
+ # select all the attempts from this survey
260
+ survey_answers = survey.attempts
261
+
262
+ # check the highest score for current user
263
+ user_highest_score = survey_answers.for_participant(@user).high_score
264
+
265
+ #check the highest score made for this survey
266
+ global_highest_score = survey_answers.high_score
267
+ ```
268
+ # Compability
269
+
270
+ ### Rails
271
+ Survey supports Rails 3 and 4. For use in Rails 4 without using protected_attributes gem.
272
+ Rails 4 support is recent, so some minor issues may still be present, please report them.
273
+
274
+ ### Active Admin
275
+ Only support versions of Active Admin higher than 0.3.1.
276
+
277
+ # License
278
+ - Modified by [Dr-Click](http://github.com/dr-click)
279
+ - Copyright © 2013 [Runtime Revolution](http://www.runtime-revolution.com), released under the MIT license.
280
+ - This repository was forked from the original one : https://github.com/clearfunction/questionnaire
281
+
282
+ # Thanks to Gems
283
+ - [Appraisal](https://github.com/thoughtbot/appraisal)
284
+ - [Code Climate](https://codeclimate.com/)
285
+ - [Simplecov](https://github.com/colszowka/simplecov)
286
+ - [Rspec](https://github.com/rspec/rspec)
287
+ - [Rails](https://github.com/rails/rails)
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ begin
5
+ require 'bundler/setup'
6
+ rescue LoadError
7
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
8
+ end
9
+
10
+ require 'rake'
11
+ require 'rdoc/task'
12
+ require 'rspec/core/rake_task'
13
+ load 'tasks/changelog.rake'
14
+
15
+ desc 'Run all specs across all appraisals'
16
+ task :spec do
17
+ sh 'bundle exec appraisal rspec'
18
+ end
19
+
20
+ task default: :spec
21
+
22
+ Rake::RDocTask.new(:rdoc) do |rdoc|
23
+ rdoc.rdoc_dir = 'rdoc'
24
+ rdoc.title = 'Ask It'
25
+ rdoc.options << '--line-numbers' << '--inline-source'
26
+ rdoc.rdoc_files.include('README.md')
27
+ rdoc.rdoc_files.include('lib/**/*.rb')
28
+ rdoc.rdoc_files.include('app/**/*.rb')
29
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AskIt
4
+ # Handles creation and management of survey attempts
5
+ class AttemptsController < ActionController::Base
6
+ # Handles creation and management of survey attempts
7
+ # helper 'ask_it/surveys'
8
+ # include AskIt::SurveysHelper
9
+
10
+ def new
11
+ @survey = Survey.active.last
12
+ @attempt = @survey.attempts.new
13
+ @attempt.answers.build
14
+ @participant = current_user # Adjust as needed
15
+ end
16
+
17
+ def create
18
+ @survey = Survey.active.last
19
+ @attempt = @survey.attempts.new(attempt_params)
20
+ @attempt.participant = current_user # Adjust as needed
21
+
22
+ if @attempt.save
23
+ redirect_to new_ask_it_attempt_path, notice: t('.success')
24
+ else
25
+ flash.now[:error] = @attempt.errors.full_messages.join(', ')
26
+ render :new
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def attempt_params
33
+ params.require(:attempt).permit(answers_attributes: %i[question_id option_id option_text option_number predefined_value_id])
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AskIt
4
+ # Handles creation and management of surveys
5
+ class SurveysController < ActionController::Base
6
+ before_action :load_survey, only: %i[show edit update]
7
+
8
+ def index
9
+ @surveys = AskIt::Survey.all
10
+ end
11
+
12
+ def new
13
+ @survey = AskIt::Survey.new
14
+ end
15
+
16
+ def create
17
+ @survey = AskIt::Survey.new(survey_params)
18
+ if @survey.valid? && @survey.save
19
+ redirect_to surveys_path, notice: I18n.t('surveys_controller.create')
20
+ else
21
+ render :new
22
+ end
23
+ end
24
+
25
+ def edit
26
+ # @survey is already loaded by before_action
27
+ end
28
+
29
+ def show
30
+ # @survey is already loaded by before_action
31
+ end
32
+
33
+ def update
34
+ if @survey.update(survey_params)
35
+ redirect_to surveys_path, notice: I18n.t('surveys_controller.update')
36
+ else
37
+ render :edit
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def load_survey
44
+ @survey = AskIt::Survey.find(params[:id])
45
+ rescue ActiveRecord::RecordNotFound
46
+ redirect_to surveys_path, alert: 'Survey not found'
47
+ end
48
+
49
+ def survey_params
50
+ params.require(:survey).permit(
51
+ :name, :description, :active,
52
+ sections_attributes: [:id, :name, :_destroy,
53
+ { questions_attributes: [:id, :text, :question_type, :_destroy,
54
+ { options_attributes: %i[id text _destroy] }] }]
55
+ )
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AskIt
4
+ class Answer < ActiveRecord::Base
5
+ self.table_name = 'survey_answers'
6
+ belongs_to :attempt
7
+ belongs_to :option
8
+ belongs_to :predefined_value
9
+ belongs_to :question
10
+
11
+ validates :option_id, :question_id, presence: true
12
+ validates :predefined_value_id, presence: true, if: :requires_predefined_value?
13
+ validates :option_text, presence: true, if: :requires_text_input?
14
+ validates :option_number, presence: true, if: :requires_number_input?
15
+
16
+ # rails 3 attr_accessible support
17
+ if Rails::VERSION::MAJOR < 4
18
+ attr_accessible :option, :attempt, :question, :question_id, :option_id, :predefined_value_id, :attempt_id,
19
+ :option_text, :option_number
20
+ end
21
+
22
+ before_create :characterize_answer
23
+ before_save :check_single_choice_with_field_case
24
+
25
+ def value
26
+ if option.nil?
27
+ AskIt::Option.find(option_id).weight
28
+ else
29
+ option.weight
30
+ end
31
+ end
32
+
33
+ def correct?
34
+ correct || option.correct?
35
+ end
36
+
37
+ #######
38
+
39
+ private
40
+
41
+ #######
42
+
43
+ def characterize_answer
44
+ self.correct = true if option.correct?
45
+ end
46
+
47
+ def check_single_choice_with_field_case
48
+ return unless [AskIt::OptionsType.multi_choices,
49
+ AskIt::OptionsType.single_choice].include?(option.options_type_id)
50
+
51
+ self.option_text = nil
52
+ self.option_number = nil
53
+ end
54
+
55
+ # Validations
56
+
57
+ def requires_predefined_value?
58
+ question&.mandatory? &&
59
+ question.predefined_values.count.positive? &&
60
+ !text_based_option?
61
+ end
62
+
63
+ def requires_text_input?
64
+ option &&
65
+ question &&
66
+ question.mandatory? &&
67
+ question.predefined_values.count.zero? &&
68
+ text_input_option?
69
+ end
70
+
71
+ def requires_number_input?
72
+ option &&
73
+ question &&
74
+ question.mandatory? &&
75
+ number_input_option?
76
+ end
77
+
78
+ def text_based_option?
79
+ [AskIt::OptionsType.text, AskIt::OptionsType.large_text].include?(option.options_type_id)
80
+ end
81
+
82
+ def text_input_option?
83
+ [
84
+ AskIt::OptionsType.text,
85
+ AskIt::OptionsType.multi_choices_with_text,
86
+ AskIt::OptionsType.single_choice_with_text,
87
+ AskIt::OptionsType.large_text
88
+ ].include?(option.options_type_id)
89
+ end
90
+
91
+ def number_input_option?
92
+ [
93
+ AskIt::OptionsType.number,
94
+ AskIt::OptionsType.multi_choices_with_number,
95
+ AskIt::OptionsType.single_choice_with_number
96
+ ].include?(option.options_type_id)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AskIt
4
+ class Attempt < ActiveRecord::Base
5
+ self.table_name = 'survey_attempts'
6
+
7
+ # relations
8
+
9
+ has_many :answers, dependent: :destroy
10
+ belongs_to :survey
11
+ belongs_to :participant, polymorphic: true
12
+
13
+ # rails 3 attr_accessible support
14
+ if Rails::VERSION::MAJOR < 4
15
+ attr_accessible :participant_id, :survey_id, :answers_attributes, :survey, :winner, :participant
16
+ end
17
+
18
+ # validations
19
+ validates :participant_id, :participant_type,
20
+ presence: true
21
+
22
+ accepts_nested_attributes_for :answers,
23
+ reject_if: ->(q) { q[:question_id].blank? && q[:option_id].blank? },
24
+ allow_destroy: true
25
+
26
+ # scopes
27
+
28
+ scope :for_survey, lambda { |survey|
29
+ where(survey_id: survey.try(:id))
30
+ }
31
+
32
+ scope :exclude_survey, lambda { |survey|
33
+ where("NOT survey_id = #{survey.try(:id)}")
34
+ }
35
+
36
+ scope :for_participant, lambda { |participant|
37
+ where(participant_id: participant.try(:id),
38
+ participant_type: participant.class.to_s)
39
+ }
40
+
41
+ scope :wins, -> { where(winner: true) }
42
+ scope :looses, -> { where(winner: false) }
43
+ scope :scores, -> { order('score DESC') }
44
+
45
+ # callbacks
46
+
47
+ validate :check_number_of_attempts_by_survey, on: :create
48
+ after_create :collect_scores
49
+
50
+ def correct_answers
51
+ answers.where(correct: true)
52
+ end
53
+
54
+ def incorrect_answers
55
+ answers.where(correct: false)
56
+ end
57
+
58
+ def collect_scores!
59
+ collect_scores
60
+ save
61
+ end
62
+
63
+ def self.high_score
64
+ scores.first.score
65
+ end
66
+
67
+ private
68
+
69
+ def check_number_of_attempts_by_survey
70
+ attempts = self.class.for_survey(survey).for_participant(participant)
71
+ upper_bound = survey.attempts_number
72
+ return unless attempts.size >= upper_bound && upper_bound.nonzero?
73
+
74
+ errors.add(:questionnaire_id,
75
+ 'Number of attempts exceeded')
76
+ end
77
+
78
+ def collect_scores
79
+ multi_select_questions = AskIt::Question.joins(:section)
80
+ .where(survey_sections: { survey_id: survey.id },
81
+ survey_questions: {
82
+ questions_type_id: AskIt::QuestionType.multi_select
83
+ })
84
+ if multi_select_questions.empty? # No multi-select questions
85
+ raw_score = answers.map(&:value).reduce(:+)
86
+ self.score = raw_score
87
+ else
88
+ # Initial score without multi-select questions
89
+ raw_score = answers.where.not(question_id: multi_select_questions.ids).map(&:value).reduce(:+) || 0
90
+ multi_select_questions.each do |question|
91
+ options = question.options
92
+ correct_question_answers = answers.where(question_id: question.id, correct: true)
93
+ break if correct_question_answers.empty? # If they didn't select any correct answers, then skip this step
94
+
95
+ correct_options_sum = options.correct.map(&:weight).reduce(:+)
96
+ correct_percentage = correct_question_answers.map(&:value).reduce(:+).fdiv(correct_options_sum)
97
+ raw_score += correct_percentage
98
+ if correct_percentage == 1
99
+ option_value = 1 / options.count.to_f
100
+ raw_score -= (option_value * answers.where(question_id: question.id, correct: false).count)
101
+ end
102
+ end
103
+ self.score = raw_score || 0
104
+ save
105
+ end
106
+ end
107
+ end
108
+ end