unique_rapidfire 1.0.0
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.
- data/MIT-LICENSE +20 -0
- data/README.md +199 -0
- data/Rakefile +36 -0
- data/app/assets/javascripts/rapidfire/application.js +12 -0
- data/app/assets/stylesheets/rapidfire/application.css +22 -0
- data/app/controllers/rapidfire/answer_groups_controller.rb +34 -0
- data/app/controllers/rapidfire/application_controller.rb +11 -0
- data/app/controllers/rapidfire/question_groups_controller.rb +48 -0
- data/app/controllers/rapidfire/questions_controller.rb +58 -0
- data/app/helpers/rapidfire/application_helper.rb +12 -0
- data/app/models/rapidfire/answer.rb +18 -0
- data/app/models/rapidfire/answer_group.rb +12 -0
- data/app/models/rapidfire/question.rb +44 -0
- data/app/models/rapidfire/question_group.rb +10 -0
- data/app/models/rapidfire/questions/checkbox.rb +21 -0
- data/app/models/rapidfire/questions/date.rb +16 -0
- data/app/models/rapidfire/questions/long.rb +6 -0
- data/app/models/rapidfire/questions/numeric.rb +21 -0
- data/app/models/rapidfire/questions/radio.rb +6 -0
- data/app/models/rapidfire/questions/select.rb +19 -0
- data/app/models/rapidfire/questions/short.rb +6 -0
- data/app/serializers/rapidfire/question_result_serializer.rb +15 -0
- data/app/services/rapidfire/answer_group_builder.rb +48 -0
- data/app/services/rapidfire/base_service.rb +21 -0
- data/app/services/rapidfire/question_form.rb +85 -0
- data/app/services/rapidfire/question_group_results.rb +29 -0
- data/app/services/rapidfire/question_result.rb +11 -0
- data/app/views/rapidfire/answer_groups/new.html.erb +11 -0
- data/app/views/rapidfire/answers/_checkbox.html.erb +11 -0
- data/app/views/rapidfire/answers/_date.html.erb +4 -0
- data/app/views/rapidfire/answers/_errors.html.erb +7 -0
- data/app/views/rapidfire/answers/_long.html.erb +4 -0
- data/app/views/rapidfire/answers/_numeric.html.erb +4 -0
- data/app/views/rapidfire/answers/_radio.html.erb +9 -0
- data/app/views/rapidfire/answers/_select.html.erb +4 -0
- data/app/views/rapidfire/answers/_short.html.erb +4 -0
- data/app/views/rapidfire/question_groups/_form.html.erb +15 -0
- data/app/views/rapidfire/question_groups/_question_group.html.erb +18 -0
- data/app/views/rapidfire/question_groups/index.html.erb +23 -0
- data/app/views/rapidfire/question_groups/new.html.erb +1 -0
- data/app/views/rapidfire/question_groups/results.html.erb +32 -0
- data/app/views/rapidfire/questions/_form.html.erb +40 -0
- data/app/views/rapidfire/questions/_question.html.erb +9 -0
- data/app/views/rapidfire/questions/edit.html.erb +1 -0
- data/app/views/rapidfire/questions/index.html.erb +21 -0
- data/app/views/rapidfire/questions/new.html.erb +1 -0
- data/config/database.yml +8 -0
- data/config/routes.rb +10 -0
- data/db/migrate/20130502170733_create_rapidfire_question_groups.rb +8 -0
- data/db/migrate/20130502195310_create_rapidfire_questions.rb +15 -0
- data/db/migrate/20130502195415_create_rapidfire_answer_groups.rb +12 -0
- data/db/migrate/20130502195504_create_rapidfire_answers.rb +13 -0
- data/lib/generators/rapidfire/views_generator.rb +23 -0
- data/lib/rapidfire.rb +6 -0
- data/lib/rapidfire/engine.rb +7 -0
- data/lib/rapidfire/version.rb +3 -0
- data/lib/tasks/rapidfire_tasks.rake +4 -0
- metadata +223 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
module Rapidfire
|
2
|
+
module ApplicationHelper
|
3
|
+
def render_answer_form_helper(answer, form)
|
4
|
+
partial = answer.question.type.to_s.split("::").last.downcase
|
5
|
+
render partial: "rapidfire/answers/#{partial}", locals: { f: form, answer: answer }
|
6
|
+
end
|
7
|
+
|
8
|
+
def checkbox_checked?(answer, option)
|
9
|
+
answer.answer_text.to_s.split(",").include?(option)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rapidfire
|
2
|
+
class Answer < ActiveRecord::Base
|
3
|
+
belongs_to :question
|
4
|
+
belongs_to :answer_group, inverse_of: :answers
|
5
|
+
|
6
|
+
validates :question, :answer_group, presence: true
|
7
|
+
validate :verify_answer_text, :if => "question.present?"
|
8
|
+
|
9
|
+
if Rails::VERSION::MAJOR == 3
|
10
|
+
attr_accessible :question_id, :answer_group, :answer_text
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def verify_answer_text
|
15
|
+
question.validate_answer(self)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Rapidfire
|
2
|
+
class AnswerGroup < ActiveRecord::Base
|
3
|
+
belongs_to :question_group
|
4
|
+
belongs_to :user, polymorphic: true
|
5
|
+
has_many :answers, inverse_of: :answer_group, autosave: true
|
6
|
+
validates :unique_id, :uniqueness => {:scope => :question_group}
|
7
|
+
|
8
|
+
if Rails::VERSION::MAJOR == 3
|
9
|
+
attr_accessible :question_group, :user, :unique_id
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Rapidfire
|
2
|
+
class Question < ActiveRecord::Base
|
3
|
+
belongs_to :question_group, :inverse_of => :questions
|
4
|
+
has_many :answers
|
5
|
+
|
6
|
+
default_scope { order(:position) }
|
7
|
+
|
8
|
+
validates :question_group, :question_text, :presence => true
|
9
|
+
serialize :validation_rules
|
10
|
+
|
11
|
+
if Rails::VERSION::MAJOR == 3
|
12
|
+
attr_accessible :question_group, :question_text, :validation_rules, :answer_options
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.inherited(child)
|
16
|
+
child.instance_eval do
|
17
|
+
def model_name
|
18
|
+
Question.model_name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def rules
|
26
|
+
validation_rules || {}
|
27
|
+
end
|
28
|
+
|
29
|
+
# answer will delegate its validation to question, and question
|
30
|
+
# will inturn add validations on answer on the fly!
|
31
|
+
def validate_answer(answer)
|
32
|
+
if rules[:presence] == "1"
|
33
|
+
answer.validates_presence_of :answer_text
|
34
|
+
end
|
35
|
+
|
36
|
+
if rules[:minimum].present? || rules[:maximum].present?
|
37
|
+
min_max = { minimum: rules[:minimum].to_i }
|
38
|
+
min_max[:maximum] = rules[:maximum].to_i if rules[:maximum].present?
|
39
|
+
|
40
|
+
answer.validates_length_of :answer_text, min_max
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rapidfire
|
2
|
+
module Questions
|
3
|
+
class Checkbox < Rapidfire::Question
|
4
|
+
validates :answer_options, :presence => true
|
5
|
+
|
6
|
+
def options
|
7
|
+
answer_options.split(/\r?\n/)
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate_answer(answer)
|
11
|
+
super(answer)
|
12
|
+
|
13
|
+
if rules[:presence] == "1" || answer.answer_text.present?
|
14
|
+
answer.answer_text.split(",").each do |value|
|
15
|
+
answer.errors.add(:answer_text, :invalid) unless options.include?(value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Rapidfire
|
2
|
+
module Questions
|
3
|
+
class Date < Rapidfire::Question
|
4
|
+
def validate_answer(answer)
|
5
|
+
super(answer)
|
6
|
+
|
7
|
+
if rules[:presence] == "1" || answer.answer_text.present?
|
8
|
+
begin ::Date.parse(answer.answer_text.to_s)
|
9
|
+
rescue ArgumentError => e
|
10
|
+
answer.errors.add(:answer_text, :invalid)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rapidfire
|
2
|
+
module Questions
|
3
|
+
class Numeric < Rapidfire::Question
|
4
|
+
def validate_answer(answer)
|
5
|
+
super(answer)
|
6
|
+
|
7
|
+
if rules[:presence] == "1" || answer.answer_text.present?
|
8
|
+
gt_or_lt = {}
|
9
|
+
if rules[:greater_than_or_equal_to].present?
|
10
|
+
gt_or_lt[:greater_than_or_equal_to] = rules[:greater_than_or_equal_to].to_i
|
11
|
+
end
|
12
|
+
if rules[:less_than_or_equal_to].present?
|
13
|
+
gt_or_lt[:less_than_or_equal_to] = rules[:less_than_or_equal_to].to_i
|
14
|
+
end
|
15
|
+
|
16
|
+
answer.validates_numericality_of :answer_text, gt_or_lt
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Rapidfire
|
2
|
+
module Questions
|
3
|
+
class Select < Rapidfire::Question
|
4
|
+
validates :answer_options, :presence => true
|
5
|
+
|
6
|
+
def options
|
7
|
+
answer_options.split(/\r?\n/)
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate_answer(answer)
|
11
|
+
super(answer)
|
12
|
+
|
13
|
+
if rules[:presence] == "1" || answer.answer_text.present?
|
14
|
+
answer.validates_inclusion_of :answer_text, :in => options
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Rapidfire
|
2
|
+
class QuestionResultSerializer < ActiveModel::Serializer
|
3
|
+
self.root = false
|
4
|
+
|
5
|
+
attributes :question_type, :question_text, :results
|
6
|
+
|
7
|
+
def question_type
|
8
|
+
object.question.type
|
9
|
+
end
|
10
|
+
|
11
|
+
def question_text
|
12
|
+
object.question.question_text
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Rapidfire
|
2
|
+
class AnswerGroupBuilder < Rapidfire::BaseService
|
3
|
+
attr_accessor :user, :question_group, :questions, :answers, :params, :unique_id
|
4
|
+
|
5
|
+
def initialize(params = {})
|
6
|
+
super(params)
|
7
|
+
build_answer_group
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_model
|
11
|
+
@answer_group
|
12
|
+
end
|
13
|
+
|
14
|
+
def save!(options = {})
|
15
|
+
params.each do |question_id, answer_attributes|
|
16
|
+
if answer = @answer_group.answers.find { |a| a.question_id.to_s == question_id.to_s }
|
17
|
+
text = answer_attributes[:answer_text]
|
18
|
+
answer.answer_text =
|
19
|
+
text.is_a?(Array) ? strip_checkbox_answers(text).join(',') : text
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
@answer_group.save!(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def save(options = {})
|
27
|
+
save!(options)
|
28
|
+
rescue Exception => e
|
29
|
+
# repopulate answers here in case of failure as they are not getting updated
|
30
|
+
@answers = @question_group.questions.collect do |question|
|
31
|
+
@answer_group.answers.find { |a| a.question_id == question.id }
|
32
|
+
end
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def build_answer_group
|
38
|
+
@answer_group = AnswerGroup.new(user: user, question_group: question_group, unique_id: unique_id)
|
39
|
+
@answers = @question_group.questions.collect do |question|
|
40
|
+
@answer_group.answers.build(question_id: question.id)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def strip_checkbox_answers(text)
|
45
|
+
text.reject(&:blank?).reject { |t| t == "0" }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rapidfire
|
2
|
+
class BaseService
|
3
|
+
if Rails::VERSION::MAJOR == 4
|
4
|
+
include ActiveModel::Model
|
5
|
+
else
|
6
|
+
extend ActiveModel::Naming
|
7
|
+
include ActiveModel::Conversion
|
8
|
+
include ActiveModel::Validations
|
9
|
+
|
10
|
+
def persisted; false end
|
11
|
+
|
12
|
+
def initialize(params={})
|
13
|
+
params.each do |attr, value|
|
14
|
+
self.public_send("#{attr}=", value)
|
15
|
+
end if params
|
16
|
+
|
17
|
+
super()
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Rapidfire
|
2
|
+
class QuestionForm < Rapidfire::BaseService
|
3
|
+
AVAILABLE_QUESTIONS =
|
4
|
+
[
|
5
|
+
Rapidfire::Questions::Checkbox,
|
6
|
+
Rapidfire::Questions::Date,
|
7
|
+
Rapidfire::Questions::Long,
|
8
|
+
Rapidfire::Questions::Numeric,
|
9
|
+
Rapidfire::Questions::Radio,
|
10
|
+
Rapidfire::Questions::Select,
|
11
|
+
Rapidfire::Questions::Short,
|
12
|
+
]
|
13
|
+
|
14
|
+
QUESTION_TYPES = AVAILABLE_QUESTIONS.inject({}) do |result, question|
|
15
|
+
question_name = question.to_s.split("::").last
|
16
|
+
result[question_name] = question.to_s
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :question_group, :question,
|
21
|
+
:type, :question_text, :answer_options, :answer_presence,
|
22
|
+
:answer_minimum_length, :answer_maximum_length,
|
23
|
+
:answer_greater_than_or_equal_to, :answer_less_than_or_equal_to
|
24
|
+
|
25
|
+
delegate :valid?, :errors, :id, :to => :question
|
26
|
+
|
27
|
+
def to_model
|
28
|
+
question
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(params = {})
|
32
|
+
from_question_to_attributes(params[:question]) if params[:question]
|
33
|
+
super(params)
|
34
|
+
@question ||= question_group.questions.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def save
|
38
|
+
@question.new_record? ? create_question : update_question
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def create_question
|
43
|
+
klass = nil
|
44
|
+
if QUESTION_TYPES.values.include?(type)
|
45
|
+
klass = type.constantize
|
46
|
+
else
|
47
|
+
errors.add(:type, :invalid)
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
|
51
|
+
@question = klass.create(to_question_params)
|
52
|
+
end
|
53
|
+
|
54
|
+
def update_question
|
55
|
+
@question.update_attributes(to_question_params)
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_question_params
|
59
|
+
{
|
60
|
+
:question_group => question_group,
|
61
|
+
:question_text => question_text,
|
62
|
+
:answer_options => answer_options,
|
63
|
+
:validation_rules => {
|
64
|
+
:presence => answer_presence,
|
65
|
+
:minimum => answer_minimum_length,
|
66
|
+
:maximum => answer_maximum_length,
|
67
|
+
:greater_than_or_equal_to => answer_greater_than_or_equal_to,
|
68
|
+
:less_than_or_equal_to => answer_less_than_or_equal_to
|
69
|
+
}
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def from_question_to_attributes(question)
|
74
|
+
self.type = question.type
|
75
|
+
self.question_group = question.question_group
|
76
|
+
self.question_text = question.question_text
|
77
|
+
self.answer_options = question.answer_options
|
78
|
+
self.answer_presence = question.rules[:presence]
|
79
|
+
self.answer_minimum_length = question.rules[:minimum]
|
80
|
+
self.answer_maximum_length = question.rules[:maximum]
|
81
|
+
self.answer_greater_than_or_equal_to = question.rules[:greater_than_or_equal_to]
|
82
|
+
self.answer_less_than_or_equal_to = question.rules[:less_than_or_equal_to]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Rapidfire
|
2
|
+
class QuestionGroupResults < Rapidfire::BaseService
|
3
|
+
attr_accessor :question_group
|
4
|
+
|
5
|
+
# extracts question along with results
|
6
|
+
# each entry will have the following:
|
7
|
+
# 1. question type and question id
|
8
|
+
# 2. question text
|
9
|
+
# 3. if aggregatable, return each option with value
|
10
|
+
# 4. else return an array of all the answers given
|
11
|
+
def extract
|
12
|
+
@question_group.questions.collect do |question|
|
13
|
+
results =
|
14
|
+
case question
|
15
|
+
when Rapidfire::Questions::Select, Rapidfire::Questions::Radio,
|
16
|
+
Rapidfire::Questions::Checkbox
|
17
|
+
answers = question.answers.map(&:answer_text).map { |text| text.split(',') }.flatten
|
18
|
+
answers.inject(Hash.new(0)) { |total, e| total[e] += 1; total }
|
19
|
+
|
20
|
+
when Rapidfire::Questions::Short, Rapidfire::Questions::Date,
|
21
|
+
Rapidfire::Questions::Long, Rapidfire::Questions::Numeric
|
22
|
+
question.answers.pluck(:answer_text)
|
23
|
+
end
|
24
|
+
|
25
|
+
QuestionResult.new(question: question, results: results)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<h2>Please answer these Questions</h2>
|
2
|
+
<hr/>
|
3
|
+
|
4
|
+
<%= form_for([@question_group, @answer_group_builder]) do |f| %>
|
5
|
+
<%- @answer_group_builder.answers.each do |answer| %>
|
6
|
+
<%= f.fields_for("#{answer.question.id}", answer) do |answer_form| %>
|
7
|
+
<%= render_answer_form_helper(answer, answer_form) %>
|
8
|
+
<% end %>
|
9
|
+
<% end %>
|
10
|
+
<%= f.submit "Save" %>
|
11
|
+
<% end %>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%= render partial: "rapidfire/answers/errors", locals: {answer: answer} %>
|
2
|
+
|
3
|
+
<%= f.label :answer_text, answer.question.question_text %>
|
4
|
+
<%= f.fields_for :answer_text do |af| %>
|
5
|
+
<%- answer.question.options.each do |option| %>
|
6
|
+
<%= af.label option do %>
|
7
|
+
<%= af.check_box nil, { id: nil, checked: checkbox_checked?(answer, option) }, option %>
|
8
|
+
<%= option %>
|
9
|
+
<% end %>
|
10
|
+
<% end %>
|
11
|
+
<% end %>
|