breakpointer-surveyor 0.2.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/.gitignore +6 -0
- data/MIT-LICENSE +20 -0
- data/README.md +20 -0
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/app/controllers/answers_controller.rb +87 -0
- data/app/controllers/dependencies_controller.rb +87 -0
- data/app/controllers/dependency_conditions_controller.rb +87 -0
- data/app/controllers/questions_controller.rb +85 -0
- data/app/controllers/sections_controller.rb +87 -0
- data/app/controllers/surveying_controller.rb +139 -0
- data/app/controllers/surveys_controller.rb +87 -0
- data/app/helpers/answers_helper.rb +2 -0
- data/app/helpers/application_helper.rb +3 -0
- data/app/helpers/questions_helper.rb +2 -0
- data/app/helpers/sections_helper.rb +2 -0
- data/app/helpers/survey_form_builder.rb +38 -0
- data/app/helpers/survey_importer_helper.rb +2 -0
- data/app/helpers/surveying_helper.rb +91 -0
- data/app/helpers/surveys_helper.rb +2 -0
- data/app/models/answer.rb +18 -0
- data/app/models/dependency.rb +43 -0
- data/app/models/dependency_condition.rb +76 -0
- data/app/models/question.rb +44 -0
- data/app/models/question_group.rb +5 -0
- data/app/models/response.rb +58 -0
- data/app/models/response_set.rb +174 -0
- data/app/models/survey.rb +54 -0
- data/app/models/survey_section.rb +19 -0
- data/app/models/user.rb +5 -0
- data/app/views/answer_display_types/_any_answer.html.haml +3 -0
- data/app/views/answer_display_types/_any_other_and_string.html.haml +5 -0
- data/app/views/answer_display_types/_any_string.html.haml +4 -0
- data/app/views/answer_display_types/_date.html.haml +3 -0
- data/app/views/answer_display_types/_datetime.html.haml +2 -0
- data/app/views/answer_display_types/_default.html.haml +1 -0
- data/app/views/answer_display_types/_float.html.haml +5 -0
- data/app/views/answer_display_types/_grid_any_answer.html.haml +2 -0
- data/app/views/answer_display_types/_grid_default.html.haml +2 -0
- data/app/views/answer_display_types/_grid_float.html.haml +2 -0
- data/app/views/answer_display_types/_grid_integer.html.haml +2 -0
- data/app/views/answer_display_types/_grid_one_answer.html.haml +2 -0
- data/app/views/answer_display_types/_grid_string.html.haml +2 -0
- data/app/views/answer_display_types/_integer.html.haml +6 -0
- data/app/views/answer_display_types/_one_answer.html.haml +3 -0
- data/app/views/answer_display_types/_one_string.html.haml +6 -0
- data/app/views/answer_display_types/_repeater_integer.html.haml +6 -0
- data/app/views/answer_display_types/_repeater_string.html.haml +7 -0
- data/app/views/answer_display_types/_string.html.haml +7 -0
- data/app/views/answer_display_types/_text.html.haml +6 -0
- data/app/views/answer_display_types/_time.html.haml +2 -0
- data/app/views/layouts/surveys.html.erb +17 -0
- data/app/views/question_display_types/_default.html.haml +12 -0
- data/app/views/question_display_types/_dropdown.html.haml +12 -0
- data/app/views/question_display_types/_grid_default.html.haml +14 -0
- data/app/views/question_display_types/_grid_dropdown.html.haml +15 -0
- data/app/views/question_display_types/_group_default.html.haml +8 -0
- data/app/views/question_display_types/_group_dropdown.html.haml +6 -0
- data/app/views/question_display_types/_image.html.haml +2 -0
- data/app/views/question_display_types/_inline.html.haml +12 -0
- data/app/views/question_display_types/_label.html.haml +4 -0
- data/app/views/question_display_types/_repeater_default.html.haml +10 -0
- data/app/views/question_display_types/_repeater_dropdown.html.haml +8 -0
- data/app/views/question_display_types/_slider.html.haml +17 -0
- data/app/views/question_group_display_types/_default.html.haml +13 -0
- data/app/views/question_group_display_types/_grid.html.haml +24 -0
- data/app/views/question_group_display_types/_repeater.html.haml +20 -0
- data/app/views/surveying/edit.html.haml +38 -0
- data/app/views/surveying/finish.html.haml +1 -0
- data/app/views/surveying/index.html.erb +19 -0
- data/app/views/surveying/new.html.haml +17 -0
- data/app/views/surveying/show.html.haml +8 -0
- data/config/routes.rb +10 -0
- data/generators/surveyor/surveyor_generator.rb +38 -0
- data/generators/surveyor/templates/README +9 -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 +35 -0
- data/generators/surveyor/templates/assets/stylesheets/jquery-ui-slider-additions.css +71 -0
- data/generators/surveyor/templates/assets/stylesheets/reset.css +46 -0
- data/generators/surveyor/templates/assets/stylesheets/sass/surveyor.sass +321 -0
- data/generators/surveyor/templates/assets/stylesheets/surveyor.css +245 -0
- data/generators/surveyor/templates/assets/stylesheets/ui.theme.css +851 -0
- data/generators/surveyor/templates/migrate/create_answers.rb +46 -0
- data/generators/surveyor/templates/migrate/create_dependencies.rb +21 -0
- data/generators/surveyor/templates/migrate/create_dependency_conditions.rb +29 -0
- data/generators/surveyor/templates/migrate/create_question_groups.rb +18 -0
- data/generators/surveyor/templates/migrate/create_questions.rb +33 -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 +25 -0
- data/generators/surveyor/templates/migrate/create_surveys.rb +25 -0
- data/generators/surveyor/templates/surveys/kitchen_sink_survey.rb +201 -0
- data/init.rb +1 -0
- data/install.rb +1 -0
- data/lib/tasks/surveyor_tasks.rake +29 -0
- data/lib/tiny_code.rb +58 -0
- data/lib/user_manager.rb +9 -0
- data/lib/xml_formatter.rb +12 -0
- data/script/surveyor/answer.rb +84 -0
- data/script/surveyor/columnizer.rb +36 -0
- data/script/surveyor/dependency.rb +43 -0
- data/script/surveyor/dependency_condition.rb +74 -0
- data/script/surveyor/dslparse.rb +66 -0
- data/script/surveyor/question.rb +76 -0
- data/script/surveyor/question_group.rb +33 -0
- data/script/surveyor/specs/answer_spec.rb +66 -0
- data/script/surveyor/specs/question_dependency_spec.rb +46 -0
- data/script/surveyor/specs/question_group_spec.rb +9 -0
- data/script/surveyor/specs/question_spec.rb +111 -0
- data/script/surveyor/specs/section_spec.rb +58 -0
- data/script/surveyor/survey.rb +108 -0
- data/script/surveyor/survey_section.rb +153 -0
- data/script/surveyor/whr_dsl.tmproj +244 -0
- data/spec/controllers/answers_controller_spec.rb +64 -0
- data/spec/controllers/dependencies_controller_spec.rb +63 -0
- data/spec/controllers/dependency_conditions_controller_spec.rb +64 -0
- data/spec/controllers/questions_controller_spec.rb +64 -0
- data/spec/controllers/sections_controller_spec.rb +64 -0
- data/spec/controllers/surveying_controller_spec.rb +328 -0
- data/spec/controllers/surveying_routing_spec.rb +45 -0
- data/spec/controllers/surveys_controller_spec.rb +64 -0
- data/spec/fixtures/answers.yml +9 -0
- data/spec/fixtures/dependencies.yml +7 -0
- data/spec/fixtures/dependency_conditions.yml +27 -0
- data/spec/fixtures/question_groups.yml +7 -0
- data/spec/fixtures/questions.yml +15 -0
- data/spec/fixtures/response_sets.yml +13 -0
- data/spec/fixtures/responses.yml +9 -0
- data/spec/fixtures/survey_sections.yml +11 -0
- data/spec/fixtures/surveys.yml +9 -0
- data/spec/fixtures/users.yml +7 -0
- data/spec/helpers/survey_importer_helper_spec.rb +11 -0
- data/spec/helpers/surveying_helper_spec.rb +11 -0
- data/spec/models/answer_spec.rb +62 -0
- data/spec/models/dependency_condition_spec.rb +347 -0
- data/spec/models/dependency_spec.rb +82 -0
- data/spec/models/question_group_spec.rb +11 -0
- data/spec/models/question_spec.rb +70 -0
- data/spec/models/response_set_spec.rb +172 -0
- data/spec/models/response_spec.rb +94 -0
- data/spec/models/survey_section_spec.rb +34 -0
- data/spec/models/survey_spec.rb +72 -0
- data/spec/models/user_spec.rb +11 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +72 -0
- data/spec/views/app/edit.html.erb_spec.rb +20 -0
- data/spec/views/app/index.html.erb_spec.rb +19 -0
- data/spec/views/app/new.html.erb_spec.rb +21 -0
- data/spec/views/app/show.html.erb_spec.rb +17 -0
- data/surveyor.gemspec +258 -0
- data/uninstall.rb +1 -0
- metadata +286 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class Answer < ActiveRecord::Base
|
|
2
|
+
|
|
3
|
+
# Associations
|
|
4
|
+
belongs_to :question
|
|
5
|
+
has_many :responses
|
|
6
|
+
|
|
7
|
+
# Validations
|
|
8
|
+
validates_presence_of :text
|
|
9
|
+
validates_numericality_of :question_id, :allow_nil => false, :only_integer => true
|
|
10
|
+
#validates_uniqueness_of :reference_identifier
|
|
11
|
+
|
|
12
|
+
# Methods
|
|
13
|
+
def partial_name
|
|
14
|
+
[(self.question.pick == "none")? nil : self.question.pick.downcase, self.response_class.downcase].compact.join("_")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
class Dependency < ActiveRecord::Base
|
|
2
|
+
# Associations
|
|
3
|
+
belongs_to :question
|
|
4
|
+
has_many :dependency_conditions
|
|
5
|
+
|
|
6
|
+
# Validations
|
|
7
|
+
validates_presence_of :rule
|
|
8
|
+
validates_format_of :rule, :with => /^(?:and|or|\)|\(|\d|\s)+$/ #TODO properly formed parenthesis etc.
|
|
9
|
+
validates_numericality_of :question_id
|
|
10
|
+
|
|
11
|
+
# Attribute aliases
|
|
12
|
+
alias_attribute :dependent_question_id, :question_id
|
|
13
|
+
|
|
14
|
+
# Is the method that determines if this dependency has been met within
|
|
15
|
+
# the provided response set
|
|
16
|
+
def met?(response_set)
|
|
17
|
+
if keyed_pairs = keyed_conditions(response_set)
|
|
18
|
+
return(rule_evaluation(keyed_pairs))
|
|
19
|
+
else
|
|
20
|
+
return(false)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Pairs up the substitution key with the evaluated condition result for substitution into the rule
|
|
25
|
+
# Example: If you have two dependency conditions with rule keys "A" and "B" in the rule "A or B"
|
|
26
|
+
# calling keyed_condition_pairs will return {:A => true, :B => false}
|
|
27
|
+
def keyed_conditions(response_set)
|
|
28
|
+
keyed_pairs = {}
|
|
29
|
+
self.dependency_conditions.each do |dc|
|
|
30
|
+
keyed_pairs.merge!(dc.to_evaluation_hash(response_set))
|
|
31
|
+
end
|
|
32
|
+
return(keyed_pairs)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Does the substiution and evaluation of the dependency rule with the keyed pairs
|
|
36
|
+
def rule_evaluation(keyed_pairs)
|
|
37
|
+
# subtitute into rule for evaluation
|
|
38
|
+
rgx = Regexp.new(self.dependency_conditions.map{|dc| dc.rule_key}.join("|")) # Making a regexp to only look for the keys used in the child conditions
|
|
39
|
+
#logger.debug("rexp: #{rgx.inspect} FOO: #{keyed_pairs.inspect} --- subbed rules: #{rule.gsub(rgx){|m| keyed_pairs[m.to_sym]}} --> #{eval(self.rule.gsub(rgx){|m| keyed_pairs[m.to_sym]})}")
|
|
40
|
+
eval(self.rule.gsub(rgx){|m| keyed_pairs[m.to_sym]}) # returns the evaluation of the rule and the conditions
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
class DependencyCondition < ActiveRecord::Base
|
|
2
|
+
# Constants
|
|
3
|
+
OPERATORS = %w(== != < > <= >=) # CONSTANT or @@class_variable when validations listed before class method
|
|
4
|
+
|
|
5
|
+
# Associations
|
|
6
|
+
belongs_to :answer
|
|
7
|
+
belongs_to :dependency
|
|
8
|
+
belongs_to :dependent_question, :foreign_key => :question_id, :class_name => :question
|
|
9
|
+
|
|
10
|
+
# Validations
|
|
11
|
+
validates_numericality_of :dependency_id, :question_id, :answer_id
|
|
12
|
+
validates_presence_of :operator, :rule_key
|
|
13
|
+
validates_inclusion_of :operator, :in => OPERATORS
|
|
14
|
+
validates_uniqueness_of :rule_key, :scope => :dependency_id
|
|
15
|
+
|
|
16
|
+
# Class methods
|
|
17
|
+
def self.operators
|
|
18
|
+
OPERATORS
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Evaluates the condition on the response_set
|
|
22
|
+
def evaluation_of(response_set)
|
|
23
|
+
response = response_set.find_response(self.answer_id) || false # turns out eval("nil and false") => nil so we need to return false if no response is found
|
|
24
|
+
return(response and self.is_satisfied_by?(response))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Checks to see if the response passed in satisfies the dependency condition
|
|
28
|
+
def is_satisfied_by?(response)
|
|
29
|
+
response_class = response.answer.response_class
|
|
30
|
+
return case self.operator
|
|
31
|
+
when "=="
|
|
32
|
+
response.as(response_class) == self.as(response_class)
|
|
33
|
+
when "!="
|
|
34
|
+
response.as(response_class) != self.as(response_class)
|
|
35
|
+
when "<"
|
|
36
|
+
response.as(response_class) < self.as(response_class)
|
|
37
|
+
when ">"
|
|
38
|
+
response.as(response_class) > self.as(response_class)
|
|
39
|
+
when "<="
|
|
40
|
+
response.as(response_class) <= self.as(response_class)
|
|
41
|
+
when ">="
|
|
42
|
+
response.as(response_class) >= self.as(response_class)
|
|
43
|
+
else
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Method that returns the dependency as a particular response_class type
|
|
49
|
+
def as(type_symbol)
|
|
50
|
+
return case type_symbol.to_sym
|
|
51
|
+
when :string
|
|
52
|
+
self.string_value
|
|
53
|
+
when :text
|
|
54
|
+
self.text_value
|
|
55
|
+
when :integer
|
|
56
|
+
self.integer_value
|
|
57
|
+
when :float
|
|
58
|
+
self.float_value
|
|
59
|
+
when :answer
|
|
60
|
+
self.answer_id
|
|
61
|
+
else
|
|
62
|
+
self.answer_id
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The hash used in the dependency parent object to evaluate its rule string
|
|
67
|
+
def to_evaluation_hash(response_set)
|
|
68
|
+
{self.symbol_key => self.evaluation_of(response_set)}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Returns the rule key to be used in the subsitution string in the parent depenedency object
|
|
72
|
+
def symbol_key
|
|
73
|
+
self.rule_key.to_sym
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
class Question < ActiveRecord::Base
|
|
2
|
+
|
|
3
|
+
# Associations
|
|
4
|
+
belongs_to :survey_section
|
|
5
|
+
belongs_to :question_group
|
|
6
|
+
has_many :answers # it might not always have answers
|
|
7
|
+
has_one :dependency
|
|
8
|
+
|
|
9
|
+
# Validations
|
|
10
|
+
validates_presence_of :text, :survey_section_id, :display_order
|
|
11
|
+
validates_inclusion_of :is_mandatory, :in => [true, false]
|
|
12
|
+
|
|
13
|
+
# Instance Methods
|
|
14
|
+
def initialize(*args)
|
|
15
|
+
super(*args)
|
|
16
|
+
default_args
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def default_args
|
|
20
|
+
# self.is_active ||= false
|
|
21
|
+
self.is_mandatory ||= true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def mandatory?
|
|
25
|
+
self.is_mandatory == true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def display_type
|
|
29
|
+
super || "default"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def has_dependency?
|
|
33
|
+
self.dependency != nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def dependency_satisfied?(response_set)
|
|
37
|
+
self.has_dependency? and self.dependency.met?(response_set)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def part_of_group?
|
|
41
|
+
!self.question_group.nil?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
class Response < ActiveRecord::Base
|
|
2
|
+
include ActionView::Helpers::SanitizeHelper
|
|
3
|
+
|
|
4
|
+
# Associations
|
|
5
|
+
belongs_to :response_set
|
|
6
|
+
belongs_to :question
|
|
7
|
+
belongs_to :answer
|
|
8
|
+
|
|
9
|
+
# Validations
|
|
10
|
+
validates_presence_of :response_set_id, :question_id, :answer_id
|
|
11
|
+
|
|
12
|
+
# Named scopes
|
|
13
|
+
named_scope :in_section, lambda {|section_id| {:include => :question, :conditions => ['questions.survey_section_id =?', section_id.to_i ]}}
|
|
14
|
+
|
|
15
|
+
def selected
|
|
16
|
+
!self.new_record?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
alias_method :selected?, :selected
|
|
20
|
+
|
|
21
|
+
def selected=(value)
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#Method that returns the response as a particular response_class type
|
|
26
|
+
def as(type_symbol)
|
|
27
|
+
return case type_symbol.to_sym
|
|
28
|
+
when :string
|
|
29
|
+
self.string_value
|
|
30
|
+
when :text
|
|
31
|
+
self.text_value
|
|
32
|
+
when :integer
|
|
33
|
+
self.integer_value
|
|
34
|
+
when :float
|
|
35
|
+
self.float_value
|
|
36
|
+
when :date
|
|
37
|
+
self.datetime_value.nil? ? nil : self.datetime_value.to_date
|
|
38
|
+
when :time
|
|
39
|
+
self.datetime_value.nil? ? nil : self.datetime_value.to_time
|
|
40
|
+
when :datetime
|
|
41
|
+
self.datetime_value
|
|
42
|
+
when :answer
|
|
43
|
+
self.answer_id
|
|
44
|
+
else
|
|
45
|
+
self.answer_id
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def to_s
|
|
50
|
+
if self.answer_id
|
|
51
|
+
self.answer.text
|
|
52
|
+
else
|
|
53
|
+
"#{(self.string_value || self.text_value || self.integer_value || self.float_value).to_s}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
class ResponseSet < ActiveRecord::Base
|
|
2
|
+
include TinyCode
|
|
3
|
+
|
|
4
|
+
# Associations
|
|
5
|
+
belongs_to :survey
|
|
6
|
+
belongs_to :user
|
|
7
|
+
has_many :responses, :dependent => :destroy
|
|
8
|
+
|
|
9
|
+
# Validations
|
|
10
|
+
validates_presence_of :survey_id, :user_id
|
|
11
|
+
validates_associated :responses
|
|
12
|
+
|
|
13
|
+
# Attributes
|
|
14
|
+
attr_protected :completed_at
|
|
15
|
+
attr_accessor :current_section_id
|
|
16
|
+
|
|
17
|
+
# Callbacks
|
|
18
|
+
after_update :save_responses
|
|
19
|
+
|
|
20
|
+
# Instance methods
|
|
21
|
+
def initialize(*args)
|
|
22
|
+
super(*args)
|
|
23
|
+
default_args
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def default_args
|
|
27
|
+
self.started_at ||= Time.now
|
|
28
|
+
self.access_code = ResponseSet.make_tiny_code
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def response_for(question_id, answer_id, response_group = nil)
|
|
32
|
+
found = responses.find_by_question_id_and_answer_id_and_response_group(question_id, answer_id, response_group)
|
|
33
|
+
found.blank? ? responses.new(:question_id => question_id, :answer_id => answer_id, :response_group => response_group) : found
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# "responses"=>{
|
|
37
|
+
#string "6"=>{"question_id"=>"6", "20"=>{"string_value"=>"saf"}},
|
|
38
|
+
#text "7"=>{"question_id"=>"7", "21"=>{"text_value"=>""}},
|
|
39
|
+
#radio+txt "1"=>{"question_id"=>"1", "answer_id"=>"1", "4"=>{"string_value"=>""}},
|
|
40
|
+
#radio "2"=>{"answer_id"=>"6"},
|
|
41
|
+
#radio "3"=>{"answer_id"=>"10"},
|
|
42
|
+
#check "4"=>{"question_id"=>"4", "answer_id"=>"15"},
|
|
43
|
+
#check+txt "5"=>{"question_id"=>"5", "16"=>{"selected"=>"1"}, "19"=>{"string_value"=>""}}
|
|
44
|
+
# },
|
|
45
|
+
# "survey_code"=>"test_survey",
|
|
46
|
+
# "commit"=>"Next Section (Utensiles and you!) >>",
|
|
47
|
+
# "authenticity_token"=>"8bee21081eea820ab1c658358c0baaa2e46de5d1",
|
|
48
|
+
# "_method"=>"put",
|
|
49
|
+
# "action"=>"update",
|
|
50
|
+
# "controller"=>"app",
|
|
51
|
+
# "response_set_code"=>"T2x8HhCQej",
|
|
52
|
+
# "section"=>"2"
|
|
53
|
+
|
|
54
|
+
def response_attributes=(response_attributes)
|
|
55
|
+
response_attributes.each do |question_id, responses_hash|
|
|
56
|
+
Response.delete_all(["response_set_id =? AND question_id =?", self.id, question_id])
|
|
57
|
+
if (answer_id = responses_hash[:answer_id])
|
|
58
|
+
if (!responses_hash[:answer_id].empty?) # Dropdowns return answer id but have an empty value if they are not set... ignoring those.
|
|
59
|
+
#radio or dropdown - only one response
|
|
60
|
+
responses.build({:question_id => question_id, :answer_id => answer_id}.merge(responses_hash[answer_id] || {}))
|
|
61
|
+
end
|
|
62
|
+
else
|
|
63
|
+
#possibly multiples responses - unresponded radios end up here too
|
|
64
|
+
# we use the variable question_id, not the "question_id" in the response_hash
|
|
65
|
+
responses_hash.delete_if{|k,v| k == "question_id"}.each do |answer_id, response_hash|
|
|
66
|
+
unless response_hash.delete_if{|k,v| v.blank?}.empty?
|
|
67
|
+
responses.build({:question_id => question_id, :answer_id => answer_id}.merge(response_hash))
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# "24"=>{
|
|
75
|
+
# "0"=>{"response_group"=>"0", "question_id"=>"24", "answer_id"=>"172"}, "1"=>{"response_group"=>"1", "question_id"=>"24", "answer_id"=>"173"},
|
|
76
|
+
# "2"=>{"response_group"=>"2", "question_id"=>"24", "answer_id"=>""}, "3"=>{"response_group"=>"3", "question_id"=>"24", "answer_id"=>""},
|
|
77
|
+
# "4"=>{"response_group"=>"4", "question_id"=>"24", "answer_id"=>""}},
|
|
78
|
+
# where "24" is the question id
|
|
79
|
+
|
|
80
|
+
# Some other examples:
|
|
81
|
+
# "25"=>{
|
|
82
|
+
# "0"=>{"response_group"=>"0", "question_id"=>"25", "179"=>{"string_value"=>"camry"}},
|
|
83
|
+
# "1"=>{"response_group"=>"1", "question_id"=>"25", "179"=>{"string_value"=>"f150"}},
|
|
84
|
+
# "2"=>{"response_group"=>"2", "question_id"=>"25", "179"=>{"string_value"=>""}},
|
|
85
|
+
# "3"=>{"response_group"=>"3", "question_id"=>"25", "179"=>{"string_value"=>""}},
|
|
86
|
+
# "4"=>{"response_group"=>"4", "question_id"=>"25", "179"=>{"string_value"=>""}}},
|
|
87
|
+
#
|
|
88
|
+
# "26"=>{
|
|
89
|
+
# "0"=>{"response_group"=>"0", "question_id"=>"26", "180"=>{"string_value"=>"1999"}},
|
|
90
|
+
# "1"=>{"response_group"=>"1", "question_id"=>"26", "180"=>{"string_value"=>"2004"}},
|
|
91
|
+
# "2"=>{"response_group"=>"2", "question_id"=>"26", "180"=>{"string_value"=>""}},
|
|
92
|
+
# "3"=>{"response_group"=>"3", "question_id"=>"26", "180"=>{"string_value"=>""}},
|
|
93
|
+
# "4"=>{"response_group"=>"4", "question_id"=>"26", "180"=>{"string_value"=>""}}},
|
|
94
|
+
#
|
|
95
|
+
# "27"=>{
|
|
96
|
+
# "0"=>{"182"=>{"integer_value"=>""}, "response_group"=>"0", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
|
97
|
+
# "1"=>{"182"=>{"integer_value"=>""}, "response_group"=>"1", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
|
98
|
+
# "2"=>{"182"=>{"integer_value"=>""}, "response_group"=>"2", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
|
99
|
+
# "3"=>{"182"=>{"integer_value"=>""}, "response_group"=>"3", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
|
100
|
+
# "4"=>{"182"=>{"integer_value"=>""}, "response_group"=>"4", "question_id"=>"27", "181"=>{"string_value"=>""}}}},
|
|
101
|
+
|
|
102
|
+
# 0,1,2,3,4 are the response group numbers
|
|
103
|
+
# and anything else in the response group hash is handled normally
|
|
104
|
+
|
|
105
|
+
# method to process responses in response groups
|
|
106
|
+
def response_group_attributes=(response_attributes)
|
|
107
|
+
response_attributes.each do |question_id, responses_group_hash|
|
|
108
|
+
Response.delete_all(["response_set_id =? AND question_id =?", self.id, question_id])
|
|
109
|
+
responses_group_hash.each do |response_group_number, group_hash|
|
|
110
|
+
if (answer_id = group_hash[:answer_id]) # if group_hash has an answer_id key we treat it differently
|
|
111
|
+
if (!group_hash[:answer_id].empty?) # dropdowns return empty values in answer_ids if they are not selected
|
|
112
|
+
#radio or dropdown - only one response
|
|
113
|
+
responses.build({:question_id => question_id, :answer_id => answer_id, :response_group => response_group_number}.merge(group_hash[answer_id] || {}))
|
|
114
|
+
end
|
|
115
|
+
else
|
|
116
|
+
#possibly multiples responses - unresponded radios end up here too
|
|
117
|
+
# we use the variable question_id in the key, not the "question_id" in the response_hash... same with response_group key
|
|
118
|
+
group_hash.delete_if{|k,v| (k == "question_id") or (k == "response_group")}.each do |answer_id, inner_hash|
|
|
119
|
+
unless inner_hash.delete_if{|k,v| v.blank?}.empty?
|
|
120
|
+
responses.build({:question_id => question_id, :answer_id => answer_id, :response_group => response_group_number}.merge(inner_hash))
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def save_responses
|
|
131
|
+
responses.each do |response|
|
|
132
|
+
response.save(false)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# ResponseSet has an awareness of its internal state
|
|
137
|
+
def empty?
|
|
138
|
+
self.responses.empty?
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def complete!
|
|
142
|
+
self.completed_at = Time.now
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def has_answered_question?(question)
|
|
146
|
+
!has_not_answered_question?(question)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def has_not_answered_question?(question)
|
|
150
|
+
self.responses.find_all_by_question_id(question.id).empty?
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# ResponseSet knows if certain answers are contained in this response set
|
|
154
|
+
# This method acts as an interface for the dependency analysis
|
|
155
|
+
def find_response(answer_id)
|
|
156
|
+
self.responses.find_by_answer_id(answer_id)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Counts the number of responses for the current user for this question
|
|
160
|
+
def count_question_responses(question)
|
|
161
|
+
Response.count(:conditions => ["response_set_id =? AND question_id=?", self.id, question.id])
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Returns the number of response groups (count of group responses enterted) for this question group
|
|
165
|
+
def count_group_responses(group)
|
|
166
|
+
counts = []
|
|
167
|
+
group.questions.each do |question|
|
|
168
|
+
counts << Response.count("response_group",:conditions => ["response_set_id =? AND question_id=? AND response_group IS NOT NULL", self.id, question.id], :distinct => true)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
counts.max #since response groups can be partially filled, such that for one response group the user may have answered only one of the questions in the group. We want to still count the partially complete group response.
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
class Survey < ActiveRecord::Base
|
|
2
|
+
include TinyCode
|
|
3
|
+
|
|
4
|
+
# Associations
|
|
5
|
+
has_many :sections, :class_name => "SurveySection", :order => 'display_order'
|
|
6
|
+
has_many :sections_with_questions, :include => :questions, :class_name => "SurveySection", :order => 'display_order'
|
|
7
|
+
has_many :response_sets
|
|
8
|
+
|
|
9
|
+
# Validations
|
|
10
|
+
validates_presence_of :title
|
|
11
|
+
|
|
12
|
+
# Class methods
|
|
13
|
+
def self.to_normalized_string(value)
|
|
14
|
+
# replace non-alphanumeric with "-". remove repeat "-"s. don't start or end with "-"
|
|
15
|
+
value.to_s.downcase.gsub(/[^a-z0-9]/,"-").gsub(/-+/,"-").gsub(/-$|^-/,"")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Instance methods
|
|
19
|
+
def initialize(*args)
|
|
20
|
+
super(*args)
|
|
21
|
+
default_args
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def default_args
|
|
25
|
+
self.inactive_at ||= DateTime.now
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def title=(value)
|
|
29
|
+
self.access_code = Survey.to_normalized_string(value)
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def active?
|
|
34
|
+
self.active_as_of?(DateTime.now)
|
|
35
|
+
end
|
|
36
|
+
def active_as_of?(datetime)
|
|
37
|
+
(self.active_at.nil? or self.active_at < datetime) and (self.inactive_at.nil? or self.inactive_at > datetime)
|
|
38
|
+
end
|
|
39
|
+
def activate!
|
|
40
|
+
self.active_at = DateTime.now
|
|
41
|
+
end
|
|
42
|
+
def deactivate!
|
|
43
|
+
self.inactive_at = DateTime.now
|
|
44
|
+
end
|
|
45
|
+
def active_at=(datetime)
|
|
46
|
+
self.inactive_at = nil if !datetime.nil? and !self.inactive_at.nil? and self.inactive_at < datetime
|
|
47
|
+
super(datetime)
|
|
48
|
+
end
|
|
49
|
+
def inactive_at=(datetime)
|
|
50
|
+
self.active_at = nil if !datetime.nil? and !self.active_at.nil? and self.active_at > datetime
|
|
51
|
+
super(datetime)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|