surveyor 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. data/.gitignore +6 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +104 -0
  4. data/Rakefile +50 -0
  5. data/VERSION +1 -0
  6. data/app/controllers/surveyor_controller.rb +117 -0
  7. data/app/helpers/survey_form_builder.rb +37 -0
  8. data/app/helpers/surveyor_helper.rb +66 -0
  9. data/app/models/answer.rb +21 -0
  10. data/app/models/dependency.rb +46 -0
  11. data/app/models/dependency_condition.rb +79 -0
  12. data/app/models/question.rb +54 -0
  13. data/app/models/question_group.rb +18 -0
  14. data/app/models/response.rb +47 -0
  15. data/app/models/response_set.rb +185 -0
  16. data/app/models/survey.rb +57 -0
  17. data/app/models/survey_section.rb +15 -0
  18. data/app/views/layouts/surveyor_default.html.erb +13 -0
  19. data/app/views/partials/_answer.html.haml +49 -0
  20. data/app/views/partials/_question.html.haml +73 -0
  21. data/app/views/partials/_question_group.html.haml +41 -0
  22. data/app/views/surveyor/edit.html.haml +32 -0
  23. data/app/views/surveyor/new.html.haml +17 -0
  24. data/app/views/surveyor/show.html.haml +12 -0
  25. data/config/routes.rb +10 -0
  26. data/generators/surveyor/EXTENDING_SURVEYOR +12 -0
  27. data/generators/surveyor/extend_surveyor_generator.rb +22 -0
  28. data/generators/surveyor/surveyor_generator.rb +60 -0
  29. data/generators/surveyor/templates/README +10 -0
  30. data/generators/surveyor/templates/assets/images/222222_11x11_icon_arrows_leftright.gif +0 -0
  31. data/generators/surveyor/templates/assets/images/222222_11x11_icon_arrows_updown.gif +0 -0
  32. data/generators/surveyor/templates/assets/images/222222_11x11_icon_close.gif +0 -0
  33. data/generators/surveyor/templates/assets/images/222222_11x11_icon_doc.gif +0 -0
  34. data/generators/surveyor/templates/assets/images/222222_11x11_icon_folder_closed.gif +0 -0
  35. data/generators/surveyor/templates/assets/images/222222_11x11_icon_folder_open.gif +0 -0
  36. data/generators/surveyor/templates/assets/images/222222_11x11_icon_minus.gif +0 -0
  37. data/generators/surveyor/templates/assets/images/222222_11x11_icon_plus.gif +0 -0
  38. data/generators/surveyor/templates/assets/images/222222_11x11_icon_resize_se.gif +0 -0
  39. data/generators/surveyor/templates/assets/images/222222_35x9_colorpicker_indicator.gif.gif +0 -0
  40. data/generators/surveyor/templates/assets/images/222222_7x7_arrow_down.gif +0 -0
  41. data/generators/surveyor/templates/assets/images/222222_7x7_arrow_left.gif +0 -0
  42. data/generators/surveyor/templates/assets/images/222222_7x7_arrow_right.gif +0 -0
  43. data/generators/surveyor/templates/assets/images/222222_7x7_arrow_up.gif +0 -0
  44. data/generators/surveyor/templates/assets/images/454545_11x11_icon_arrows_leftright.gif +0 -0
  45. data/generators/surveyor/templates/assets/images/454545_11x11_icon_arrows_updown.gif +0 -0
  46. data/generators/surveyor/templates/assets/images/454545_11x11_icon_close.gif +0 -0
  47. data/generators/surveyor/templates/assets/images/454545_11x11_icon_doc.gif +0 -0
  48. data/generators/surveyor/templates/assets/images/454545_11x11_icon_folder_closed.gif +0 -0
  49. data/generators/surveyor/templates/assets/images/454545_11x11_icon_folder_open.gif +0 -0
  50. data/generators/surveyor/templates/assets/images/454545_11x11_icon_minus.gif +0 -0
  51. data/generators/surveyor/templates/assets/images/454545_11x11_icon_plus.gif +0 -0
  52. data/generators/surveyor/templates/assets/images/454545_7x7_arrow_down.gif +0 -0
  53. data/generators/surveyor/templates/assets/images/454545_7x7_arrow_left.gif +0 -0
  54. data/generators/surveyor/templates/assets/images/454545_7x7_arrow_right.gif +0 -0
  55. data/generators/surveyor/templates/assets/images/454545_7x7_arrow_up.gif +0 -0
  56. data/generators/surveyor/templates/assets/images/888888_11x11_icon_arrows_leftright.gif +0 -0
  57. data/generators/surveyor/templates/assets/images/888888_11x11_icon_arrows_updown.gif +0 -0
  58. data/generators/surveyor/templates/assets/images/888888_11x11_icon_close.gif +0 -0
  59. data/generators/surveyor/templates/assets/images/888888_11x11_icon_doc.gif +0 -0
  60. data/generators/surveyor/templates/assets/images/888888_11x11_icon_folder_closed.gif +0 -0
  61. data/generators/surveyor/templates/assets/images/888888_11x11_icon_folder_open.gif +0 -0
  62. data/generators/surveyor/templates/assets/images/888888_11x11_icon_minus.gif +0 -0
  63. data/generators/surveyor/templates/assets/images/888888_11x11_icon_plus.gif +0 -0
  64. data/generators/surveyor/templates/assets/images/888888_7x7_arrow_down.gif +0 -0
  65. data/generators/surveyor/templates/assets/images/888888_7x7_arrow_left.gif +0 -0
  66. data/generators/surveyor/templates/assets/images/888888_7x7_arrow_right.gif +0 -0
  67. data/generators/surveyor/templates/assets/images/888888_7x7_arrow_up.gif +0 -0
  68. data/generators/surveyor/templates/assets/images/dadada_40x100_textures_02_glass_75.png +0 -0
  69. data/generators/surveyor/templates/assets/images/e6e6e6_40x100_textures_02_glass_75.png +0 -0
  70. data/generators/surveyor/templates/assets/images/ffffff_40x100_textures_01_flat_0.png +0 -0
  71. data/generators/surveyor/templates/assets/images/ffffff_40x100_textures_02_glass_65.png +0 -0
  72. data/generators/surveyor/templates/assets/javascripts/accessibleUISlider.jQuery.js +201 -0
  73. data/generators/surveyor/templates/assets/javascripts/jquery-1.2.6.js +3549 -0
  74. data/generators/surveyor/templates/assets/javascripts/jquery-ui-personalized-1.5.3.js +7616 -0
  75. data/generators/surveyor/templates/assets/javascripts/jquery.form.js +637 -0
  76. data/generators/surveyor/templates/assets/javascripts/surveyor.js +28 -0
  77. data/generators/surveyor/templates/assets/stylesheets/jquery-ui-slider-additions.css +71 -0
  78. data/generators/surveyor/templates/assets/stylesheets/reset.css +46 -0
  79. data/generators/surveyor/templates/assets/stylesheets/sass/surveyor.sass +245 -0
  80. data/generators/surveyor/templates/assets/stylesheets/surveyor.css +231 -0
  81. data/generators/surveyor/templates/assets/stylesheets/ui.theme.css +851 -0
  82. data/generators/surveyor/templates/extensions/survey_extensions.rb +18 -0
  83. data/generators/surveyor/templates/extensions/surveyor_controller_extensions.rb +28 -0
  84. data/generators/surveyor/templates/extensions/surveyor_custom.html.erb +13 -0
  85. data/generators/surveyor/templates/extensions/surveyor_helper_extensions.rb +18 -0
  86. data/generators/surveyor/templates/initializers/haml.rb +8 -0
  87. data/generators/surveyor/templates/initializers/surveyor.rb +11 -0
  88. data/generators/surveyor/templates/migrate/create_answers.rb +45 -0
  89. data/generators/surveyor/templates/migrate/create_dependencies.rb +21 -0
  90. data/generators/surveyor/templates/migrate/create_dependency_conditions.rb +29 -0
  91. data/generators/surveyor/templates/migrate/create_question_groups.rb +18 -0
  92. data/generators/surveyor/templates/migrate/create_questions.rb +33 -0
  93. data/generators/surveyor/templates/migrate/create_response_sets.rb +22 -0
  94. data/generators/surveyor/templates/migrate/create_responses.rb +33 -0
  95. data/generators/surveyor/templates/migrate/create_survey_sections.rb +25 -0
  96. data/generators/surveyor/templates/migrate/create_surveys.rb +25 -0
  97. data/generators/surveyor/templates/surveys/kitchen_sink_survey.rb +201 -0
  98. data/generators/surveyor/templates/tasks/surveyor.rb +4 -0
  99. data/init.rb +1 -0
  100. data/install.rb +1 -0
  101. data/lib/surveyor.rb +7 -0
  102. data/lib/surveyor/config.rb +45 -0
  103. data/lib/tasks/surveyor_tasks.rake +33 -0
  104. data/lib/tiny_code.rb +58 -0
  105. data/lib/xml_formatter.rb +12 -0
  106. data/script/surveyor/answer.rb +84 -0
  107. data/script/surveyor/columnizer.rb +36 -0
  108. data/script/surveyor/dependency.rb +43 -0
  109. data/script/surveyor/dependency_condition.rb +74 -0
  110. data/script/surveyor/question.rb +76 -0
  111. data/script/surveyor/question_group.rb +33 -0
  112. data/script/surveyor/specs/answer_spec.rb +66 -0
  113. data/script/surveyor/specs/question_dependency_spec.rb +46 -0
  114. data/script/surveyor/specs/question_group_spec.rb +9 -0
  115. data/script/surveyor/specs/question_spec.rb +111 -0
  116. data/script/surveyor/specs/section_spec.rb +58 -0
  117. data/script/surveyor/survey.rb +108 -0
  118. data/script/surveyor/survey_parser.rb +64 -0
  119. data/script/surveyor/survey_section.rb +153 -0
  120. data/script/surveyor/whr_dsl.tmproj +244 -0
  121. data/spec/controllers/surveyor_controller_spec.rb +328 -0
  122. data/spec/factories.rb +107 -0
  123. data/spec/models/answer_spec.rb +29 -0
  124. data/spec/models/dependency_condition_spec.rb +338 -0
  125. data/spec/models/dependency_spec.rb +82 -0
  126. data/spec/models/question_group_spec.rb +11 -0
  127. data/spec/models/question_spec.rb +75 -0
  128. data/spec/models/response_set_spec.rb +153 -0
  129. data/spec/models/response_spec.rb +94 -0
  130. data/spec/models/survey_section_spec.rb +34 -0
  131. data/spec/models/survey_spec.rb +72 -0
  132. data/spec/rcov.opts +2 -0
  133. data/spec/spec.opts +4 -0
  134. data/spec/spec_helper.rb +14 -0
  135. data/surveyor.gemspec +187 -0
  136. data/uninstall.rb +1 -0
  137. metadata +211 -0
@@ -0,0 +1,79 @@
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
+ # Instance methods
22
+
23
+ # The hash used in the dependency parent object to evaluate its rule string
24
+ def to_evaluation_hash(response_set)
25
+ x = {rule_key.to_sym => self.evaluation_of(response_set)}
26
+ # logger.warn "to_evaluation_hash #{x.inspect}"
27
+ return x
28
+ end
29
+
30
+ # # Evaluates the condition on the response_set
31
+ # def evaluation_of(response_set)
32
+ # response = response_set.responses.detect{|r| r.answer_id == answer_id} || false # turns out eval("nil and false") => nil so we need to return false if no response is found
33
+ # return(response and self.is_satisfied_by?(response))
34
+ # end
35
+ # Evaluates the condition on the response_set
36
+ def evaluation_of(response_set)
37
+ # 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
38
+ response = response_set.responses.detect{|r| r.answer_id.to_i == self.answer_id.to_i} || false # turns out eval("nil and false") => nil so we need to return false if no response is found
39
+ # logger.warn "evaluation_of_response #{response.inspect}"
40
+ return(response and self.is_satisfied_by?(response))
41
+ end
42
+
43
+ # Checks to see if the response passed in satisfies the dependency condition
44
+ def is_satisfied_by?(response)
45
+ response_class = response.answer.response_class
46
+ # response.as(response_class).send(operator.to_sym, self.as(response_class))
47
+ return case self.operator
48
+ when "=="
49
+ response.as(response_class) == self.as(response_class)
50
+ when "!="
51
+ response.as(response_class) != self.as(response_class)
52
+ when "<"
53
+ response.as(response_class) < self.as(response_class)
54
+ when ">"
55
+ response.as(response_class) > self.as(response_class)
56
+ when "<="
57
+ response.as(response_class) <= self.as(response_class)
58
+ when ">="
59
+ response.as(response_class) >= self.as(response_class)
60
+ else
61
+ false
62
+ end
63
+ end
64
+
65
+ # Method that returns the dependency as a particular response_class type
66
+ def as(type_symbol)
67
+ return case type_symbol.to_sym
68
+ when :string, :text, :integer, :float, :datetime
69
+ self.send("#{type_symbol}_value".to_sym)
70
+ when :date
71
+ self.datetime_value.nil? ? nil : self.datetime_value.to_date
72
+ when :time
73
+ self.datetime_value.nil? ? nil : self.datetime_value.to_time
74
+ else # :answer_id
75
+ self.answer_id
76
+ end
77
+ end
78
+
79
+ end
@@ -0,0 +1,54 @@
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
+ # Scopes
10
+ default_scope :order => "display_order ASC"
11
+
12
+ # Validations
13
+ validates_presence_of :text, :survey_section_id, :display_order
14
+ validates_inclusion_of :is_mandatory, :in => [true, false]
15
+
16
+ # Instance Methods
17
+ def initialize(*args)
18
+ super(*args)
19
+ default_args
20
+ end
21
+
22
+ def default_args
23
+ self.is_mandatory ||= true
24
+ self.display_type = "default"
25
+ self.pick = "none"
26
+ end
27
+
28
+ def mandatory?
29
+ self.is_mandatory == true
30
+ end
31
+
32
+ def dependent?
33
+ self.dependency != nil
34
+ end
35
+ def triggered?(response_set)
36
+ dependent? ? self.dependency.met?(response_set) : true
37
+ end
38
+ def dep_class(response_set)
39
+ dependent? ? triggered?(response_set) ? "dependent" : "hidden dependent" : nil
40
+ end
41
+ def dependency_satisfied?(response_set)
42
+ self.has_dependency? and self.dependency.met?(response_set)
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,18 @@
1
+ class QuestionGroup < ActiveRecord::Base
2
+
3
+ has_many :questions
4
+
5
+ # Instance Methods
6
+ def initialize(*args)
7
+ super(*args)
8
+ default_args
9
+ end
10
+
11
+ def default_args
12
+ self.display_type = "inline"
13
+ end
14
+
15
+ def renderer
16
+ display_type.blank? ? :default : display_type.to_sym
17
+ end
18
+ end
@@ -0,0 +1,47 @@
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, :text, :integer, :float, :datetime
29
+ self.send("#{type_symbol}_value".to_sym)
30
+ when :date
31
+ self.datetime_value.nil? ? nil : self.datetime_value.to_date
32
+ when :time
33
+ self.datetime_value.nil? ? nil : self.datetime_value.to_time
34
+ else # :answer_id
35
+ self.answer_id
36
+ end
37
+ end
38
+
39
+ def to_s
40
+ if self.answer.response_class == "answer" and self.answer_id
41
+ return self.answer.text
42
+ else
43
+ return "#{(self.string_value || self.text_value || self.integer_value || self.float_value || nil).to_s}"
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,185 @@
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
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, group = nil)
32
+ response_group = group.nil? ? nil : group.to_s # for PostgreSql 8.3 compatibility - response_group is a string
33
+ # found = responses.find_by_question_id_and_answer_id_and_response_group(question_id, answer_id, response_group)
34
+ found = responses.detect{|r| r.question_id == question_id && r.answer_id == answer_id && r.response_group == response_group}
35
+ found.blank? ? responses.new(:question_id => question_id, :answer_id => answer_id, :response_group => response_group) : found
36
+ end
37
+
38
+ def clear_responses
39
+ question_ids = Question.find_all_by_survey_section_id(current_section_id).map(&:id)
40
+ # logger.warn "responses: #{responses.inspect}\n"
41
+ responses.select{|r| question_ids.include? r.question_id}.map(&:destroy)
42
+ responses.reload
43
+ # logger.warn "responses: #{responses.inspect}\n"
44
+ end
45
+
46
+ # "responses"=>{
47
+ #string "6"=>{"question_id"=>"6", "20"=>{"string_value"=>"saf"}},
48
+ #text "7"=>{"question_id"=>"7", "21"=>{"text_value"=>""}},
49
+ #radio+txt "1"=>{"question_id"=>"1", "answer_id"=>"1", "4"=>{"string_value"=>""}},
50
+ #radio "2"=>{"answer_id"=>"6"},
51
+ #radio "3"=>{"answer_id"=>"10"},
52
+ #check "4"=>{"question_id"=>"4", "answer_id"=>"15"},
53
+ #check+txt "5"=>{"question_id"=>"5", "16"=>{"selected"=>"1"}, "19"=>{"string_value"=>""}}
54
+ # },
55
+ # "survey_code"=>"test_survey",
56
+ # "commit"=>"Next Section (Utensiles and you!) >>",
57
+ # "authenticity_token"=>"8bee21081eea820ab1c658358c0baaa2e46de5d1",
58
+ # "_method"=>"put",
59
+ # "action"=>"update",
60
+ # "controller"=>"app",
61
+ # "response_set_code"=>"T2x8HhCQej",
62
+ # "section"=>"2"
63
+
64
+ def response_attributes=(response_attributes)
65
+ response_attributes.each do |question_id, responses_hash|
66
+ # Response.delete_all(["response_set_id =? AND question_id =?", self.id, question_id])
67
+ if (answer_id = responses_hash[:answer_id])
68
+ if (!responses_hash[:answer_id].empty?) # Dropdowns return answer id but have an empty value if they are not set... ignoring those.
69
+ #radio or dropdown - only one response
70
+ responses.build({:question_id => question_id, :answer_id => answer_id}.merge(responses_hash[answer_id] || {}))
71
+ end
72
+ else
73
+ #possibly multiples responses - unresponded radios end up here too
74
+ # we use the variable question_id, not the "question_id" in the response_hash
75
+ responses_hash.delete_if{|k,v| k == "question_id"}.each do |answer_id, response_hash|
76
+ unless response_hash.delete_if{|k,v| v.blank?}.empty?
77
+ responses.build({:question_id => question_id, :answer_id => answer_id}.merge(response_hash))
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ # "24"=>{
85
+ # "0"=>{"response_group"=>"0", "question_id"=>"24", "answer_id"=>"172"}, "1"=>{"response_group"=>"1", "question_id"=>"24", "answer_id"=>"173"},
86
+ # "2"=>{"response_group"=>"2", "question_id"=>"24", "answer_id"=>""}, "3"=>{"response_group"=>"3", "question_id"=>"24", "answer_id"=>""},
87
+ # "4"=>{"response_group"=>"4", "question_id"=>"24", "answer_id"=>""}},
88
+ # where "24" is the question id
89
+
90
+ # Some other examples:
91
+ # "25"=>{
92
+ # "0"=>{"response_group"=>"0", "question_id"=>"25", "179"=>{"string_value"=>"camry"}},
93
+ # "1"=>{"response_group"=>"1", "question_id"=>"25", "179"=>{"string_value"=>"f150"}},
94
+ # "2"=>{"response_group"=>"2", "question_id"=>"25", "179"=>{"string_value"=>""}},
95
+ # "3"=>{"response_group"=>"3", "question_id"=>"25", "179"=>{"string_value"=>""}},
96
+ # "4"=>{"response_group"=>"4", "question_id"=>"25", "179"=>{"string_value"=>""}}},
97
+ #
98
+ # "26"=>{
99
+ # "0"=>{"response_group"=>"0", "question_id"=>"26", "180"=>{"string_value"=>"1999"}},
100
+ # "1"=>{"response_group"=>"1", "question_id"=>"26", "180"=>{"string_value"=>"2004"}},
101
+ # "2"=>{"response_group"=>"2", "question_id"=>"26", "180"=>{"string_value"=>""}},
102
+ # "3"=>{"response_group"=>"3", "question_id"=>"26", "180"=>{"string_value"=>""}},
103
+ # "4"=>{"response_group"=>"4", "question_id"=>"26", "180"=>{"string_value"=>""}}},
104
+ #
105
+ # "27"=>{
106
+ # "0"=>{"182"=>{"integer_value"=>""}, "response_group"=>"0", "question_id"=>"27", "181"=>{"string_value"=>""}},
107
+ # "1"=>{"182"=>{"integer_value"=>""}, "response_group"=>"1", "question_id"=>"27", "181"=>{"string_value"=>""}},
108
+ # "2"=>{"182"=>{"integer_value"=>""}, "response_group"=>"2", "question_id"=>"27", "181"=>{"string_value"=>""}},
109
+ # "3"=>{"182"=>{"integer_value"=>""}, "response_group"=>"3", "question_id"=>"27", "181"=>{"string_value"=>""}},
110
+ # "4"=>{"182"=>{"integer_value"=>""}, "response_group"=>"4", "question_id"=>"27", "181"=>{"string_value"=>""}}}},
111
+
112
+ # 0,1,2,3,4 are the response group numbers
113
+ # and anything else in the response group hash is handled normally
114
+
115
+ # method to process responses in response groups
116
+ def response_group_attributes=(response_attributes)
117
+ response_attributes.each do |question_id, responses_group_hash|
118
+ # Response.delete_all(["response_set_id =? AND question_id =?", self.id, question_id])
119
+ responses_group_hash.each do |response_group_number, group_hash|
120
+ if (answer_id = group_hash[:answer_id]) # if group_hash has an answer_id key we treat it differently
121
+ if (!group_hash[:answer_id].empty?) # dropdowns return empty values in answer_ids if they are not selected
122
+ #radio or dropdown - only one response
123
+ responses.build({:question_id => question_id, :answer_id => answer_id, :response_group => response_group_number}.merge(group_hash[answer_id] || {}))
124
+ end
125
+ else
126
+ #possibly multiples responses - unresponded radios end up here too
127
+ # we use the variable question_id in the key, not the "question_id" in the response_hash... same with response_group key
128
+ group_hash.delete_if{|k,v| (k == "question_id") or (k == "response_group")}.each do |answer_id, inner_hash|
129
+ unless inner_hash.delete_if{|k,v| v.blank?}.empty?
130
+ responses.build({:question_id => question_id, :answer_id => answer_id, :response_group => response_group_number}.merge(inner_hash))
131
+ end
132
+ end
133
+ end
134
+
135
+ end
136
+ end
137
+ end
138
+
139
+ def find_response(answer_id)
140
+ self.responses.find_by_answer_id(answer_id)
141
+ end
142
+
143
+ def save_responses
144
+ responses.each do |response|
145
+ response.save(false)
146
+ end
147
+ end
148
+
149
+ # ResponseSet has an awareness of its internal state
150
+ def empty?
151
+ self.responses.empty?
152
+ end
153
+
154
+ def complete!
155
+ self.completed_at = Time.now
156
+ end
157
+
158
+ def has_answered_question?(question)
159
+ !has_not_answered_question?(question)
160
+ end
161
+
162
+ def has_not_answered_question?(question)
163
+ self.responses.find_all_by_question_id(question.id).empty?
164
+ end
165
+
166
+ # Returns the number of response groups (count of group responses enterted) for this question group
167
+ def count_group_responses(questions)
168
+ questions.map{|q| responses.select{|r| r.question_id == q.id && !r.response_group.nil?}.size }.max
169
+ end
170
+
171
+ def unanswered_dependencies
172
+ dependencies.select{|d| d.met?(self) and self.has_not_answered_question?(d.question)}.map(&:question)
173
+ end
174
+ def all_dependencies
175
+ arr = dependencies.partition{|d| d.met?(self) }
176
+ {:show => arr[0].map{|d| "question_#{d.question_id}"}, :hide => arr[1].map{|d| "question_#{d.question_id}"}}
177
+ end
178
+
179
+ protected
180
+
181
+ def dependencies
182
+ question_ids = Question.find_all_by_survey_section_id(current_section_id).map(&:id)
183
+ Dependency.depending_on_questions(question_ids)
184
+ end
185
+ end
@@ -0,0 +1,57 @@
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
+ # Scopes
10
+ named_scope :with_sections, {:include => :sections}
11
+
12
+ # Validations
13
+ validates_presence_of :title
14
+
15
+ # Class methods
16
+ def self.to_normalized_string(value)
17
+ # replace non-alphanumeric with "-". remove repeat "-"s. don't start or end with "-"
18
+ value.to_s.downcase.gsub(/[^a-z0-9]/,"-").gsub(/-+/,"-").gsub(/-$|^-/,"")
19
+ end
20
+
21
+ # Instance methods
22
+ def initialize(*args)
23
+ super(*args)
24
+ default_args
25
+ end
26
+
27
+ def default_args
28
+ self.inactive_at ||= DateTime.now
29
+ end
30
+
31
+ def title=(value)
32
+ self.access_code = Survey.to_normalized_string(value)
33
+ super
34
+ end
35
+
36
+ def active?
37
+ self.active_as_of?(DateTime.now)
38
+ end
39
+ def active_as_of?(datetime)
40
+ (self.active_at.nil? or self.active_at < datetime) and (self.inactive_at.nil? or self.inactive_at > datetime)
41
+ end
42
+ def activate!
43
+ self.active_at = DateTime.now
44
+ end
45
+ def deactivate!
46
+ self.inactive_at = DateTime.now
47
+ end
48
+ def active_at=(datetime)
49
+ self.inactive_at = nil if !datetime.nil? and !self.inactive_at.nil? and self.inactive_at < datetime
50
+ super(datetime)
51
+ end
52
+ def inactive_at=(datetime)
53
+ self.active_at = nil if !datetime.nil? and !self.active_at.nil? and self.active_at > datetime
54
+ super(datetime)
55
+ end
56
+
57
+ end
@@ -0,0 +1,15 @@
1
+ class SurveySection < ActiveRecord::Base
2
+
3
+ # Associations
4
+ has_many :questions
5
+ belongs_to :survey
6
+
7
+ # Scopes
8
+ default_scope :order => "display_order ASC"
9
+ named_scope :with_includes, { :include => {:questions => [:answers, :question_group, {:dependency => :dependency_conditions}]}}
10
+
11
+ # Validations
12
+ validates_presence_of :title, :survey, :display_order
13
+
14
+ end
15
+