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.
Files changed (58) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +199 -0
  3. data/Rakefile +36 -0
  4. data/app/assets/javascripts/rapidfire/application.js +12 -0
  5. data/app/assets/stylesheets/rapidfire/application.css +22 -0
  6. data/app/controllers/rapidfire/answer_groups_controller.rb +34 -0
  7. data/app/controllers/rapidfire/application_controller.rb +11 -0
  8. data/app/controllers/rapidfire/question_groups_controller.rb +48 -0
  9. data/app/controllers/rapidfire/questions_controller.rb +58 -0
  10. data/app/helpers/rapidfire/application_helper.rb +12 -0
  11. data/app/models/rapidfire/answer.rb +18 -0
  12. data/app/models/rapidfire/answer_group.rb +12 -0
  13. data/app/models/rapidfire/question.rb +44 -0
  14. data/app/models/rapidfire/question_group.rb +10 -0
  15. data/app/models/rapidfire/questions/checkbox.rb +21 -0
  16. data/app/models/rapidfire/questions/date.rb +16 -0
  17. data/app/models/rapidfire/questions/long.rb +6 -0
  18. data/app/models/rapidfire/questions/numeric.rb +21 -0
  19. data/app/models/rapidfire/questions/radio.rb +6 -0
  20. data/app/models/rapidfire/questions/select.rb +19 -0
  21. data/app/models/rapidfire/questions/short.rb +6 -0
  22. data/app/serializers/rapidfire/question_result_serializer.rb +15 -0
  23. data/app/services/rapidfire/answer_group_builder.rb +48 -0
  24. data/app/services/rapidfire/base_service.rb +21 -0
  25. data/app/services/rapidfire/question_form.rb +85 -0
  26. data/app/services/rapidfire/question_group_results.rb +29 -0
  27. data/app/services/rapidfire/question_result.rb +11 -0
  28. data/app/views/rapidfire/answer_groups/new.html.erb +11 -0
  29. data/app/views/rapidfire/answers/_checkbox.html.erb +11 -0
  30. data/app/views/rapidfire/answers/_date.html.erb +4 -0
  31. data/app/views/rapidfire/answers/_errors.html.erb +7 -0
  32. data/app/views/rapidfire/answers/_long.html.erb +4 -0
  33. data/app/views/rapidfire/answers/_numeric.html.erb +4 -0
  34. data/app/views/rapidfire/answers/_radio.html.erb +9 -0
  35. data/app/views/rapidfire/answers/_select.html.erb +4 -0
  36. data/app/views/rapidfire/answers/_short.html.erb +4 -0
  37. data/app/views/rapidfire/question_groups/_form.html.erb +15 -0
  38. data/app/views/rapidfire/question_groups/_question_group.html.erb +18 -0
  39. data/app/views/rapidfire/question_groups/index.html.erb +23 -0
  40. data/app/views/rapidfire/question_groups/new.html.erb +1 -0
  41. data/app/views/rapidfire/question_groups/results.html.erb +32 -0
  42. data/app/views/rapidfire/questions/_form.html.erb +40 -0
  43. data/app/views/rapidfire/questions/_question.html.erb +9 -0
  44. data/app/views/rapidfire/questions/edit.html.erb +1 -0
  45. data/app/views/rapidfire/questions/index.html.erb +21 -0
  46. data/app/views/rapidfire/questions/new.html.erb +1 -0
  47. data/config/database.yml +8 -0
  48. data/config/routes.rb +10 -0
  49. data/db/migrate/20130502170733_create_rapidfire_question_groups.rb +8 -0
  50. data/db/migrate/20130502195310_create_rapidfire_questions.rb +15 -0
  51. data/db/migrate/20130502195415_create_rapidfire_answer_groups.rb +12 -0
  52. data/db/migrate/20130502195504_create_rapidfire_answers.rb +13 -0
  53. data/lib/generators/rapidfire/views_generator.rb +23 -0
  54. data/lib/rapidfire.rb +6 -0
  55. data/lib/rapidfire/engine.rb +7 -0
  56. data/lib/rapidfire/version.rb +3 -0
  57. data/lib/tasks/rapidfire_tasks.rake +4 -0
  58. 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,10 @@
1
+ module Rapidfire
2
+ class QuestionGroup < ActiveRecord::Base
3
+ has_many :questions
4
+ validates :name, :presence => true
5
+
6
+ if Rails::VERSION::MAJOR == 3
7
+ attr_accessible :name
8
+ end
9
+ end
10
+ 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,6 @@
1
+ module Rapidfire
2
+ module Questions
3
+ class Long < Rapidfire::Question
4
+ end
5
+ end
6
+ 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,6 @@
1
+ module Rapidfire
2
+ module Questions
3
+ class Radio < Select
4
+ end
5
+ end
6
+ 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,6 @@
1
+ module Rapidfire
2
+ module Questions
3
+ class Short < Rapidfire::Question
4
+ end
5
+ end
6
+ 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
+ module Rapidfire
2
+ class QuestionResult < Rapidfire::BaseService
3
+ include ActiveModel::Serialization
4
+
5
+ attr_accessor :question, :results
6
+
7
+ def active_model_serializer
8
+ Rapidfire::QuestionResultSerializer
9
+ end
10
+ end
11
+ 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 %>