jakewendt-surveyor 0.11.3
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 +285 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/app/controllers/surveyor_controller.rb +142 -0
- data/app/helpers/survey_form_builder.rb +37 -0
- data/app/helpers/surveyor_helper.rb +67 -0
- data/app/models/answer.rb +23 -0
- data/app/models/dependency.rb +50 -0
- data/app/models/dependency_condition.rb +47 -0
- data/app/models/question.rb +54 -0
- data/app/models/question_group.rb +33 -0
- data/app/models/response.rb +94 -0
- data/app/models/response_set.rb +316 -0
- data/app/models/survey.rb +172 -0
- data/app/models/survey_section.rb +18 -0
- data/app/models/validation.rb +38 -0
- data/app/models/validation_condition.rb +47 -0
- data/app/views/layouts/surveyor_default.html.erb +13 -0
- data/app/views/partials/_answer.html.haml +58 -0
- data/app/views/partials/_question.html.haml +80 -0
- data/app/views/partials/_question_group.html.haml +39 -0
- data/app/views/surveyor/edit.html.haml +38 -0
- data/app/views/surveyor/new.html.haml +16 -0
- data/app/views/surveyor/show.html.haml +12 -0
- data/config/routes.rb +11 -0
- data/features/step_definitions/surveyor_steps.rb +7 -0
- data/features/step_definitions/web_steps.rb +273 -0
- data/features/support/env.rb +57 -0
- data/features/support/paths.rb +25 -0
- data/features/surveyor.feature +11 -0
- data/generators/extend_surveyor/extend_surveyor_generator.rb +22 -0
- data/generators/extend_surveyor/templates/EXTENDING_SURVEYOR +17 -0
- data/generators/extend_surveyor/templates/extensions/survey_extensions.rb +24 -0
- data/generators/extend_surveyor/templates/extensions/surveyor_controller_extensions.rb +28 -0
- data/generators/extend_surveyor/templates/extensions/surveyor_custom.html.erb +13 -0
- data/generators/extend_surveyor/templates/extensions/surveyor_helper_extensions.rb +17 -0
- data/generators/surveyor/surveyor_generator.rb +67 -0
- data/generators/surveyor/templates/README +10 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_arrows_leftright.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_arrows_updown.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_close.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_doc.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_folder_closed.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_folder_open.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_minus.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_plus.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_resize_se.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_35x9_colorpicker_indicator.gif.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_7x7_arrow_down.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_7x7_arrow_left.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_7x7_arrow_right.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_7x7_arrow_up.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_arrows_leftright.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_arrows_updown.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_close.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_doc.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_folder_closed.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_folder_open.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_minus.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_plus.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_7x7_arrow_down.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_7x7_arrow_left.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_7x7_arrow_right.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_7x7_arrow_up.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_arrows_leftright.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_arrows_updown.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_close.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_doc.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_folder_closed.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_folder_open.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_minus.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_plus.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_7x7_arrow_down.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_7x7_arrow_left.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_7x7_arrow_right.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_7x7_arrow_up.gif +0 -0
- data/generators/surveyor/templates/assets/images/dadada_40x100_textures_02_glass_75.png +0 -0
- data/generators/surveyor/templates/assets/images/e6e6e6_40x100_textures_02_glass_75.png +0 -0
- data/generators/surveyor/templates/assets/images/ffffff_40x100_textures_01_flat_0.png +0 -0
- data/generators/surveyor/templates/assets/images/ffffff_40x100_textures_02_glass_65.png +0 -0
- data/generators/surveyor/templates/assets/javascripts/accessibleUISlider.jQuery.js +201 -0
- data/generators/surveyor/templates/assets/javascripts/jquery-1.2.6.js +3549 -0
- data/generators/surveyor/templates/assets/javascripts/jquery-ui-personalized-1.5.3.js +7616 -0
- data/generators/surveyor/templates/assets/javascripts/jquery.form.js +637 -0
- data/generators/surveyor/templates/assets/javascripts/surveyor.js +47 -0
- data/generators/surveyor/templates/assets/stylesheets/jquery-ui-slider-additions.css +71 -0
- data/generators/surveyor/templates/assets/stylesheets/reset.css +50 -0
- data/generators/surveyor/templates/assets/stylesheets/sass/surveyor.sass +243 -0
- data/generators/surveyor/templates/assets/stylesheets/surveyor.css +235 -0
- data/generators/surveyor/templates/assets/stylesheets/ui.theme.css +851 -0
- data/generators/surveyor/templates/initializers/haml.rb +8 -0
- data/generators/surveyor/templates/initializers/surveyor.rb +10 -0
- data/generators/surveyor/templates/migrate/add_correct_answer_id_to_questions.rb +9 -0
- data/generators/surveyor/templates/migrate/add_display_order_to_surveys.rb +9 -0
- data/generators/surveyor/templates/migrate/add_index_to_response_sets.rb +9 -0
- data/generators/surveyor/templates/migrate/add_index_to_surveys.rb +9 -0
- data/generators/surveyor/templates/migrate/add_manual_numbering.rb +11 -0
- data/generators/surveyor/templates/migrate/add_unique_indicies.rb +17 -0
- data/generators/surveyor/templates/migrate/create_answers.rb +37 -0
- data/generators/surveyor/templates/migrate/create_dependencies.rb +22 -0
- data/generators/surveyor/templates/migrate/create_dependency_conditions.rb +29 -0
- data/generators/surveyor/templates/migrate/create_question_groups.rb +27 -0
- data/generators/surveyor/templates/migrate/create_questions.rb +36 -0
- data/generators/surveyor/templates/migrate/create_response_sets.rb +22 -0
- data/generators/surveyor/templates/migrate/create_responses.rb +33 -0
- data/generators/surveyor/templates/migrate/create_survey_sections.rb +29 -0
- data/generators/surveyor/templates/migrate/create_surveys.rb +31 -0
- data/generators/surveyor/templates/migrate/create_validation_conditions.rb +32 -0
- data/generators/surveyor/templates/migrate/create_validations.rb +20 -0
- data/generators/surveyor/templates/surveys/kitchen_sink_survey.rb +218 -0
- data/generators/surveyor/templates/tasks/surveyor.rb +4 -0
- data/generators/test_surveyor/templates/TESTING_SURVEYOR +0 -0
- data/generators/test_surveyor/templates/environments/cucumber.rb +21 -0
- data/generators/test_surveyor/test_surveyor_generator.rb +15 -0
- data/jakewendt-surveyor.gemspec +212 -0
- data/lib/fixtures_extensions.rb +6 -0
- data/lib/jakewendt-surveyor.rb +1 -0
- data/lib/surveyor.rb +44 -0
- data/lib/surveyor/acts_as_response.rb +33 -0
- data/lib/surveyor/config.rb +45 -0
- data/lib/tasks/surveyor_tasks.rake +33 -0
- data/lib/xml_formatter.rb +12 -0
- data/rails/init.rb +1 -0
- data/script/surveyor/answer.rb +54 -0
- data/script/surveyor/base.rb +77 -0
- data/script/surveyor/dependency.rb +13 -0
- data/script/surveyor/dependency_condition.rb +40 -0
- data/script/surveyor/parser.rb +207 -0
- data/script/surveyor/question.rb +37 -0
- data/script/surveyor/question_group.rb +26 -0
- data/script/surveyor/specs/answer_spec.rb +29 -0
- data/script/surveyor/specs/question_spec.rb +63 -0
- data/script/surveyor/specs/spec_helper.rb +7 -0
- data/script/surveyor/specs/survey_section_spec.rb +23 -0
- data/script/surveyor/specs/validation_condition_spec.rb +20 -0
- data/script/surveyor/specs/validation_spec.rb +20 -0
- data/script/surveyor/survey.rb +35 -0
- data/script/surveyor/survey_section.rb +21 -0
- data/script/surveyor/validation.rb +21 -0
- data/script/surveyor/validation_condition.rb +21 -0
- data/script/surveyor/whr_dsl.tmproj +244 -0
- data/spec/controllers/surveyor_controller_spec.rb +193 -0
- data/spec/factories.rb +145 -0
- data/spec/lib/surveyor_spec.rb +44 -0
- data/spec/models/answer_spec.rb +29 -0
- data/spec/models/dependency_condition_spec.rb +321 -0
- data/spec/models/dependency_spec.rb +81 -0
- data/spec/models/question_group_spec.rb +35 -0
- data/spec/models/question_spec.rb +75 -0
- data/spec/models/response_set_spec.rb +245 -0
- data/spec/models/response_spec.rb +76 -0
- data/spec/models/survey_section_spec.rb +32 -0
- data/spec/models/survey_spec.rb +71 -0
- data/spec/models/validation_condition_spec.rb +105 -0
- data/spec/models/validation_spec.rb +59 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +12 -0
- metadata +254 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
class SurveyFormBuilder < ActionView::Helpers::FormBuilder
|
2
|
+
def survey_check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
3
|
+
fields = @template.survey_check_box(@object_name, method, options.merge(:object => @object), checked_value, unchecked_value)
|
4
|
+
fields[1]
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
module ActionView
|
9
|
+
module Helpers
|
10
|
+
module FormHelper
|
11
|
+
def survey_check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
|
12
|
+
if (Rails::VERSION::STRING.to_f > 2.1)
|
13
|
+
InstanceTag.new(object_name, method, self, options.delete(:object)).to_survey_check_box_tag(options, checked_value, unchecked_value)
|
14
|
+
else
|
15
|
+
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_survey_check_box_tag(options, checked_value, unchecked_value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class InstanceTag
|
21
|
+
def to_survey_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
|
22
|
+
options = options.stringify_keys
|
23
|
+
options["type"] = "checkbox"
|
24
|
+
options["value"] = checked_value
|
25
|
+
if options.has_key?("checked")
|
26
|
+
cv = options.delete "checked"
|
27
|
+
checked = cv == true || cv == "checked"
|
28
|
+
else
|
29
|
+
checked = self.class.check_box_checked?(value(object), checked_value)
|
30
|
+
end
|
31
|
+
options["checked"] = "checked" if checked
|
32
|
+
add_default_name_and_id(options)
|
33
|
+
[tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value), tag("input", options)]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module SurveyorHelper
|
2
|
+
|
3
|
+
# Extending surveyor
|
4
|
+
include SurveyorHelperExtensions if Surveyor::Config['extend'].include?("surveyor_helper")
|
5
|
+
|
6
|
+
# Configuration
|
7
|
+
def surveyor_config
|
8
|
+
Surveyor::Config
|
9
|
+
end
|
10
|
+
|
11
|
+
# Layout: stylsheets and javascripts
|
12
|
+
def surveyor_includes
|
13
|
+
surveyor_stylsheets + surveyor_javascripts
|
14
|
+
end
|
15
|
+
def surveyor_stylsheets
|
16
|
+
stylesheet_link_tag 'surveyor/reset', 'surveyor/surveyor', 'surveyor/ui.theme.css','surveyor/jquery-ui-slider-additions'
|
17
|
+
end
|
18
|
+
def surveyor_javascripts
|
19
|
+
javascript_include_tag 'surveyor/jquery-1.2.6.js', 'surveyor/jquery-ui-personalized-1.5.3.js', 'surveyor/accessibleUISlider.jQuery.js','surveyor/jquery.form.js', 'surveyor/surveyor.js'
|
20
|
+
end
|
21
|
+
|
22
|
+
# Section: dependencies, menu, previous and next
|
23
|
+
def dependency_explanation_helper(question,response_set)
|
24
|
+
# Attempts to explain why this dependent question needs to be answered by referenced the dependent question and users response
|
25
|
+
trigger_responses = []
|
26
|
+
dependent_questions = Question.find_all_by_id(question.dependency.dependency_conditions.map(&:question_id)).uniq
|
27
|
+
response_set.responses.find_all_by_question_id(dependent_questions.map(&:id)).uniq.each do |resp|
|
28
|
+
trigger_responses << resp.to_s
|
29
|
+
end
|
30
|
+
" You answered "#{trigger_responses.join("" and "")}" to the question "#{dependent_questions.map(&:text).join("","")}""
|
31
|
+
end
|
32
|
+
def menu_button_for(section)
|
33
|
+
submit_tag(section.title, :name => "section[#{section.id}]")
|
34
|
+
end
|
35
|
+
def previous_section
|
36
|
+
# submit_tag("« Previous section", :name => "section[#{@section.previous.id}]") unless @section.previous.nil?
|
37
|
+
# refactored to use copy in memory instead of making extra db calls
|
38
|
+
submit_tag("« Previous section", :name => "section[#{@sections[@sections.index(@section)-1].id}]") unless @sections.first == @section
|
39
|
+
end
|
40
|
+
def next_section
|
41
|
+
# @section.next.nil? ? submit_tag("Click here to finish", :name => "finish") : submit_tag("Next section »", :name => "section[#{@section.next.id}]")
|
42
|
+
# refactored to use copy in memory instead of making extra db calls
|
43
|
+
@sections.last == @section ? submit_tag("Click here to finish", :name => "finish") : submit_tag("Next section »", :name => "section[#{@sections[@sections.index(@section)+1].id}]")
|
44
|
+
end
|
45
|
+
|
46
|
+
# Questions
|
47
|
+
def next_number
|
48
|
+
@n ||= 0
|
49
|
+
"#{@n += 1}<span style='padding-left:0.1em;'>)</span>"
|
50
|
+
end
|
51
|
+
def split_text(text = "") # Split text into with "|" delimiter - parts to go before/after input element
|
52
|
+
{:prefix => text.split("|")[0].blank? ? " " : text.split("|")[0], :postfix => text.split("|")[1] || " "}
|
53
|
+
end
|
54
|
+
def question_help_helper(question)
|
55
|
+
question.help_text.blank? ? "" : %Q(<span class="question-help">#{question.help_text}</span>)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Answers
|
59
|
+
def fields_for_response(response, response_group = nil, &block)
|
60
|
+
name = response_group.nil? ? "responses[#{response.question_id}][#{response.answer_id}]" : "response_groups[#{response.question_id}][#{response_group}][#{response.answer_id}]"
|
61
|
+
fields_for(name, response, :builder => SurveyFormBuilder, &block)
|
62
|
+
end
|
63
|
+
def fields_for_radio(response, &block)
|
64
|
+
fields_for("responses[#{response.question_id}]", response, :builder => SurveyFormBuilder, &block)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Answer < ActiveRecord::Base
|
2
|
+
|
3
|
+
# Extending surveyor
|
4
|
+
include "#{self.name}Extensions".constantize if Surveyor::Config['extend'].include?(self.name.underscore)
|
5
|
+
|
6
|
+
# Associations
|
7
|
+
belongs_to :question
|
8
|
+
has_many :responses
|
9
|
+
|
10
|
+
# Scopes
|
11
|
+
default_scope :order => "display_order ASC"
|
12
|
+
|
13
|
+
# Validations
|
14
|
+
validates_presence_of :text
|
15
|
+
validates_numericality_of :question_id, :allow_nil => false, :only_integer => true
|
16
|
+
|
17
|
+
# Methods
|
18
|
+
def renderer(q = question)
|
19
|
+
r = [q.pick.to_s, self.response_class].compact.map(&:downcase).join("_")
|
20
|
+
r.blank? ? :default : r.to_sym
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class Dependency < ActiveRecord::Base
|
2
|
+
|
3
|
+
# Extending surveyor
|
4
|
+
include "#{self.name}Extensions".constantize if Surveyor::Config['extend'].include?(self.name.underscore)
|
5
|
+
|
6
|
+
# Associations
|
7
|
+
belongs_to :question
|
8
|
+
belongs_to :question_group
|
9
|
+
has_many :dependency_conditions
|
10
|
+
|
11
|
+
# Validations
|
12
|
+
validates_presence_of :rule
|
13
|
+
validates_format_of :rule, :with => /^(?:and|or|\)|\(|[A-Z]|\s)+$/ #TODO properly formed parenthesis etc.
|
14
|
+
validates_numericality_of :question_id, :if => Proc.new { |d| d.question_group_id.nil? }
|
15
|
+
validates_numericality_of :question_group_id, :if => Proc.new { |d| d.question_id.nil? }
|
16
|
+
|
17
|
+
# Attribute aliases
|
18
|
+
alias_attribute :dependent_question_id, :question_id
|
19
|
+
|
20
|
+
def question_group_id=(i)
|
21
|
+
write_attribute(:question_id, nil) unless i.nil?
|
22
|
+
write_attribute(:question_group_id, i)
|
23
|
+
end
|
24
|
+
|
25
|
+
def question_id=(i)
|
26
|
+
write_attribute(:question_group_id, nil) unless i.nil?
|
27
|
+
write_attribute(:question_id, i)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Has this dependency has been met in the context of response_set?
|
31
|
+
# Substitutes the conditions hash into the rule and evaluates it
|
32
|
+
def is_met?(response_set)
|
33
|
+
ch = conditions_hash(response_set)
|
34
|
+
return false if ch.blank?
|
35
|
+
# logger.debug "rule: #{self.rule.inspect}"
|
36
|
+
# logger.debug "rexp: #{rgx.inspect}"
|
37
|
+
# logger.debug "keyp: #{ch.inspect}"
|
38
|
+
# logger.debug "subd: #{self.rule.gsub(rgx){|m| ch[m.to_sym]}}"
|
39
|
+
rgx = Regexp.new(self.dependency_conditions.map{|dc| ["a","o"].include?(dc.rule_key) ? "#{dc.rule_key}(?!nd|r)" : dc.rule_key}.join("|")) # exclude and, or
|
40
|
+
eval(self.rule.gsub(rgx){|m| ch[m.to_sym]})
|
41
|
+
end
|
42
|
+
|
43
|
+
# A hash of the conditions (keyed by rule_key) and their evaluation (boolean) in the context of response_set
|
44
|
+
def conditions_hash(response_set)
|
45
|
+
hash = {}
|
46
|
+
self.dependency_conditions.each{|dc| hash.merge!(dc.to_hash(response_set))}
|
47
|
+
return hash
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class DependencyCondition < ActiveRecord::Base
|
2
|
+
|
3
|
+
# Extending surveyor
|
4
|
+
include "#{self.name}Extensions".constantize if Surveyor::Config['extend'].include?(self.name.underscore)
|
5
|
+
|
6
|
+
# Constants
|
7
|
+
OPERATORS = %w(== != < > <= >=) # CONSTANT or @@class_variable when validations listed before class method
|
8
|
+
|
9
|
+
# Associations
|
10
|
+
belongs_to :answer
|
11
|
+
belongs_to :dependency
|
12
|
+
belongs_to :dependent_question, :foreign_key => :question_id, :class_name => :question
|
13
|
+
belongs_to :question
|
14
|
+
|
15
|
+
# Validations
|
16
|
+
validates_numericality_of :dependency_id, :question_id, :answer_id
|
17
|
+
validates_presence_of :operator, :rule_key
|
18
|
+
validates_inclusion_of :operator, :in => OPERATORS
|
19
|
+
validates_uniqueness_of :rule_key, :scope => :dependency_id
|
20
|
+
|
21
|
+
acts_as_response # includes "as" instance method
|
22
|
+
|
23
|
+
# Class methods
|
24
|
+
def self.operators
|
25
|
+
OPERATORS
|
26
|
+
end
|
27
|
+
|
28
|
+
# Instance methods
|
29
|
+
def to_hash(response_set)
|
30
|
+
response = response_set.responses.detect{|r| r.answer_id.to_i == self.answer_id.to_i} || false # eval("nil and false") => nil so return false if no response is found
|
31
|
+
{rule_key.to_sym => (response and self.is_met?(response))}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Checks to see if the response passed in meets the dependency condition
|
35
|
+
def is_met?(response)
|
36
|
+
klass = response.answer.response_class
|
37
|
+
return case self.operator
|
38
|
+
when "==", "<", ">", "<=", ">="
|
39
|
+
response.as(klass).send(self.operator, self.as(klass))
|
40
|
+
when "!="
|
41
|
+
!(response.as(klass) == self.as(klass))
|
42
|
+
else
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Question < ActiveRecord::Base
|
2
|
+
|
3
|
+
# Extending surveyor
|
4
|
+
include "#{self.name}Extensions".constantize if Surveyor::Config['extend'].include?(self.name.underscore)
|
5
|
+
|
6
|
+
# Associations
|
7
|
+
belongs_to :survey_section
|
8
|
+
belongs_to :question_group
|
9
|
+
has_many :answers, :order => "display_order ASC" # it might not always have answers
|
10
|
+
has_one :dependency
|
11
|
+
|
12
|
+
# Scopes
|
13
|
+
default_scope :order => "display_order ASC"
|
14
|
+
|
15
|
+
# Validations
|
16
|
+
validates_presence_of :text, :survey_section_id, :display_order
|
17
|
+
validates_inclusion_of :is_mandatory, :in => [true, false]
|
18
|
+
|
19
|
+
# Instance Methods
|
20
|
+
def initialize(*args)
|
21
|
+
super(*args)
|
22
|
+
default_args
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_args
|
26
|
+
self.is_mandatory ||= true
|
27
|
+
self.display_type ||= "default"
|
28
|
+
self.pick ||= "none"
|
29
|
+
end
|
30
|
+
|
31
|
+
def mandatory?
|
32
|
+
self.is_mandatory == true
|
33
|
+
end
|
34
|
+
|
35
|
+
def dependent?
|
36
|
+
self.dependency != nil
|
37
|
+
end
|
38
|
+
def triggered?(response_set)
|
39
|
+
dependent? ? self.dependency.is_met?(response_set) : true
|
40
|
+
end
|
41
|
+
def css_class(response_set)
|
42
|
+
[(dependent? ? "dependent" : nil), (triggered?(response_set) ? nil : "hidden"), custom_class].compact.join(" ")
|
43
|
+
end
|
44
|
+
|
45
|
+
def part_of_group?
|
46
|
+
!self.question_group.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
def renderer(g = question_group)
|
50
|
+
r = [g ? g.renderer.to_s : nil, display_type].compact.join("_")
|
51
|
+
r.blank? ? :default : r.to_sym
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class QuestionGroup < ActiveRecord::Base
|
2
|
+
|
3
|
+
# Extending surveyor
|
4
|
+
include "#{self.name}Extensions".constantize if Surveyor::Config['extend'].include?(self.name.underscore)
|
5
|
+
|
6
|
+
has_many :questions
|
7
|
+
has_one :dependency
|
8
|
+
|
9
|
+
# Instance Methods
|
10
|
+
def initialize(*args)
|
11
|
+
super(*args)
|
12
|
+
default_args
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_args
|
16
|
+
self.display_type ||= "inline"
|
17
|
+
end
|
18
|
+
|
19
|
+
def renderer
|
20
|
+
display_type.blank? ? :default : display_type.to_sym
|
21
|
+
end
|
22
|
+
|
23
|
+
def dependent?
|
24
|
+
self.dependency != nil
|
25
|
+
end
|
26
|
+
def triggered?(response_set)
|
27
|
+
dependent? ? self.dependency.is_met?(response_set) : true
|
28
|
+
end
|
29
|
+
def css_class(response_set)
|
30
|
+
[(dependent? ? "dependent" : nil), (triggered?(response_set) ? nil : "hidden"), custom_class].compact.join(" ")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
class Response < ActiveRecord::Base
|
2
|
+
|
3
|
+
# Extending surveyor
|
4
|
+
include "#{self.name}Extensions".constantize if Surveyor::Config['extend'].include?(self.name.underscore)
|
5
|
+
|
6
|
+
include ActionView::Helpers::SanitizeHelper
|
7
|
+
|
8
|
+
# Associations
|
9
|
+
belongs_to :response_set
|
10
|
+
belongs_to :question
|
11
|
+
belongs_to :answer
|
12
|
+
|
13
|
+
# Validations
|
14
|
+
validates_presence_of :response_set_id, :question_id, :answer_id
|
15
|
+
|
16
|
+
acts_as_response # includes "as" instance method
|
17
|
+
|
18
|
+
def selected
|
19
|
+
!self.new_record?
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :selected?, :selected
|
23
|
+
|
24
|
+
def selected=(value)
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def correct?
|
29
|
+
question.correct_answer_id.nil? or self.answer.response_class != "answer" or (question.correct_answer_id.to_i == answer_id.to_i)
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s # used in dependency_explanation_helper
|
33
|
+
if self.answer.response_class == "answer" and self.answer_id
|
34
|
+
return self.answer.text
|
35
|
+
else
|
36
|
+
return "#{(self.string_value || self.text_value || self.integer_value || self.float_value || nil).to_s}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# Error for when answer's response_class is not in
|
42
|
+
# ( answer string integer float text date time datetime )
|
43
|
+
# Actually, date and time aren't available anymore.
|
44
|
+
class InvalidResponseClass < StandardError #:nodoc:
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return an individual response's question and
|
48
|
+
# answer coded for Home Exposure questionnaire.
|
49
|
+
def q_and_a_codes
|
50
|
+
q_code = self.question.data_export_identifier
|
51
|
+
|
52
|
+
unless %w( answer string integer float
|
53
|
+
text datetime
|
54
|
+
).include?(self.answer.response_class)
|
55
|
+
raise InvalidResponseClass
|
56
|
+
end
|
57
|
+
|
58
|
+
a_code = if self.answer.response_class == "answer"
|
59
|
+
self.answer.data_export_identifier
|
60
|
+
else
|
61
|
+
self.send("#{self.answer.response_class}_value")
|
62
|
+
end
|
63
|
+
[ q_code, a_code ]
|
64
|
+
end
|
65
|
+
|
66
|
+
def q_and_a_codes_and_text
|
67
|
+
q_code = self.question.data_export_identifier
|
68
|
+
|
69
|
+
unless %w( answer string integer float
|
70
|
+
text datetime
|
71
|
+
).include?(self.answer.response_class)
|
72
|
+
raise InvalidResponseClass
|
73
|
+
end
|
74
|
+
|
75
|
+
a_code = if self.answer.response_class == "answer"
|
76
|
+
self.answer.data_export_identifier
|
77
|
+
else
|
78
|
+
self.send("#{self.answer.response_class}_value")
|
79
|
+
end
|
80
|
+
|
81
|
+
a_text = if self.answer.response_class == "answer"
|
82
|
+
self.answer.text
|
83
|
+
else
|
84
|
+
self.send("#{self.answer.response_class}_value")
|
85
|
+
end
|
86
|
+
|
87
|
+
q_text = self.question.text
|
88
|
+
|
89
|
+
{ q_code => { :a_code => a_code, :a_text => a_text, :q_text => q_text }}
|
90
|
+
end
|
91
|
+
alias_method :codes_and_text, :q_and_a_codes_and_text
|
92
|
+
|
93
|
+
|
94
|
+
end
|
@@ -0,0 +1,316 @@
|
|
1
|
+
class ResponseSet < ActiveRecord::Base
|
2
|
+
|
3
|
+
# Extending surveyor
|
4
|
+
include "#{self.name}Extensions".constantize if Surveyor::Config['extend'].include?(self.name.underscore)
|
5
|
+
|
6
|
+
# Associations
|
7
|
+
belongs_to :survey
|
8
|
+
belongs_to :user
|
9
|
+
has_many :responses, :dependent => :destroy
|
10
|
+
|
11
|
+
# Validations
|
12
|
+
validates_presence_of :survey_id
|
13
|
+
validates_associated :responses
|
14
|
+
validates_uniqueness_of :access_code
|
15
|
+
|
16
|
+
# Attributes
|
17
|
+
attr_protected :completed_at
|
18
|
+
attr_accessor :current_section_id
|
19
|
+
|
20
|
+
# Callbacks
|
21
|
+
after_update :save_responses
|
22
|
+
|
23
|
+
# Instance methods
|
24
|
+
def initialize(*args)
|
25
|
+
super(*args)
|
26
|
+
default_args
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_args
|
30
|
+
self.started_at ||= Time.now
|
31
|
+
self.access_code = Surveyor.make_tiny_code
|
32
|
+
end
|
33
|
+
|
34
|
+
def access_code=(val)
|
35
|
+
while ResponseSet.find_by_access_code(val)
|
36
|
+
val = Surveyor.make_tiny_code
|
37
|
+
end
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_csv
|
42
|
+
qcols = Question.content_columns.map(&:name) - %w(created_at updated_at)
|
43
|
+
acols = Answer.content_columns.map(&:name) - %w(created_at updated_at)
|
44
|
+
rcols = Response.content_columns.map(&:name)
|
45
|
+
require 'fastercsv'
|
46
|
+
FCSV(result = "") do |csv|
|
47
|
+
csv << qcols.map{|qcol| "question.#{qcol}"} + acols.map{|acol| "answer.#{acol}"} + rcols.map{|rcol| "response.#{rcol}"}
|
48
|
+
responses.each do |response|
|
49
|
+
csv << qcols.map{|qcol| response.question.send(qcol)} + acols.map{|acol| response.answer.send(acol)} + rcols.map{|rcol| response.send(rcol)}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
def response_for(question_id, answer_id, group = nil)
|
56
|
+
found = responses.detect{|r| r.question_id == question_id && r.answer_id == answer_id && r.response_group.to_s == group.to_s}
|
57
|
+
found.blank? ? responses.new(:question_id => question_id, :answer_id => answer_id, :response_group => group) : found
|
58
|
+
end
|
59
|
+
|
60
|
+
def clear_responses
|
61
|
+
question_ids = Question.find_all_by_survey_section_id(current_section_id).map(&:id)
|
62
|
+
responses.select{|r| question_ids.include? r.question_id}.map(&:destroy)
|
63
|
+
responses.reload
|
64
|
+
end
|
65
|
+
|
66
|
+
def response_attributes=(response_attributes)
|
67
|
+
response_attributes.each do |question_id, responses_hash|
|
68
|
+
# Response.delete_all(["response_set_id =? AND question_id =?", self.id, question_id])
|
69
|
+
if (answer_id = responses_hash[:answer_id])
|
70
|
+
if (!responses_hash[:answer_id].empty?) # Dropdowns return answer id but have an empty value if they are not set... ignoring those.
|
71
|
+
#radio or dropdown - only one response
|
72
|
+
responses.build({:question_id => question_id, :answer_id => answer_id}.merge(responses_hash[answer_id] || {}))
|
73
|
+
end
|
74
|
+
else
|
75
|
+
#possibly multiples responses - unresponded radios end up here too
|
76
|
+
# we use the variable question_id, not the "question_id" in the response_hash
|
77
|
+
responses_hash.delete_if{|k,v| k == "question_id"}.each do |answer_id, response_hash|
|
78
|
+
unless response_hash.delete_if{|k,v| v.blank?}.empty?
|
79
|
+
responses.build({:question_id => question_id, :answer_id => answer_id}.merge(response_hash))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def response_group_attributes=(response_attributes)
|
87
|
+
response_attributes.each do |question_id, responses_group_hash|
|
88
|
+
# Response.delete_all(["response_set_id =? AND question_id =?", self.id, question_id])
|
89
|
+
responses_group_hash.each do |response_group_number, group_hash|
|
90
|
+
if (answer_id = group_hash[:answer_id]) # if group_hash has an answer_id key we treat it differently
|
91
|
+
if (!group_hash[:answer_id].empty?) # dropdowns return empty values in answer_ids if they are not selected
|
92
|
+
#radio or dropdown - only one response
|
93
|
+
responses.build({:question_id => question_id, :answer_id => answer_id, :response_group => response_group_number}.merge(group_hash[answer_id] || {}))
|
94
|
+
end
|
95
|
+
else
|
96
|
+
#possibly multiples responses - unresponded radios end up here too
|
97
|
+
# we use the variable question_id in the key, not the "question_id" in the response_hash... same with response_group key
|
98
|
+
group_hash.delete_if{|k,v| (k == "question_id") or (k == "response_group")}.each do |answer_id, inner_hash|
|
99
|
+
unless inner_hash.delete_if{|k,v| v.blank?}.empty?
|
100
|
+
responses.build({:question_id => question_id, :answer_id => answer_id, :response_group => response_group_number}.merge(inner_hash))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def save_responses
|
110
|
+
responses.each{|response| response.save(false)}
|
111
|
+
end
|
112
|
+
|
113
|
+
def complete!
|
114
|
+
self.completed_at = Time.now
|
115
|
+
end
|
116
|
+
|
117
|
+
def correct?
|
118
|
+
responses.all?(&:correct?)
|
119
|
+
end
|
120
|
+
def correctness_hash
|
121
|
+
{ :questions => survey.sections_with_questions.map(&:questions).flatten.compact.size,
|
122
|
+
:responses => responses.compact.size,
|
123
|
+
:correct => responses.find_all(&:correct?).compact.size
|
124
|
+
}
|
125
|
+
end
|
126
|
+
def mandatory_questions_complete?
|
127
|
+
progress_hash[:triggered_mandatory] == progress_hash[:triggered_mandatory_completed]
|
128
|
+
end
|
129
|
+
def progress_hash
|
130
|
+
qs = survey.sections_with_questions.map(&:questions).flatten
|
131
|
+
ds = dependencies(qs.map(&:id))
|
132
|
+
triggered = qs - ds.select{|d| !d.is_met?(self)}.map(&:question)
|
133
|
+
{ :questions => qs.compact.size,
|
134
|
+
:triggered => triggered.compact.size,
|
135
|
+
:triggered_mandatory => triggered.select{|q| q.mandatory?}.compact.size,
|
136
|
+
:triggered_mandatory_completed => triggered.select{|q| q.mandatory? and is_answered?(q)}.compact.size
|
137
|
+
}
|
138
|
+
end
|
139
|
+
def is_answered?(question)
|
140
|
+
%w(label image).include?(question.display_type) or !is_unanswered?(question)
|
141
|
+
end
|
142
|
+
def is_unanswered?(question)
|
143
|
+
self.responses.detect{|r| r.question_id == question.id}.nil?
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns the number of response groups (count of group responses enterted) for this question group
|
147
|
+
def count_group_responses(questions)
|
148
|
+
questions.map{|q| responses.select{|r| (r.question_id.to_i == q.id.to_i) && !r.response_group.nil?}.group_by(&:response_group).size }.max
|
149
|
+
end
|
150
|
+
|
151
|
+
def unanswered_dependencies
|
152
|
+
dependencies.select{|d| d.is_met?(self) and self.is_unanswered?(d.question)}.map(&:question)
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
# Why return nil, when you can return a real value.
|
157
|
+
# This is untested so there may be some consequences.
|
158
|
+
# I only had it for use in the console. Commenting.
|
159
|
+
# def current_section_id
|
160
|
+
# # Do all surveys have a first section???
|
161
|
+
# @current_section_id || self.survey.sections.first.id
|
162
|
+
# end
|
163
|
+
|
164
|
+
def all_validations
|
165
|
+
arr = validations.partition{|v| v.is_valid?(self) }
|
166
|
+
{ :valid => arr[0].map{|v| "question_#{v.answer.question_id}"},
|
167
|
+
:invalid => arr[1].map{|v| "question_#{v.answer.question_id}"} }
|
168
|
+
end
|
169
|
+
|
170
|
+
def all_things_hash
|
171
|
+
self.all_dependencies.merge(self.all_validations)
|
172
|
+
end
|
173
|
+
|
174
|
+
def all_dependencies
|
175
|
+
arr = dependencies.partition{|d| d.is_met?(self) }
|
176
|
+
{:show => arr[0].map{|d| d.question_group_id.nil? ? "question_#{d.question_id}" : "question_group_#{d.question_group_id}"}, :hide => arr[1].map{|d| d.question_group_id.nil? ? "question_#{d.question_id}" : "question_group_#{d.question_group_id}"}}
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
|
181
|
+
# Collect all of the question and answers coded for
|
182
|
+
# the Home Exposures questionnaire.
|
183
|
+
#
|
184
|
+
# >> ResponseSet.last.q_and_a_codes
|
185
|
+
#
|
186
|
+
# => [["how_often_vacuumed_12mos", "1"],
|
187
|
+
# ["freq_grilled_meat_outside_12mos", "2"],
|
188
|
+
# ["other_pest_community_sprayed", "dogs"],
|
189
|
+
# ["year_home_built", 1900],
|
190
|
+
# ["number_of_rooms_in_home", 5]]
|
191
|
+
def q_and_a_codes
|
192
|
+
self.responses.collect(&:q_and_a_codes)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Collect all of the question and answers coded for
|
196
|
+
# the Home Exposures questionnaire.
|
197
|
+
#
|
198
|
+
# >> ResponseSet.last.q_and_a_codes_as_attributes
|
199
|
+
#
|
200
|
+
# => {"doneness_of_meat_exterior_12mos"=>"3",
|
201
|
+
# "number_of_rooms_in_home"=>5,
|
202
|
+
# "year_home_built"=>1900,
|
203
|
+
# "cmty_sprayed_other_pest_12mos"=>"1",
|
204
|
+
# "other_pest_community_sprayed"=>"dogs"}
|
205
|
+
#
|
206
|
+
# >> HomeExposureResponse.create(
|
207
|
+
# ResponseSet.find(7).q_and_a_codes_as_attributes)
|
208
|
+
def q_and_a_codes_as_attributes
|
209
|
+
Hash[*self.responses.collect(&:q_and_a_codes).flatten]
|
210
|
+
end
|
211
|
+
|
212
|
+
# Return a good chunk of info used when merging
|
213
|
+
# response sets into one home exposure response
|
214
|
+
def q_and_a_codes_and_text_as_attributes
|
215
|
+
h=Hash.new({:a_code => '', :a_text => '(no answer)', :q_text => ''})
|
216
|
+
# h.merge keeps h's defaults!!! woohoo!
|
217
|
+
h.merge(self.responses.collect(&:codes_and_text).inject(:merge)||{})
|
218
|
+
end
|
219
|
+
alias_method :codes_and_text, :q_and_a_codes_and_text_as_attributes
|
220
|
+
|
221
|
+
# Compare the Q and A codes of 2 response sets
|
222
|
+
# and return boolean.
|
223
|
+
def is_the_same_as?(another_response_set)
|
224
|
+
ars = ResponseSet.find(another_response_set)
|
225
|
+
(self.q_and_a_codes_as_attributes.diff(
|
226
|
+
ars.q_and_a_codes_as_attributes)).blank?
|
227
|
+
end
|
228
|
+
|
229
|
+
# Compare the Q and A codes of 2 response sets
|
230
|
+
# and return hash of differences.
|
231
|
+
def diff(another_response_set)
|
232
|
+
ars = ResponseSet.find(another_response_set)
|
233
|
+
self.q_and_a_codes_as_attributes.diff(ars.q_and_a_codes_as_attributes)
|
234
|
+
end
|
235
|
+
|
236
|
+
def is_complete?
|
237
|
+
# eventually return the is_complete column value
|
238
|
+
# false
|
239
|
+
!self.completed_at.nil?
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
|
244
|
+
|
245
|
+
|
246
|
+
protected
|
247
|
+
|
248
|
+
def validations(question_ids = nil)
|
249
|
+
question_ids ||= Question.find_all_by_survey_section_id(
|
250
|
+
current_section_id).map(&:id)
|
251
|
+
answer_ids = Answer.all(
|
252
|
+
:conditions => {:question_id => question_ids }).map(&:id)
|
253
|
+
Validation.all(:conditions => {:answer_id => answer_ids},
|
254
|
+
:include => :validation_conditions)
|
255
|
+
end
|
256
|
+
|
257
|
+
def dependencies(question_ids = nil)
|
258
|
+
question_ids ||= Question.find_all_by_survey_section_id(current_section_id).map(&:id)
|
259
|
+
depdendecy_ids = DependencyCondition.all(:conditions => {:question_id => question_ids}).map(&:dependency_id)
|
260
|
+
Dependency.find(depdendecy_ids, :include => :dependency_conditions)
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
# responses
|
266
|
+
|
267
|
+
# "responses"=>{
|
268
|
+
#string "6"=>{"question_id"=>"6", "20"=>{"string_value"=>"saf"}},
|
269
|
+
#text "7"=>{"question_id"=>"7", "21"=>{"text_value"=>""}},
|
270
|
+
#radio+txt "1"=>{"question_id"=>"1", "answer_id"=>"1", "4"=>{"string_value"=>""}},
|
271
|
+
#radio "2"=>{"answer_id"=>"6"},
|
272
|
+
#radio "3"=>{"answer_id"=>"10"},
|
273
|
+
#check "4"=>{"question_id"=>"4", "answer_id"=>"15"},
|
274
|
+
#check+txt "5"=>{"question_id"=>"5", "16"=>{"selected"=>"1"}, "19"=>{"string_value"=>""}}
|
275
|
+
# },
|
276
|
+
# "survey_code"=>"test_survey",
|
277
|
+
# "commit"=>"Next Section (Utensiles and you!) >>",
|
278
|
+
# "authenticity_token"=>"8bee21081eea820ab1c658358c0baaa2e46de5d1",
|
279
|
+
# "_method"=>"put",
|
280
|
+
# "action"=>"update",
|
281
|
+
# "controller"=>"app",
|
282
|
+
# "response_set_code"=>"T2x8HhCQej",
|
283
|
+
# "section"=>"2"
|
284
|
+
|
285
|
+
# response groups
|
286
|
+
|
287
|
+
# "24"=>{
|
288
|
+
# "0"=>{"response_group"=>"0", "question_id"=>"24", "answer_id"=>"172"}, "1"=>{"response_group"=>"1", "question_id"=>"24", "answer_id"=>"173"},
|
289
|
+
# "2"=>{"response_group"=>"2", "question_id"=>"24", "answer_id"=>""}, "3"=>{"response_group"=>"3", "question_id"=>"24", "answer_id"=>""},
|
290
|
+
# "4"=>{"response_group"=>"4", "question_id"=>"24", "answer_id"=>""}},
|
291
|
+
# where "24" is the question id
|
292
|
+
|
293
|
+
# Some other examples:
|
294
|
+
# "25"=>{
|
295
|
+
# "0"=>{"response_group"=>"0", "question_id"=>"25", "179"=>{"string_value"=>"camry"}},
|
296
|
+
# "1"=>{"response_group"=>"1", "question_id"=>"25", "179"=>{"string_value"=>"f150"}},
|
297
|
+
# "2"=>{"response_group"=>"2", "question_id"=>"25", "179"=>{"string_value"=>""}},
|
298
|
+
# "3"=>{"response_group"=>"3", "question_id"=>"25", "179"=>{"string_value"=>""}},
|
299
|
+
# "4"=>{"response_group"=>"4", "question_id"=>"25", "179"=>{"string_value"=>""}}},
|
300
|
+
#
|
301
|
+
# "26"=>{
|
302
|
+
# "0"=>{"response_group"=>"0", "question_id"=>"26", "180"=>{"string_value"=>"1999"}},
|
303
|
+
# "1"=>{"response_group"=>"1", "question_id"=>"26", "180"=>{"string_value"=>"2004"}},
|
304
|
+
# "2"=>{"response_group"=>"2", "question_id"=>"26", "180"=>{"string_value"=>""}},
|
305
|
+
# "3"=>{"response_group"=>"3", "question_id"=>"26", "180"=>{"string_value"=>""}},
|
306
|
+
# "4"=>{"response_group"=>"4", "question_id"=>"26", "180"=>{"string_value"=>""}}},
|
307
|
+
#
|
308
|
+
# "27"=>{
|
309
|
+
# "0"=>{"182"=>{"integer_value"=>""}, "response_group"=>"0", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
310
|
+
# "1"=>{"182"=>{"integer_value"=>""}, "response_group"=>"1", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
311
|
+
# "2"=>{"182"=>{"integer_value"=>""}, "response_group"=>"2", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
312
|
+
# "3"=>{"182"=>{"integer_value"=>""}, "response_group"=>"3", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
313
|
+
# "4"=>{"182"=>{"integer_value"=>""}, "response_group"=>"4", "question_id"=>"27", "181"=>{"string_value"=>""}}}},
|
314
|
+
|
315
|
+
# 0,1,2,3,4 are the response group numbers
|
316
|
+
# and anything else in the response group hash is handled normally
|