ask_it 0.1.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 (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