effective_questions 0.0.2 → 0.0.4
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.
- checksums.yaml +4 -4
- data/app/controllers/admin/responses_controller.rb +13 -0
- data/app/datatables/admin/effective_questions_datatable.rb +7 -2
- data/app/datatables/admin/effective_responses_datatable.rb +24 -0
- data/app/models/concerns/acts_as_questionable.rb +4 -0
- data/app/models/concerns/acts_as_responsable.rb +14 -0
- data/app/models/effective/question.rb +67 -2
- data/app/models/effective/question_answer.rb +204 -0
- data/app/models/effective/question_option.rb +2 -0
- data/app/models/effective/response.rb +78 -7
- data/app/models/effective/response_option.rb +5 -0
- data/app/views/admin/questions/_fields_category.html.haml +76 -0
- data/app/views/admin/questions/_fields_scored.html.haml +131 -0
- data/app/views/admin/questions/_form.html.haml +7 -86
- data/app/views/effective/questionable/results/_question_option.html.haml +1 -1
- data/app/views/effective/responsable/_results.html.haml +27 -1
- data/app/views/effective/responses/fields/_decimal.html.haml +2 -0
- data/app/views/effective/responses/fields/_percentage.html.haml +2 -0
- data/app/views/effective/responses/fields/_price.html.haml +2 -0
- data/app/views/effective/responses/responses/_choose_one.html.haml +1 -1
- data/app/views/effective/responses/responses/_decimal.html.haml +1 -0
- data/app/views/effective/responses/responses/_percentage.html.haml +5 -0
- data/app/views/effective/responses/responses/_price.html.haml +1 -0
- data/app/views/effective/responses/responses/_select_all_that_apply.html.haml +2 -2
- data/app/views/effective/responses/responses/_select_up_to_1.html.haml +1 -1
- data/app/views/effective/responses/responses/_select_up_to_2.html.haml +2 -2
- data/app/views/effective/responses/responses/_select_up_to_3.html.haml +2 -2
- data/app/views/effective/responses/responses/_select_up_to_4.html.haml +2 -2
- data/app/views/effective/responses/responses/_select_up_to_5.html.haml +2 -2
- data/config/routes.rb +1 -0
- data/db/migrate/101_create_effective_questions.rb +44 -0
- data/lib/effective_questions/version.rb +1 -1
- metadata +13 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7b1be795579cdb74c263712b86c74f5b5fe34e2e1d87fbbb57fc8cef6fc07510
|
|
4
|
+
data.tar.gz: 1acfbae757e1852f801024ee6679a9d9c1661cd8fd3f5e4c37d24807dad2b0f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16c8a5452a08adf9fe820e809c5a5847a460f01040a3356b1dc8448b50f9e268e3ea4e18a747fa842550898c5856cf44081fe86fb19763dc146af3f65bb76cdb
|
|
7
|
+
data.tar.gz: 7cddf83480381e353009cbbf263c3fc7c9c902e0d9c447c3ef7125d61d1dde8d76cc89a27ddd87ba3204726d4ad1fc43873f4c703bc728bef563d5a4dfe6bd93
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Admin
|
|
2
|
+
class ResponsesController < ApplicationController
|
|
3
|
+
before_action(:authenticate_user!) if defined?(Devise)
|
|
4
|
+
before_action { EffectiveResources.authorize!(self, :admin, :effective_questions) }
|
|
5
|
+
|
|
6
|
+
include Effective::CrudController
|
|
7
|
+
|
|
8
|
+
def permitted_params
|
|
9
|
+
params.require(:effective_response).permit!
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -12,12 +12,14 @@ class Admin::EffectiveQuestionsDatatable < Effective::Datatable
|
|
|
12
12
|
col :questionable
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
col :position, visible:
|
|
15
|
+
col :position, visible: attributes[:follow_up].blank? do |question|
|
|
16
16
|
question.position.to_i + 1
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
col :title
|
|
20
|
-
col :
|
|
20
|
+
col :task
|
|
21
|
+
col :points
|
|
22
|
+
col :body, as: :text, visible: false
|
|
21
23
|
col :required
|
|
22
24
|
|
|
23
25
|
col :category, label: 'Type'
|
|
@@ -27,6 +29,9 @@ class Admin::EffectiveQuestionsDatatable < Effective::Datatable
|
|
|
27
29
|
col :follow_up_questions, action: false, label: 'Follow up questions'
|
|
28
30
|
end
|
|
29
31
|
|
|
32
|
+
col :scored
|
|
33
|
+
col :answer_to_s, label: 'Answer'
|
|
34
|
+
|
|
30
35
|
actions_col
|
|
31
36
|
end
|
|
32
37
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class Admin::EffectiveResponsesDatatable < Effective::Datatable
|
|
2
|
+
datatable do
|
|
3
|
+
col :updated_at, visible: false
|
|
4
|
+
col :created_at, visible: false
|
|
5
|
+
col :id, visible: false
|
|
6
|
+
|
|
7
|
+
col :responsable
|
|
8
|
+
col :questionable
|
|
9
|
+
col :question
|
|
10
|
+
|
|
11
|
+
col(:response) do |response|
|
|
12
|
+
render('effective/responses/response', response: response)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
col :correct?, as: :boolean
|
|
16
|
+
|
|
17
|
+
actions_col
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
collection do
|
|
21
|
+
Effective::Response.all.deep
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
@@ -27,4 +27,18 @@ module ActsAsResponsable
|
|
|
27
27
|
response ||= responses.build(questionable: question.questionable, question: question)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
def responsable_completed?
|
|
31
|
+
try(:done?) == true || try(:completed?) == true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def scored_responses
|
|
35
|
+
responses.select { |r| r.question&.scored? }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def responsable_score
|
|
39
|
+
scored = scored_responses()
|
|
40
|
+
return 0 unless scored.present?
|
|
41
|
+
|
|
42
|
+
((scored.count(&:correct?).to_f / scored.length) * 100_000).round
|
|
43
|
+
end
|
|
30
44
|
end
|
|
@@ -4,11 +4,13 @@ module Effective
|
|
|
4
4
|
|
|
5
5
|
belongs_to :question, optional: true # Present when I'm a follow up question
|
|
6
6
|
belongs_to :question_option, optional: true # Might be present when I'm a follow up question
|
|
7
|
+
has_many :follow_up_questions, -> { order(:position) }, class_name: 'Effective::Question', foreign_key: :question_id, dependent: :destroy
|
|
7
8
|
|
|
8
9
|
has_many :question_options, -> { order(:position) }, inverse_of: :question, dependent: :delete_all
|
|
9
|
-
accepts_nested_attributes_for :question_options, reject_if:
|
|
10
|
+
accepts_nested_attributes_for :question_options, reject_if: -> (atts) { atts['title'].blank? }, allow_destroy: true
|
|
10
11
|
|
|
11
|
-
has_many :
|
|
12
|
+
has_many :question_answers, dependent: :delete_all
|
|
13
|
+
accepts_nested_attributes_for :question_answers, reject_if: :all_blank, allow_destroy: true
|
|
12
14
|
|
|
13
15
|
has_rich_text :body
|
|
14
16
|
log_changes(to: :questionable) if respond_to?(:log_changes)
|
|
@@ -24,8 +26,11 @@ module Effective
|
|
|
24
26
|
'Select up to 4',
|
|
25
27
|
'Select up to 5',
|
|
26
28
|
'Date', # Date Field
|
|
29
|
+
'Decimal', # Decimal Field
|
|
27
30
|
'Email', # Email Field
|
|
28
31
|
'Number', # Numeric Field
|
|
32
|
+
'Percentage', # Percentage Field
|
|
33
|
+
'Price', # Price Field
|
|
29
34
|
'Long Answer', # Text Area
|
|
30
35
|
'Short Answer', # Text Field
|
|
31
36
|
'Upload File' # File field
|
|
@@ -53,6 +58,11 @@ module Effective
|
|
|
53
58
|
follow_up :boolean
|
|
54
59
|
follow_up_value :string
|
|
55
60
|
|
|
61
|
+
scored :boolean
|
|
62
|
+
|
|
63
|
+
task :string
|
|
64
|
+
points :decimal
|
|
65
|
+
|
|
56
66
|
timestamps
|
|
57
67
|
end
|
|
58
68
|
|
|
@@ -81,6 +91,30 @@ module Effective
|
|
|
81
91
|
validates :question_option, presence: true, if: -> { follow_up? && question.try(:question_option?) }
|
|
82
92
|
validates :follow_up_value, presence: true, if: -> { follow_up? && !question.try(:question_option?) }
|
|
83
93
|
|
|
94
|
+
validates :scored, inclusion: { in: [false], message: 'is not supported' }, if: -> { category == 'Upload File' }
|
|
95
|
+
|
|
96
|
+
validates :question_answer, presence: true, if: -> { scored? }
|
|
97
|
+
|
|
98
|
+
validate(if: -> { scored? && (choose_one? || select_up_to_1? || select_all_that_apply?) }) do
|
|
99
|
+
errors.add(:base, 'must have at least one correct answer option') if answer_options.count < 1
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
validate(if: -> { scored? && select_up_to_2? }) do
|
|
103
|
+
errors.add(:base, 'must have two or more correct answer options') if answer_options.count < 2
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
validate(if: -> { scored? && select_up_to_3? }) do
|
|
107
|
+
errors.add(:base, 'must have three or more correct answer options') if answer_options.count < 3
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
validate(if: -> { scored? && select_up_to_4? }) do
|
|
111
|
+
errors.add(:base, 'must have four or more correct answer options') if answer_options.count < 4
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
validate(if: -> { scored? && select_up_to_5? }) do
|
|
115
|
+
errors.add(:base, 'must have five or more correct answer options') if answer_options.count < 5
|
|
116
|
+
end
|
|
117
|
+
|
|
84
118
|
# Create choose_one? and select_all_that_apply? methods for each category
|
|
85
119
|
CATEGORIES.each do |category|
|
|
86
120
|
define_method(category.parameterize.underscore + '?') { self.category == category }
|
|
@@ -95,8 +129,11 @@ module Effective
|
|
|
95
129
|
|
|
96
130
|
case category
|
|
97
131
|
when 'Date' then :date
|
|
132
|
+
when 'Decimal' then :decimal
|
|
98
133
|
when 'Email' then :email
|
|
99
134
|
when 'Number' then :number
|
|
135
|
+
when 'Percentage' then :percentage
|
|
136
|
+
when 'Price' then :price
|
|
100
137
|
when 'Long Answer' then :long_answer
|
|
101
138
|
when 'Short Answer' then :short_answer
|
|
102
139
|
when 'Upload File' then :upload_file
|
|
@@ -104,6 +141,26 @@ module Effective
|
|
|
104
141
|
end
|
|
105
142
|
end
|
|
106
143
|
|
|
144
|
+
def answer
|
|
145
|
+
return unless scored?
|
|
146
|
+
question_option? ? answer_options : question_answer.try(:answer)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def answer_to_s
|
|
150
|
+
return '' unless scored?
|
|
151
|
+
|
|
152
|
+
case category
|
|
153
|
+
when 'Choose one' then "One of: #{answer_options.join(', ')}"
|
|
154
|
+
when 'Select all that apply' then "All of: #{answer_options.join(', ')}"
|
|
155
|
+
when 'Select up to 1' then "One of: #{answer_options.join(', ')}"
|
|
156
|
+
when 'Select up to 2' then "Two of: #{answer_options.join(', ')}"
|
|
157
|
+
when 'Select up to 3' then "Three of: #{answer_options.join(', ')}"
|
|
158
|
+
when 'Select up to 4' then "Four of: #{answer_options.join(', ')}"
|
|
159
|
+
when 'Select up to 5' then "Five of: #{answer_options.join(', ')}"
|
|
160
|
+
else question_answer.try(:to_s)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
107
164
|
def show_if_value
|
|
108
165
|
question.try(:question_option?) ? question_option_id : follow_up_value
|
|
109
166
|
end
|
|
@@ -116,9 +173,17 @@ module Effective
|
|
|
116
173
|
WITH_OPTIONS_CATEGORIES.include?(category)
|
|
117
174
|
end
|
|
118
175
|
|
|
176
|
+
def answer_options
|
|
177
|
+
question_options.reject(&:marked_for_destruction?).select(&:answer?)
|
|
178
|
+
end
|
|
179
|
+
|
|
119
180
|
def category_partial
|
|
120
181
|
category.to_s.parameterize.underscore
|
|
121
182
|
end
|
|
122
183
|
|
|
184
|
+
def question_answer
|
|
185
|
+
question_answers.first || question_answers.build()
|
|
186
|
+
end
|
|
187
|
+
|
|
123
188
|
end
|
|
124
189
|
end
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
module Effective
|
|
2
|
+
class QuestionAnswer < ActiveRecord::Base
|
|
3
|
+
belongs_to :question
|
|
4
|
+
|
|
5
|
+
OPERATIONS = [
|
|
6
|
+
'Equal to',
|
|
7
|
+
'Within range',
|
|
8
|
+
'Less than',
|
|
9
|
+
'Less than or equal to',
|
|
10
|
+
'Greater than',
|
|
11
|
+
'Greater than or equal to',
|
|
12
|
+
'Contains',
|
|
13
|
+
'Does not contain'
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
effective_resource do
|
|
17
|
+
operation :string
|
|
18
|
+
|
|
19
|
+
# The answer
|
|
20
|
+
date :date
|
|
21
|
+
date_begin :date
|
|
22
|
+
date_end :date
|
|
23
|
+
|
|
24
|
+
decimal :decimal
|
|
25
|
+
decimal_begin :decimal
|
|
26
|
+
decimal_end :decimal
|
|
27
|
+
|
|
28
|
+
email :string
|
|
29
|
+
|
|
30
|
+
number :integer
|
|
31
|
+
number_begin :integer
|
|
32
|
+
number_end :integer
|
|
33
|
+
|
|
34
|
+
percentage :integer
|
|
35
|
+
percentage_begin :integer
|
|
36
|
+
percentage_end :integer
|
|
37
|
+
|
|
38
|
+
price :integer
|
|
39
|
+
price_begin :integer
|
|
40
|
+
price_end :integer
|
|
41
|
+
|
|
42
|
+
long_answer :text
|
|
43
|
+
short_answer :text
|
|
44
|
+
|
|
45
|
+
timestamps
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
scope :deep, -> { includes(:question) }
|
|
49
|
+
|
|
50
|
+
with_options(unless: -> { question.blank? }) do
|
|
51
|
+
validate(if: -> { question.category.present? }) do
|
|
52
|
+
supported = operations(question.category)
|
|
53
|
+
errors.add(:operation, 'is not supported for this question') if supported.present? && supported.exclude?(operation)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
validates :date, presence: true, if: -> { question.date? && (equals? || gteq? || lteq? || lt? || gt?) }
|
|
57
|
+
validates :date_begin, presence: true, if: -> { question.date? && within_range? }
|
|
58
|
+
validates :date_end, presence: true, if: -> { question.date? && within_range? }
|
|
59
|
+
|
|
60
|
+
validates :decimal, presence: true, if: -> { question.decimal? && (equals? || gteq? || lteq? || lt? || gt?) }
|
|
61
|
+
validates :decimal_begin, presence: true, if: -> { question.decimal? && within_range? }
|
|
62
|
+
validates :decimal_end, presence: true, if: -> { question.decimal? && within_range? }
|
|
63
|
+
|
|
64
|
+
validates :email, presence: true, if: -> { question.email? && (equals? || contains? || does_not_contain?) }
|
|
65
|
+
validates :email, email: true, if: -> { question.email? && equals? }
|
|
66
|
+
|
|
67
|
+
validates :number, presence: true, if: -> { question.number? && (equals? || gteq? || lteq? || lt? || gt?) }
|
|
68
|
+
validates :number_begin, presence: true, if: -> { question.number? && within_range? }
|
|
69
|
+
validates :number_end, presence: true, if: -> { question.number? && within_range? }
|
|
70
|
+
|
|
71
|
+
validates :percentage, presence: true, if: -> { question.percentage? && (equals? || gteq? || lteq? || lt? || gt?) }
|
|
72
|
+
validates :percentage_begin, presence: true, if: -> { question.percentage? && within_range? }
|
|
73
|
+
validates :percentage_end, presence: true, if: -> { question.percentage? && within_range? }
|
|
74
|
+
|
|
75
|
+
validates :price, presence: true, if: -> { question.price? && (equals? || gteq? || lteq? || lt? || gt?) }
|
|
76
|
+
validates :price_begin, presence: true, if: -> { question.price? && within_range? }
|
|
77
|
+
validates :price_end, presence: true, if: -> { question.price? && within_range? }
|
|
78
|
+
|
|
79
|
+
validates :long_answer, presence: true, if: -> { question.long_answer? }
|
|
80
|
+
validates :short_answer, presence: true, if: -> { question.short_answer? }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def to_s
|
|
84
|
+
return '' unless question.present?
|
|
85
|
+
return '-' unless operations(question.category).present?
|
|
86
|
+
|
|
87
|
+
case operation
|
|
88
|
+
when 'Equal to'
|
|
89
|
+
"Equal to #{format_value(answer)}"
|
|
90
|
+
when 'Within range'
|
|
91
|
+
"Between #{format_value(answer.begin)} and #{format_value(answer.end)}"
|
|
92
|
+
when 'Less than'
|
|
93
|
+
"Less than #{format_value(answer)}"
|
|
94
|
+
when 'Less than or equal to'
|
|
95
|
+
"Less than or equal to #{format_value(answer)}"
|
|
96
|
+
when 'Greater than'
|
|
97
|
+
"Greater than #{format_value(answer)}"
|
|
98
|
+
when 'Greater than or equal to'
|
|
99
|
+
"Greater than or equal to #{format_value(answer)}"
|
|
100
|
+
when 'Contains'
|
|
101
|
+
"Contains #{format_value(answer)}"
|
|
102
|
+
when 'Does not contain'
|
|
103
|
+
"Does not contain #{format_value(answer)}"
|
|
104
|
+
else
|
|
105
|
+
raise("unknown operation: #{operation}")
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def operations(category)
|
|
110
|
+
case category
|
|
111
|
+
when 'Choose one' then nil
|
|
112
|
+
when 'Select all that apply' then nil
|
|
113
|
+
when 'Select up to 1' then nil
|
|
114
|
+
when 'Select up to 2' then nil
|
|
115
|
+
when 'Select up to 3' then nil
|
|
116
|
+
when 'Select up to 4' then nil
|
|
117
|
+
when 'Select up to 5' then nil
|
|
118
|
+
when 'Upload File' then nil
|
|
119
|
+
when 'Date' then OPERATIONS - ['Contains', 'Does not contain']
|
|
120
|
+
when 'Decimal' then OPERATIONS - ['Contains', 'Does not contain']
|
|
121
|
+
when 'Number' then OPERATIONS - ['Contains', 'Does not contain']
|
|
122
|
+
when 'Percentage' then OPERATIONS - ['Contains', 'Does not contain']
|
|
123
|
+
when 'Price' then OPERATIONS - ['Contains', 'Does not contain']
|
|
124
|
+
when 'Email' then ['Equal to', 'Contains', 'Does not contain']
|
|
125
|
+
when 'Long Answer' then ['Equal to', 'Contains', 'Does not contain']
|
|
126
|
+
when 'Short Answer' then ['Equal to', 'Contains', 'Does not contain']
|
|
127
|
+
else
|
|
128
|
+
raise("unknown operations for category: #{category}")
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def answer
|
|
133
|
+
if operation == 'Within range'
|
|
134
|
+
return (date_begin..date_end) if question.date?
|
|
135
|
+
return (decimal_begin..decimal_end) if question.decimal?
|
|
136
|
+
return (number_begin..number_end) if question.number?
|
|
137
|
+
return (percentage_begin..percentage_end) if question.percentage?
|
|
138
|
+
return (price_begin..price_end) if question.price?
|
|
139
|
+
else
|
|
140
|
+
return date if question.date?
|
|
141
|
+
return decimal if question.decimal?
|
|
142
|
+
return email if question.email?
|
|
143
|
+
return number if question.number?
|
|
144
|
+
return percentage if question.percentage?
|
|
145
|
+
return price if question.price?
|
|
146
|
+
return long_answer if question.long_answer?
|
|
147
|
+
return short_answer if question.short_answer?
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
raise("unknown operation: #{operation}")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def equals?
|
|
154
|
+
operation == 'Equal to'
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def contains?
|
|
158
|
+
operation == 'Contains'
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def does_not_contain?
|
|
162
|
+
operation == 'Does not contain'
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def within_range?
|
|
166
|
+
operation == 'Within range'
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def lt?
|
|
170
|
+
operation == 'Less than'
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def gt?
|
|
174
|
+
operation == 'Greater than'
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def lteq?
|
|
178
|
+
operation == 'Less than or equal to'
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def gteq?
|
|
182
|
+
operation == 'Greater than or equal to'
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
|
|
187
|
+
def format_value(val)
|
|
188
|
+
return '' if val.blank?
|
|
189
|
+
|
|
190
|
+
if question.price?
|
|
191
|
+
"$#{'%0.2f' % (val / 100.0)}"
|
|
192
|
+
elsif question.percentage?
|
|
193
|
+
precision = (val % 1000).zero? ? 0 : 3
|
|
194
|
+
"#{format("%.#{precision}f", val.to_f / 1000)}%"
|
|
195
|
+
elsif question.date?
|
|
196
|
+
val.strftime('%Y-%m-%d')
|
|
197
|
+
elsif val.is_a?(Numeric)
|
|
198
|
+
val.to_s
|
|
199
|
+
else
|
|
200
|
+
"\"#{val}\""
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
@@ -12,8 +12,11 @@ module Effective
|
|
|
12
12
|
effective_resource do
|
|
13
13
|
# The response
|
|
14
14
|
date :date
|
|
15
|
+
decimal :decimal
|
|
15
16
|
email :string
|
|
16
17
|
number :integer
|
|
18
|
+
percentage :integer
|
|
19
|
+
price :integer
|
|
17
20
|
long_answer :text
|
|
18
21
|
short_answer :text
|
|
19
22
|
|
|
@@ -27,6 +30,9 @@ module Effective
|
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
validates :date, presence: true, if: -> { question&.required? && question.date? }
|
|
33
|
+
validates :decimal, presence: true, if: -> { question&.required? && question.decimal? }
|
|
34
|
+
validates :percentage, presence: true, if: -> { question&.required? && question.percentage? }
|
|
35
|
+
validates :price, presence: true, if: -> { question&.required? && question.price? }
|
|
30
36
|
validates :email, presence: true, email: true, if: -> { question&.required? && question.email? }
|
|
31
37
|
validates :number, presence: true, if: -> { question&.required? && question.number? }
|
|
32
38
|
validates :long_answer, presence: true, if: -> { question&.required? && question.long_answer? }
|
|
@@ -53,8 +59,24 @@ module Effective
|
|
|
53
59
|
length: { maximum: 5, message: 'please select 5 options or fewer' }
|
|
54
60
|
|
|
55
61
|
def to_s
|
|
56
|
-
|
|
57
|
-
response.
|
|
62
|
+
return '' unless question.present?
|
|
63
|
+
return '' unless response.present?
|
|
64
|
+
|
|
65
|
+
case question.category
|
|
66
|
+
when 'Date'
|
|
67
|
+
response.strftime('%Y-%m-%d')
|
|
68
|
+
when 'Price'
|
|
69
|
+
"$#{'%0.2f' % (response / 100.0)}"
|
|
70
|
+
when 'Percentage'
|
|
71
|
+
precision = (response % 1000).zero? ? 0 : 3
|
|
72
|
+
"#{format("%.#{precision}f", response.to_f / 1000)}%"
|
|
73
|
+
when 'Upload File'
|
|
74
|
+
response.filename.to_s
|
|
75
|
+
when 'Select all that apply', 'Select up to 2', 'Select up to 3', 'Select up to 4', 'Select up to 5'
|
|
76
|
+
Array(response).map(&:to_s).join(', ')
|
|
77
|
+
else
|
|
78
|
+
response.to_s
|
|
79
|
+
end
|
|
58
80
|
end
|
|
59
81
|
|
|
60
82
|
def response
|
|
@@ -63,13 +85,16 @@ module Effective
|
|
|
63
85
|
return date if question.date?
|
|
64
86
|
return email if question.email?
|
|
65
87
|
return number if question.number?
|
|
88
|
+
return percentage if question.percentage?
|
|
89
|
+
return price if question.price?
|
|
90
|
+
return decimal if question.decimal?
|
|
66
91
|
return long_answer if question.long_answer?
|
|
67
92
|
return short_answer if question.short_answer?
|
|
68
93
|
return upload_file if question.upload_file?
|
|
69
94
|
|
|
70
|
-
return
|
|
71
|
-
return
|
|
72
|
-
return
|
|
95
|
+
return response_options.first if question.choose_one?
|
|
96
|
+
return response_options.first if question.select_up_to_1?
|
|
97
|
+
return response_options if question.question_option?
|
|
73
98
|
|
|
74
99
|
raise('unknown response for unexpected question category')
|
|
75
100
|
end
|
|
@@ -79,8 +104,54 @@ module Effective
|
|
|
79
104
|
end
|
|
80
105
|
|
|
81
106
|
def completed?
|
|
82
|
-
|
|
83
|
-
|
|
107
|
+
responsable.responsable_completed?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def correct?
|
|
111
|
+
return unless question.present? && question.scored?
|
|
112
|
+
return false unless response.present?
|
|
113
|
+
|
|
114
|
+
if question.question_option?
|
|
115
|
+
# For option-based questions, check if selected options match answer options
|
|
116
|
+
answers = question.answer.map(&:id)
|
|
117
|
+
selected = Array(response).map(&:question_option_id)
|
|
118
|
+
|
|
119
|
+
if question.select_all_that_apply?
|
|
120
|
+
return selected.sort == answers.sort
|
|
121
|
+
else
|
|
122
|
+
# For choose_one or select_up_to_X, all selected must be correct
|
|
123
|
+
return selected.present? && (selected - answers).blank?
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# For non-option questions, check against question_answer
|
|
128
|
+
answer = question.question_answer
|
|
129
|
+
return false unless answer.present?
|
|
130
|
+
|
|
131
|
+
case answer.operation
|
|
132
|
+
when 'Equal to'
|
|
133
|
+
if response.is_a?(String) && answer.answer.is_a?(String)
|
|
134
|
+
response.downcase.strip == answer.answer.downcase.strip
|
|
135
|
+
else
|
|
136
|
+
response == answer.answer
|
|
137
|
+
end
|
|
138
|
+
when 'Within range'
|
|
139
|
+
answer.answer.cover?(response)
|
|
140
|
+
when 'Less than'
|
|
141
|
+
response < answer.answer
|
|
142
|
+
when 'Less than or equal to'
|
|
143
|
+
response <= answer.answer
|
|
144
|
+
when 'Greater than'
|
|
145
|
+
response > answer.answer
|
|
146
|
+
when 'Greater than or equal to'
|
|
147
|
+
response >= answer.answer
|
|
148
|
+
when 'Contains'
|
|
149
|
+
response.to_s.downcase.include?(answer.answer.to_s.downcase)
|
|
150
|
+
when 'Does not contain'
|
|
151
|
+
!response.to_s.downcase.include?(answer.answer.to_s.downcase)
|
|
152
|
+
else
|
|
153
|
+
false
|
|
154
|
+
end
|
|
84
155
|
end
|
|
85
156
|
|
|
86
157
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
= f.show_if :category, 'Choose one' do
|
|
2
|
+
= card('Choose one') do
|
|
3
|
+
%p Display radio buttons to select one option
|
|
4
|
+
|
|
5
|
+
= f.show_if :category, 'Select all that apply' do
|
|
6
|
+
= card('Select all that apply') do
|
|
7
|
+
%p Display checkboxes to select all options that apply
|
|
8
|
+
|
|
9
|
+
= f.show_if :category, 'Select up to 1' do
|
|
10
|
+
= card('Select up to 1 (one)') do
|
|
11
|
+
%p Display checkboxes to select up to 1 option
|
|
12
|
+
|
|
13
|
+
= f.show_if :category, 'Select up to 2' do
|
|
14
|
+
= card('Select up to 2 (two)') do
|
|
15
|
+
%p Display checkboxes to select up to 2 options
|
|
16
|
+
|
|
17
|
+
= f.show_if :category, 'Select up to 3' do
|
|
18
|
+
= card('Select up to 3 (three)') do
|
|
19
|
+
%p Display checkboxes to select up to 3 options
|
|
20
|
+
|
|
21
|
+
= f.show_if :category, 'Select up to 4' do
|
|
22
|
+
= card('Select up to 4 (four)') do
|
|
23
|
+
%p Display checkboxes to select up to 4 options
|
|
24
|
+
|
|
25
|
+
= f.show_if :category, 'Select up to 5' do
|
|
26
|
+
= card('Select up to 5 (five)') do
|
|
27
|
+
%p Display checkboxes to select up to 5 options
|
|
28
|
+
|
|
29
|
+
= f.show_if :category, 'Short Answer' do
|
|
30
|
+
= card('Short Answer') do
|
|
31
|
+
%p Display a text field to enter a short text answer
|
|
32
|
+
|
|
33
|
+
= f.show_if :category, 'Long Answer' do
|
|
34
|
+
= card('Long Answer') do
|
|
35
|
+
%p Display a textarea to enter a long text answer
|
|
36
|
+
|
|
37
|
+
= f.show_if :category, 'Date' do
|
|
38
|
+
= card('Date') do
|
|
39
|
+
%p Display a date field to enter a date
|
|
40
|
+
|
|
41
|
+
= f.show_if :category, 'Decimal' do
|
|
42
|
+
= card('Decimal') do
|
|
43
|
+
%p Display a decimal field to enter a decimal number
|
|
44
|
+
|
|
45
|
+
= f.show_if :category, 'Email' do
|
|
46
|
+
= card('Email') do
|
|
47
|
+
%p Display an email field to enter an email
|
|
48
|
+
|
|
49
|
+
= f.show_if :category, 'Number' do
|
|
50
|
+
= card('Number') do
|
|
51
|
+
%p Display a number field to enter an integer number
|
|
52
|
+
|
|
53
|
+
= f.show_if :category, 'Percentage' do
|
|
54
|
+
= card('Percentage') do
|
|
55
|
+
%p Display a percentage field to enter a percentage
|
|
56
|
+
|
|
57
|
+
= f.show_if :category, 'Price' do
|
|
58
|
+
= card('Price') do
|
|
59
|
+
%p Display a price field to enter a price
|
|
60
|
+
|
|
61
|
+
= f.show_if :category, 'Upload File' do
|
|
62
|
+
= card('Upload File') do
|
|
63
|
+
%p Display a file field to upload a file
|
|
64
|
+
|
|
65
|
+
= f.show_if_any :category, Effective::Question::WITH_OPTIONS_CATEGORIES do
|
|
66
|
+
.mt-3
|
|
67
|
+
= card do
|
|
68
|
+
%h5 Options
|
|
69
|
+
%p Display the following options:
|
|
70
|
+
|
|
71
|
+
= f.has_many :question_options, class: 'tight' do |fa|
|
|
72
|
+
.row
|
|
73
|
+
.col= fa.text_field :title, label: false
|
|
74
|
+
= f.show_if(:scored, true) do
|
|
75
|
+
.col.mr-3= fa.check_box :answer, label: "Correct answer"
|
|
76
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
= f.show_if(:scored, true) do
|
|
2
|
+
= f.fields_for :question_answers, f.object.question_answer do |fa|
|
|
3
|
+
= card('Answer') do
|
|
4
|
+
%p The answer to this question is:
|
|
5
|
+
|
|
6
|
+
= f.show_if :category, 'Choose one' do
|
|
7
|
+
%ul
|
|
8
|
+
%li One correct answer option as selected above.
|
|
9
|
+
|
|
10
|
+
= f.show_if :category, 'Select up to 1' do
|
|
11
|
+
%ul
|
|
12
|
+
%li One correct answer option as selected above.
|
|
13
|
+
|
|
14
|
+
= f.show_if :category, 'Select up to 2' do
|
|
15
|
+
%ul
|
|
16
|
+
%li Two correct answer options as selected above.
|
|
17
|
+
|
|
18
|
+
= f.show_if :category, 'Select up to 3' do
|
|
19
|
+
%ul
|
|
20
|
+
%li Three correct answer options as selected above.
|
|
21
|
+
|
|
22
|
+
= f.show_if :category, 'Select up to 4' do
|
|
23
|
+
%ul
|
|
24
|
+
%li Four correct answer options as selected above.
|
|
25
|
+
|
|
26
|
+
= f.show_if :category, 'Select up to 5' do
|
|
27
|
+
%ul
|
|
28
|
+
%li Five correct answer options as selected above.
|
|
29
|
+
|
|
30
|
+
= f.show_if :category, 'Select all that apply' do
|
|
31
|
+
%ul
|
|
32
|
+
%li All correct answer options as selected above.
|
|
33
|
+
|
|
34
|
+
= f.show_if :category, 'Short Answer' do
|
|
35
|
+
- operations = fa.object.operations('Short Answer')
|
|
36
|
+
= fa.radios :operation, operations, buttons: true, label: false
|
|
37
|
+
|
|
38
|
+
= fa.show_if(:operation, 'Equal to') do
|
|
39
|
+
= fa.text_field :short_answer
|
|
40
|
+
|
|
41
|
+
= fa.show_if_any(:operation, operations - ['Equal to']) do
|
|
42
|
+
= fa.text_field :short_answer, label: 'Matched text', hint: 'Does not support wildcards'
|
|
43
|
+
|
|
44
|
+
= f.show_if :category, 'Long Answer' do
|
|
45
|
+
- operations = fa.object.operations('Long Answer')
|
|
46
|
+
= fa.radios :operation, operations, buttons: true, label: false
|
|
47
|
+
|
|
48
|
+
= fa.show_if(:operation, 'Equal to') do
|
|
49
|
+
= fa.text_field :long_answer
|
|
50
|
+
|
|
51
|
+
= fa.show_if_any(:operation, operations - ['Equal to']) do
|
|
52
|
+
= fa.text_field :long_answer, label: 'Matched text', hint: 'Does not support wildcards'
|
|
53
|
+
|
|
54
|
+
= f.show_if :category, 'Date' do
|
|
55
|
+
- operations = fa.object.operations('Date')
|
|
56
|
+
|
|
57
|
+
= fa.radios :operation, operations, buttons: true, label: false
|
|
58
|
+
|
|
59
|
+
= fa.show_if_any(:operation, operations - ['Within range']) do
|
|
60
|
+
.d-flex= fa.date_field :date
|
|
61
|
+
|
|
62
|
+
= fa.show_if(:operation, 'Within range') do
|
|
63
|
+
.d-flex.align-items-center
|
|
64
|
+
= fa.date_field :date_begin
|
|
65
|
+
%span.mx-4 to
|
|
66
|
+
= fa.date_field :date_end
|
|
67
|
+
|
|
68
|
+
= f.show_if :category, 'Decimal' do
|
|
69
|
+
- operations = fa.object.operations('Decimal')
|
|
70
|
+
= fa.radios :operation, operations, buttons: true, label: false
|
|
71
|
+
|
|
72
|
+
= fa.show_if_any(:operation, operations - ['Within range']) do
|
|
73
|
+
.d-flex= fa.float_field :decimal
|
|
74
|
+
|
|
75
|
+
= fa.show_if(:operation, 'Within range') do
|
|
76
|
+
.d-flex.align-items-center
|
|
77
|
+
= fa.float_field :decimal_begin
|
|
78
|
+
%span.mx-4 to
|
|
79
|
+
= fa.float_field :decimal_end
|
|
80
|
+
|
|
81
|
+
= f.show_if :category, 'Email' do
|
|
82
|
+
- operations = fa.object.operations('Email')
|
|
83
|
+
= fa.radios :operation, operations, buttons: true, label: false
|
|
84
|
+
|
|
85
|
+
= fa.show_if(:operation, 'Equal to') do
|
|
86
|
+
.d-flex= fa.email_field :email
|
|
87
|
+
|
|
88
|
+
= fa.show_if_any(:operation, operations - ['Equal to']) do
|
|
89
|
+
.d-flex= fa.text_field :email, label: 'Text to match'
|
|
90
|
+
|
|
91
|
+
= f.show_if :category, 'Number' do
|
|
92
|
+
- operations = fa.object.operations('Number')
|
|
93
|
+
= fa.radios :operation, operations, buttons: true, label: false
|
|
94
|
+
|
|
95
|
+
= fa.show_if_any(:operation, operations - ['Within range']) do
|
|
96
|
+
.d-flex= fa.number_field :number
|
|
97
|
+
|
|
98
|
+
= fa.show_if(:operation, 'Within range') do
|
|
99
|
+
.d-flex.align-items-center
|
|
100
|
+
= fa.number_field :number_begin
|
|
101
|
+
%span.mx-4 to
|
|
102
|
+
= fa.number_field :number_end
|
|
103
|
+
|
|
104
|
+
= f.show_if :category, 'Percentage' do
|
|
105
|
+
- operations = fa.object.operations('Percentage')
|
|
106
|
+
= fa.radios :operation, operations, buttons: true, label: false
|
|
107
|
+
|
|
108
|
+
= fa.show_if_any(:operation, operations - ['Within range']) do
|
|
109
|
+
.d-flex= fa.percent_field :percentage
|
|
110
|
+
|
|
111
|
+
= fa.show_if(:operation, 'Within range') do
|
|
112
|
+
.d-flex.align-items-center
|
|
113
|
+
= fa.percent_field :percentage_begin
|
|
114
|
+
%span.mx-4 to
|
|
115
|
+
= fa.percent_field :percentage_end
|
|
116
|
+
|
|
117
|
+
= f.show_if :category, 'Price' do
|
|
118
|
+
- operations = fa.object.operations('Price')
|
|
119
|
+
= fa.radios :operation, operations, buttons: true, label: false
|
|
120
|
+
|
|
121
|
+
= fa.show_if_any(:operation, operations - ['Within range']) do
|
|
122
|
+
.d-flex= fa.price_field :price
|
|
123
|
+
|
|
124
|
+
= fa.show_if(:operation, 'Within range') do
|
|
125
|
+
.d-flex.align-items-center
|
|
126
|
+
= fa.price_field :price_begin
|
|
127
|
+
%span.mx-4 to
|
|
128
|
+
= fa.price_field :price_end
|
|
129
|
+
|
|
130
|
+
= f.show_if :category, 'Upload File' do
|
|
131
|
+
%p Automatic scoring is not supported for this question type.
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
= f.text_field :follow_up_value, label: 'Answer required to display this question', required: true, hint: 'Enter the exact answer value that should trigger this question to appear'
|
|
22
22
|
|
|
23
23
|
= f.text_field :title, label: 'Question Title'
|
|
24
|
+
.row
|
|
25
|
+
.col= f.text_field :task, hint: 'An optional task name or number used in exports'
|
|
26
|
+
.col= f.number_field :points, hint: 'An optional number of points awarded for this question'
|
|
24
27
|
|
|
25
28
|
- if defined?(EffectiveArticleEditor)
|
|
26
29
|
= f.article_editor :body, label: 'Body (optional)'
|
|
@@ -28,94 +31,12 @@
|
|
|
28
31
|
= f.rich_text_area :body, label: 'Body (optional)'
|
|
29
32
|
|
|
30
33
|
= f.check_box :required, hint: 'A response to this question will be required'
|
|
31
|
-
= f.select :category, Effective::Question::CATEGORIES
|
|
32
|
-
|
|
33
|
-
= f.show_if :category, 'Choose one' do
|
|
34
|
-
.mt-3.card
|
|
35
|
-
.card-body
|
|
36
|
-
%h5 Choose one
|
|
37
|
-
%p Display radio buttons to select one option
|
|
38
|
-
|
|
39
|
-
= f.show_if :category, 'Select all that apply' do
|
|
40
|
-
.card
|
|
41
|
-
.card-body
|
|
42
|
-
%h5 Select all that apply
|
|
43
|
-
%p Display checkboxes to select all options that apply
|
|
44
|
-
|
|
45
|
-
= f.show_if :category, 'Select up to 1' do
|
|
46
|
-
.card
|
|
47
|
-
.card-body
|
|
48
|
-
%h5 Select up to 1 (one)
|
|
49
|
-
%p Display checkboxes to select up to 1 option
|
|
50
|
-
|
|
51
|
-
= f.show_if :category, 'Select up to 2' do
|
|
52
|
-
.card
|
|
53
|
-
.card-body
|
|
54
|
-
%h5 Select up to 2 (two)
|
|
55
|
-
%p Display checkboxes to select up to 2 options
|
|
56
|
-
|
|
57
|
-
= f.show_if :category, 'Select up to 3' do
|
|
58
|
-
.card
|
|
59
|
-
.card-body
|
|
60
|
-
%h5 Select up to 3 (three)
|
|
61
|
-
%p Display checkboxes to select up to 3 options
|
|
62
|
-
|
|
63
|
-
= f.show_if :category, 'Select up to 4' do
|
|
64
|
-
.card
|
|
65
|
-
.card-body
|
|
66
|
-
%h5 Select up to 4 (four)
|
|
67
|
-
%p Display checkboxes to select up to 4 options
|
|
68
|
-
|
|
69
|
-
= f.show_if :category, 'Select up to 5' do
|
|
70
|
-
.card
|
|
71
|
-
.card-body
|
|
72
|
-
%h5 Select up to 5 (five)
|
|
73
|
-
%p Display checkboxes to select up to 5 options
|
|
74
34
|
|
|
75
|
-
= f.
|
|
76
|
-
|
|
77
|
-
.card-body
|
|
78
|
-
%h5 Short Answer
|
|
79
|
-
%p Display a text field to enter a short text answer
|
|
80
|
-
|
|
81
|
-
= f.show_if :category, 'Long Answer' do
|
|
82
|
-
.card
|
|
83
|
-
.card-body
|
|
84
|
-
%h5 Long Answer
|
|
85
|
-
%p Display a textarea to enter a long text answer
|
|
86
|
-
|
|
87
|
-
= f.show_if :category, 'Date' do
|
|
88
|
-
.card
|
|
89
|
-
.card-body
|
|
90
|
-
%h5 Date
|
|
91
|
-
%p Display a date field to enter a date
|
|
92
|
-
|
|
93
|
-
= f.show_if :category, 'Email' do
|
|
94
|
-
.card
|
|
95
|
-
.card-body
|
|
96
|
-
%h5 Email
|
|
97
|
-
%p Display an email field to enter an email
|
|
98
|
-
|
|
99
|
-
= f.show_if :category, 'Number' do
|
|
100
|
-
.card
|
|
101
|
-
.card-body
|
|
102
|
-
%h5 Number
|
|
103
|
-
%p Display a number field to enter an integer number
|
|
104
|
-
|
|
105
|
-
= f.show_if :category, 'Upload File' do
|
|
106
|
-
.card
|
|
107
|
-
.card-body
|
|
108
|
-
%h5 Upload File
|
|
109
|
-
%p Display a file field to upload a file
|
|
110
|
-
|
|
111
|
-
= f.show_if_any :category, Effective::Question::WITH_OPTIONS_CATEGORIES do
|
|
112
|
-
.mt-3.card
|
|
113
|
-
.card-body
|
|
114
|
-
%h5 Options
|
|
115
|
-
%p Display the following options:
|
|
35
|
+
= f.select :category, Effective::Question::CATEGORIES
|
|
36
|
+
.mt-3= render('admin/questions/fields_category', f: f)
|
|
116
37
|
|
|
117
|
-
|
|
118
|
-
|
|
38
|
+
= f.check_box :scored, hint: 'Any responses to this question will be automatically scored'
|
|
39
|
+
.mt-3= render('admin/questions/fields_scored', f: f)
|
|
119
40
|
|
|
120
41
|
= effective_submit(f)
|
|
121
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
- (question.question_options + [nil]).each do |question_option|
|
|
2
2
|
- selected = responses.select { |br| br.response.blank? } if question_option.blank?
|
|
3
|
-
- selected ||= responses.select { |br| Array(br.response).
|
|
3
|
+
- selected ||= responses.select { |br| Array(br.response).any? { |ro| ro.question_option == question_option } }
|
|
4
4
|
|
|
5
5
|
- count = selected.length
|
|
6
6
|
- total = responses.length
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
- # The individiaual responses for one
|
|
1
|
+
- # The individiaual responses for one responsable resource
|
|
2
|
+
- include_scores = (local_assigns[:include_scores] == true)
|
|
3
|
+
|
|
2
4
|
.responsable-results
|
|
3
5
|
%table.table.table-hover
|
|
4
6
|
%thead
|
|
5
7
|
%tr
|
|
6
8
|
%th Question
|
|
7
9
|
%th Response
|
|
10
|
+
- if include_scores
|
|
11
|
+
%th Scored Result
|
|
8
12
|
|
|
9
13
|
%tbody
|
|
10
14
|
- questionable.questions.top_level.deep.each do |question|
|
|
@@ -14,6 +18,17 @@
|
|
|
14
18
|
%td= question
|
|
15
19
|
%td= render('effective/responses/response', response: response)
|
|
16
20
|
|
|
21
|
+
- if include_scores
|
|
22
|
+
%td
|
|
23
|
+
- if question.scored?
|
|
24
|
+
%span{title: "Correct answer was #{question.answer_to_s}"}
|
|
25
|
+
- if response.correct?
|
|
26
|
+
= badge('Correct', 'badge-success')
|
|
27
|
+
- else
|
|
28
|
+
= badge('Incorrect', 'badge-danger')
|
|
29
|
+
- else
|
|
30
|
+
= '-'
|
|
31
|
+
|
|
17
32
|
- question.follow_up_questions.deep.each do |follow_up_question|
|
|
18
33
|
- response = responsable.response(question: follow_up_question)
|
|
19
34
|
|
|
@@ -21,3 +36,14 @@
|
|
|
21
36
|
%tr
|
|
22
37
|
%td= follow_up_question
|
|
23
38
|
%td= render('effective/responses/response', response: response)
|
|
39
|
+
|
|
40
|
+
- if include_scores
|
|
41
|
+
%td
|
|
42
|
+
- if follow_up_question.scored?
|
|
43
|
+
%span{title: "Correct answer was #{follow_up_question.answer_to_s}"}
|
|
44
|
+
- if response.correct?
|
|
45
|
+
= badge('Correct', 'badge-success')
|
|
46
|
+
- else
|
|
47
|
+
= badge('Incorrect', 'badge-danger')
|
|
48
|
+
- else
|
|
49
|
+
= '-'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
= response.response
|
|
1
|
+
= response.response&.question_option || '-'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
= response.response.presence || '-'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
= price_to_currency(response.response) || '-'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
= response.response
|
|
1
|
+
= response.response || '-'
|
data/config/routes.rb
CHANGED
|
@@ -16,6 +16,11 @@ class CreateEffectiveQuestions < ActiveRecord::Migration[6.0]
|
|
|
16
16
|
t.integer :question_id
|
|
17
17
|
t.integer :question_option_id
|
|
18
18
|
|
|
19
|
+
t.boolean :scored, default: false
|
|
20
|
+
|
|
21
|
+
t.string :task
|
|
22
|
+
t.decimal :points
|
|
23
|
+
|
|
19
24
|
t.datetime :updated_at
|
|
20
25
|
t.datetime :created_at
|
|
21
26
|
end
|
|
@@ -26,6 +31,8 @@ class CreateEffectiveQuestions < ActiveRecord::Migration[6.0]
|
|
|
26
31
|
t.string :title
|
|
27
32
|
t.integer :position
|
|
28
33
|
|
|
34
|
+
t.boolean :answer, default: false
|
|
35
|
+
|
|
29
36
|
t.datetime :updated_at
|
|
30
37
|
t.datetime :created_at
|
|
31
38
|
end
|
|
@@ -44,6 +51,9 @@ class CreateEffectiveQuestions < ActiveRecord::Migration[6.0]
|
|
|
44
51
|
t.integer :number
|
|
45
52
|
t.text :long_answer
|
|
46
53
|
t.text :short_answer
|
|
54
|
+
t.decimal :decimal
|
|
55
|
+
t.integer :percentage
|
|
56
|
+
t.integer :price
|
|
47
57
|
|
|
48
58
|
t.datetime :updated_at
|
|
49
59
|
t.datetime :created_at
|
|
@@ -56,5 +66,39 @@ class CreateEffectiveQuestions < ActiveRecord::Migration[6.0]
|
|
|
56
66
|
t.datetime :updated_at
|
|
57
67
|
t.datetime :created_at
|
|
58
68
|
end
|
|
69
|
+
|
|
70
|
+
create_table :question_answers do |t|
|
|
71
|
+
t.integer :question_id
|
|
72
|
+
|
|
73
|
+
t.string :operation
|
|
74
|
+
|
|
75
|
+
t.date :date
|
|
76
|
+
t.date :date_begin
|
|
77
|
+
t.date :date_end
|
|
78
|
+
|
|
79
|
+
t.decimal :decimal
|
|
80
|
+
t.decimal :decimal_begin
|
|
81
|
+
t.decimal :decimal_end
|
|
82
|
+
|
|
83
|
+
t.string :email
|
|
84
|
+
|
|
85
|
+
t.integer :number
|
|
86
|
+
t.integer :number_begin
|
|
87
|
+
t.integer :number_end
|
|
88
|
+
|
|
89
|
+
t.integer :percentage
|
|
90
|
+
t.integer :percentage_begin
|
|
91
|
+
t.integer :percentage_end
|
|
92
|
+
|
|
93
|
+
t.integer :price
|
|
94
|
+
t.integer :price_begin
|
|
95
|
+
t.integer :price_end
|
|
96
|
+
|
|
97
|
+
t.text :long_answer
|
|
98
|
+
t.text :short_answer
|
|
99
|
+
|
|
100
|
+
t.timestamps
|
|
101
|
+
end
|
|
102
|
+
|
|
59
103
|
end
|
|
60
104
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: effective_questions
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Code and Effect
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-02-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -177,15 +177,20 @@ files:
|
|
|
177
177
|
- app/assets/config/effective_questions_manifest.js
|
|
178
178
|
- app/assets/stylesheets/effective_questions.scss
|
|
179
179
|
- app/controllers/admin/questions_controller.rb
|
|
180
|
+
- app/controllers/admin/responses_controller.rb
|
|
180
181
|
- app/datatables/admin/effective_questionable_results_datatable.rb
|
|
181
182
|
- app/datatables/admin/effective_questions_datatable.rb
|
|
183
|
+
- app/datatables/admin/effective_responses_datatable.rb
|
|
182
184
|
- app/helpers/effective_questions_helper.rb
|
|
183
185
|
- app/models/concerns/acts_as_questionable.rb
|
|
184
186
|
- app/models/concerns/acts_as_responsable.rb
|
|
185
187
|
- app/models/effective/question.rb
|
|
188
|
+
- app/models/effective/question_answer.rb
|
|
186
189
|
- app/models/effective/question_option.rb
|
|
187
190
|
- app/models/effective/response.rb
|
|
188
191
|
- app/models/effective/response_option.rb
|
|
192
|
+
- app/views/admin/questions/_fields_category.html.haml
|
|
193
|
+
- app/views/admin/questions/_fields_scored.html.haml
|
|
189
194
|
- app/views/admin/questions/_form.html.haml
|
|
190
195
|
- app/views/effective/questionable/_result.html.haml
|
|
191
196
|
- app/views/effective/questionable/_results.html.haml
|
|
@@ -210,9 +215,12 @@ files:
|
|
|
210
215
|
- app/views/effective/responses/_response.html.haml
|
|
211
216
|
- app/views/effective/responses/fields/_choose_one.html.haml
|
|
212
217
|
- app/views/effective/responses/fields/_date.html.haml
|
|
218
|
+
- app/views/effective/responses/fields/_decimal.html.haml
|
|
213
219
|
- app/views/effective/responses/fields/_email.html.haml
|
|
214
220
|
- app/views/effective/responses/fields/_long_answer.html.haml
|
|
215
221
|
- app/views/effective/responses/fields/_number.html.haml
|
|
222
|
+
- app/views/effective/responses/fields/_percentage.html.haml
|
|
223
|
+
- app/views/effective/responses/fields/_price.html.haml
|
|
216
224
|
- app/views/effective/responses/fields/_select_all_that_apply.html.haml
|
|
217
225
|
- app/views/effective/responses/fields/_select_up_to_1.html.haml
|
|
218
226
|
- app/views/effective/responses/fields/_select_up_to_2.html.haml
|
|
@@ -223,9 +231,12 @@ files:
|
|
|
223
231
|
- app/views/effective/responses/fields/_upload_file.html.haml
|
|
224
232
|
- app/views/effective/responses/responses/_choose_one.html.haml
|
|
225
233
|
- app/views/effective/responses/responses/_date.html.haml
|
|
234
|
+
- app/views/effective/responses/responses/_decimal.html.haml
|
|
226
235
|
- app/views/effective/responses/responses/_email.html.haml
|
|
227
236
|
- app/views/effective/responses/responses/_long_answer.html.haml
|
|
228
237
|
- app/views/effective/responses/responses/_number.html.haml
|
|
238
|
+
- app/views/effective/responses/responses/_percentage.html.haml
|
|
239
|
+
- app/views/effective/responses/responses/_price.html.haml
|
|
229
240
|
- app/views/effective/responses/responses/_select_all_that_apply.html.haml
|
|
230
241
|
- app/views/effective/responses/responses/_select_up_to_1.html.haml
|
|
231
242
|
- app/views/effective/responses/responses/_select_up_to_2.html.haml
|