unique_rapidfire 1.0.0

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