unique_rapidfire 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 %>
|