hssc_surveyor 1.4.1.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. data/.gitignore +28 -0
  2. data/.rspec +1 -0
  3. data/CHANGELOG.md +198 -0
  4. data/Gemfile +5 -0
  5. data/Gemfile.rails_version +19 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +186 -0
  8. data/Rakefile +105 -0
  9. data/app/controllers/surveyor_controller.rb +6 -0
  10. data/app/helpers/results_helper.rb +20 -0
  11. data/app/helpers/survey_form_builder.rb +37 -0
  12. data/app/helpers/surveyor_helper.rb +3 -0
  13. data/app/inputs/quiet_input.rb +5 -0
  14. data/app/inputs/surveyor_check_boxes_input.rb +35 -0
  15. data/app/inputs/surveyor_radio_input.rb +18 -0
  16. data/app/models/answer.rb +4 -0
  17. data/app/models/dependency.rb +4 -0
  18. data/app/models/dependency_condition.rb +4 -0
  19. data/app/models/question.rb +4 -0
  20. data/app/models/question_group.rb +5 -0
  21. data/app/models/response.rb +5 -0
  22. data/app/models/response_set.rb +4 -0
  23. data/app/models/survey.rb +4 -0
  24. data/app/models/survey_section.rb +5 -0
  25. data/app/models/survey_section_sweeper.rb +15 -0
  26. data/app/models/survey_translation.rb +5 -0
  27. data/app/models/validation.rb +4 -0
  28. data/app/models/validation_condition.rb +4 -0
  29. data/app/views/layouts/results.html.erb +13 -0
  30. data/app/views/layouts/surveyor_default.html.erb +12 -0
  31. data/app/views/partials/_answer.html.haml +25 -0
  32. data/app/views/partials/_dependents.html.haml +5 -0
  33. data/app/views/partials/_question.html.haml +41 -0
  34. data/app/views/partials/_question_group.html.haml +44 -0
  35. data/app/views/partials/_section.html.haml +12 -0
  36. data/app/views/partials/_section_menu.html.haml +12 -0
  37. data/app/views/surveyor/edit.html.haml +24 -0
  38. data/app/views/surveyor/export.json.rabl +85 -0
  39. data/app/views/surveyor/new.html.haml +24 -0
  40. data/app/views/surveyor/show.html.haml +74 -0
  41. data/app/views/surveyor/show.json.rabl +15 -0
  42. data/ci-exec.sh +56 -0
  43. data/config/routes.rb +8 -0
  44. data/cucumber.yml +10 -0
  45. data/doc/REPRESENTATIONS.md +34 -0
  46. data/doc/api_id_schema.json +7 -0
  47. data/doc/question types.png +0 -0
  48. data/doc/response_set_schema.json +54 -0
  49. data/doc/surveyor question combinations.png +0 -0
  50. data/doc/surveyor reject or delete decision matrix.png +0 -0
  51. data/doc/surveyor_models.png +0 -0
  52. data/doc/surveyor_models2.png +0 -0
  53. data/doc/surveyor_timestamp_schema.json +9 -0
  54. data/features/ajax_submissions.feature +140 -0
  55. data/features/export_to_json.feature +344 -0
  56. data/features/internationalization.feature +121 -0
  57. data/features/no_duplicates.feature +110 -0
  58. data/features/show_survey.feature +71 -0
  59. data/features/step_definitions/parser_steps.rb +145 -0
  60. data/features/step_definitions/surveyor_steps.rb +325 -0
  61. data/features/step_definitions/ui_steps.rb +25 -0
  62. data/features/step_definitions/web_steps.rb +225 -0
  63. data/features/support/REDCapDemoDatabase_DataDictionary.csv +127 -0
  64. data/features/support/database_cleaner.rb +16 -0
  65. data/features/support/env.rb +56 -0
  66. data/features/support/hooks.rb +4 -0
  67. data/features/support/paths.rb +39 -0
  68. data/features/support/redcap_new_headers.csv +1 -0
  69. data/features/support/redcap_siblings.csv +1 -0
  70. data/features/support/redcap_whitespace.csv +1 -0
  71. data/features/support/selectors.rb +39 -0
  72. data/features/support/simultaneous_ajax.rb +101 -0
  73. data/features/support/single_quit_selenium_driver.rb +23 -0
  74. data/features/support/slow_updates.rb +18 -0
  75. data/features/surveyor.feature +895 -0
  76. data/features/surveyor_dependencies.feature +476 -0
  77. data/features/surveyor_parser.feature +504 -0
  78. data/features/z_redcap_parser.feature +62 -0
  79. data/lib/assets/images/surveyor/next.gif +0 -0
  80. data/lib/assets/images/surveyor/prev.gif +0 -0
  81. data/lib/assets/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  82. data/lib/assets/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  83. data/lib/assets/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  84. data/lib/assets/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  85. data/lib/assets/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  86. data/lib/assets/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  87. data/lib/assets/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  88. data/lib/assets/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  89. data/lib/assets/images/ui-icons_222222_256x240.png +0 -0
  90. data/lib/assets/images/ui-icons_2e83ff_256x240.png +0 -0
  91. data/lib/assets/images/ui-icons_454545_256x240.png +0 -0
  92. data/lib/assets/images/ui-icons_888888_256x240.png +0 -0
  93. data/lib/assets/images/ui-icons_cd0a0a_256x240.png +0 -0
  94. data/lib/assets/javascripts/surveyor/jquery-1.9.0.js +9555 -0
  95. data/lib/assets/javascripts/surveyor/jquery-ui-1.10.0.custom.js +14850 -0
  96. data/lib/assets/javascripts/surveyor/jquery-ui-timepicker-addon.js +1919 -0
  97. data/lib/assets/javascripts/surveyor/jquery.maskedinput.js +338 -0
  98. data/lib/assets/javascripts/surveyor/jquery.selectToUISlider.js +240 -0
  99. data/lib/assets/javascripts/surveyor/jquery.surveyor.js +156 -0
  100. data/lib/assets/stylesheets/surveyor/jquery-ui-1.10.0.custom.css +1174 -0
  101. data/lib/assets/stylesheets/surveyor/jquery-ui-timepicker-addon.css +11 -0
  102. data/lib/assets/stylesheets/surveyor/reset.css +48 -0
  103. data/lib/assets/stylesheets/surveyor/results.css +125 -0
  104. data/lib/assets/stylesheets/surveyor/ui.slider.extras.css +110 -0
  105. data/lib/assets/stylesheets/surveyor.sass +132 -0
  106. data/lib/generators/surveyor/custom_generator.rb +18 -0
  107. data/lib/generators/surveyor/install_generator.rb +102 -0
  108. data/lib/generators/surveyor/templates/app/assets/javascripts/surveyor_all.js +6 -0
  109. data/lib/generators/surveyor/templates/app/assets/stylesheets/surveyor_all.css +9 -0
  110. data/lib/generators/surveyor/templates/app/controllers/surveyor_controller.rb +40 -0
  111. data/lib/generators/surveyor/templates/app/views/layouts/surveyor_custom.html.erb +13 -0
  112. data/lib/generators/surveyor/templates/config/locales/surveyor_en.yml +19 -0
  113. data/lib/generators/surveyor/templates/config/locales/surveyor_es.yml +19 -0
  114. data/lib/generators/surveyor/templates/config/locales/surveyor_he.yml +19 -0
  115. data/lib/generators/surveyor/templates/config/locales/surveyor_ko.yml +19 -0
  116. data/lib/generators/surveyor/templates/db/migrate/add_api_id_to_question_groups.rb +10 -0
  117. data/lib/generators/surveyor/templates/db/migrate/add_api_ids.rb +14 -0
  118. data/lib/generators/surveyor/templates/db/migrate/add_api_ids_to_response_sets_and_responses.rb +12 -0
  119. data/lib/generators/surveyor/templates/db/migrate/add_attachment_to_response.rb +5 -0
  120. data/lib/generators/surveyor/templates/db/migrate/add_correct_answer_id_to_questions.rb +10 -0
  121. data/lib/generators/surveyor/templates/db/migrate/add_default_value_to_answers.rb +10 -0
  122. data/lib/generators/surveyor/templates/db/migrate/add_display_order_to_surveys.rb +10 -0
  123. data/lib/generators/surveyor/templates/db/migrate/add_display_type_to_answers.rb +14 -0
  124. data/lib/generators/surveyor/templates/db/migrate/add_index_to_response_sets.rb +10 -0
  125. data/lib/generators/surveyor/templates/db/migrate/add_index_to_surveys.rb +10 -0
  126. data/lib/generators/surveyor/templates/db/migrate/add_input_mask_attributes_to_answer.rb +12 -0
  127. data/lib/generators/surveyor/templates/db/migrate/add_section_id_to_responses.rb +12 -0
  128. data/lib/generators/surveyor/templates/db/migrate/add_unique_index_on_access_code_and_version_in_surveys.rb +10 -0
  129. data/lib/generators/surveyor/templates/db/migrate/add_unique_indicies.rb +18 -0
  130. data/lib/generators/surveyor/templates/db/migrate/add_version_to_surveys.rb +10 -0
  131. data/lib/generators/surveyor/templates/db/migrate/api_ids_must_be_unique.rb +23 -0
  132. data/lib/generators/surveyor/templates/db/migrate/create_answers.rb +38 -0
  133. data/lib/generators/surveyor/templates/db/migrate/create_dependencies.rb +23 -0
  134. data/lib/generators/surveyor/templates/db/migrate/create_dependency_conditions.rb +30 -0
  135. data/lib/generators/surveyor/templates/db/migrate/create_question_groups.rb +28 -0
  136. data/lib/generators/surveyor/templates/db/migrate/create_questions.rb +37 -0
  137. data/lib/generators/surveyor/templates/db/migrate/create_response_sets.rb +23 -0
  138. data/lib/generators/surveyor/templates/db/migrate/create_responses.rb +34 -0
  139. data/lib/generators/surveyor/templates/db/migrate/create_survey_sections.rb +30 -0
  140. data/lib/generators/surveyor/templates/db/migrate/create_survey_translations.rb +19 -0
  141. data/lib/generators/surveyor/templates/db/migrate/create_surveys.rb +32 -0
  142. data/lib/generators/surveyor/templates/db/migrate/create_validation_conditions.rb +33 -0
  143. data/lib/generators/surveyor/templates/db/migrate/create_validations.rb +21 -0
  144. data/lib/generators/surveyor/templates/db/migrate/drop_unique_index_on_access_code_in_surveys.rb +10 -0
  145. data/lib/generators/surveyor/templates/db/migrate/update_blank_api_ids_on_question_group.rb +22 -0
  146. data/lib/generators/surveyor/templates/db/migrate/update_blank_versions_on_surveys.rb +13 -0
  147. data/lib/generators/surveyor/templates/surveys/EXTENDING_SURVEYOR.md +52 -0
  148. data/lib/generators/surveyor/templates/surveys/MODIFYING_SURVEYOR.md +91 -0
  149. data/lib/generators/surveyor/templates/surveys/date_survey.rb +16 -0
  150. data/lib/generators/surveyor/templates/surveys/kitchen_sink_survey.rb +284 -0
  151. data/lib/generators/surveyor/templates/surveys/languages.rb +14 -0
  152. data/lib/generators/surveyor/templates/surveys/quiz.rb +11 -0
  153. data/lib/generators/surveyor/templates/surveys/translations/languages.es.yml +18 -0
  154. data/lib/generators/surveyor/templates/surveys/translations/languages.he.yml +18 -0
  155. data/lib/generators/surveyor/templates/surveys/translations/languages.ko.yml +18 -0
  156. data/lib/generators/surveyor/templates/vendor/assets/stylesheets/custom.sass +5 -0
  157. data/lib/surveyor/acts_as_response.rb +17 -0
  158. data/lib/surveyor/common.rb +59 -0
  159. data/lib/surveyor/engine.rb +10 -0
  160. data/lib/surveyor/helpers/asset_pipeline.rb +13 -0
  161. data/lib/surveyor/helpers/formtastic_custom_input.rb +13 -0
  162. data/lib/surveyor/helpers/surveyor_helper_methods.rb +103 -0
  163. data/lib/surveyor/models/answer_methods.rb +86 -0
  164. data/lib/surveyor/models/dependency_condition_methods.rb +72 -0
  165. data/lib/surveyor/models/dependency_methods.rb +60 -0
  166. data/lib/surveyor/models/question_group_methods.rb +61 -0
  167. data/lib/surveyor/models/question_methods.rb +115 -0
  168. data/lib/surveyor/models/response_methods.rb +132 -0
  169. data/lib/surveyor/models/response_set_methods.rb +196 -0
  170. data/lib/surveyor/models/survey_methods.rb +103 -0
  171. data/lib/surveyor/models/survey_section_methods.rb +56 -0
  172. data/lib/surveyor/models/survey_translation_methods.rb +33 -0
  173. data/lib/surveyor/models/validation_condition_methods.rb +56 -0
  174. data/lib/surveyor/models/validation_methods.rb +48 -0
  175. data/lib/surveyor/mustache_context.rb +11 -0
  176. data/lib/surveyor/parser.rb +428 -0
  177. data/lib/surveyor/redcap_parser.rb +289 -0
  178. data/lib/surveyor/surveyor_controller_methods.rb +237 -0
  179. data/lib/surveyor/unparser.rb +147 -0
  180. data/lib/surveyor/version.rb +3 -0
  181. data/lib/surveyor.rb +13 -0
  182. data/lib/tasks/surveyor_tasks.rake +88 -0
  183. data/rails/init.rb +1 -0
  184. data/spec/controllers/surveyor_controller_spec.rb +307 -0
  185. data/spec/factories.rb +161 -0
  186. data/spec/helpers/formtastic_custom_input_spec.rb +15 -0
  187. data/spec/helpers/surveyor_helper_spec.rb +118 -0
  188. data/spec/lib/benchmark_spec.rb +22 -0
  189. data/spec/lib/chinese_survey.rb +14 -0
  190. data/spec/lib/common_spec.rb +34 -0
  191. data/spec/lib/parser_spec.rb +204 -0
  192. data/spec/lib/rake_kitchen_sink.rb +40 -0
  193. data/spec/lib/redcap_parser_spec.rb +75 -0
  194. data/spec/lib/tasks_spec.rake +26 -0
  195. data/spec/lib/unparser_spec.rb +126 -0
  196. data/spec/models/answer_spec.rb +175 -0
  197. data/spec/models/dependency_condition_spec.rb +439 -0
  198. data/spec/models/dependency_spec.rb +101 -0
  199. data/spec/models/question_group_spec.rb +93 -0
  200. data/spec/models/question_spec.rb +207 -0
  201. data/spec/models/response_set_spec.rb +477 -0
  202. data/spec/models/response_spec.rb +218 -0
  203. data/spec/models/survey_section_spec.rb +85 -0
  204. data/spec/models/survey_spec.rb +191 -0
  205. data/spec/models/validation_condition_spec.rb +113 -0
  206. data/spec/models/validation_spec.rb +74 -0
  207. data/spec/rcov.opts +2 -0
  208. data/spec/spec.opts +4 -0
  209. data/spec/spec_helper.rb +56 -0
  210. data/stacktests.sh +65 -0
  211. data/surveyor.gemspec +45 -0
  212. metadata +657 -0
@@ -0,0 +1,196 @@
1
+ require 'rabl'
2
+ require 'paperclip'
3
+
4
+ module Surveyor
5
+ module Models
6
+ module ResponseSetMethods
7
+ def self.included(base)
8
+ # Associations
9
+ base.send :belongs_to, :survey
10
+ base.send :belongs_to, :user
11
+ base.send :has_many, :responses, :order => "#{Response.quoted_table_name}.created_at ASC", :dependent => :destroy
12
+ base.send :accepts_nested_attributes_for, :responses, :allow_destroy => true
13
+
14
+ @@validations_already_included ||= nil
15
+ unless @@validations_already_included
16
+ # Validations
17
+ base.send :validates_presence_of, :survey_id
18
+ base.send :validates_associated, :responses
19
+ base.send :validates_uniqueness_of, :access_code
20
+
21
+ @@validations_already_included = true
22
+ end
23
+
24
+ # Attributes
25
+ base.send :attr_protected, :completed_at
26
+
27
+ # Whitelisting attributes
28
+ base.send :attr_accessible, :survey, :responses_attributes, :user_id, :survey_id
29
+
30
+ base.send :before_create, :ensure_start_timestamp
31
+ base.send :before_create, :ensure_identifiers
32
+
33
+ # Class methods
34
+ base.instance_eval do
35
+ def has_blank_value?(hash)
36
+ return true if hash["answer_id"].blank?
37
+ return false if (q = Question.find_by_id(hash["question_id"])) and q.pick == "one"
38
+ hash.any?{|k,v| v.is_a?(Array) ? v.all?{|x| x.to_s.blank?} : v.to_s.blank?}
39
+ end
40
+ end
41
+ end
42
+
43
+ def ensure_start_timestamp
44
+ self.started_at ||= Time.now
45
+ end
46
+
47
+ def ensure_identifiers
48
+ self.access_code ||= Surveyor::Common.make_tiny_code
49
+ self.api_id ||= Surveyor::Common.generate_api_id
50
+ end
51
+
52
+ def to_csv(access_code = false, print_header = true)
53
+ qcols = Question.content_columns.map(&:name) - %w(created_at updated_at)
54
+ acols = Answer.content_columns.map(&:name) - %w(created_at updated_at)
55
+ rcols = Response.content_columns.map(&:name)
56
+ result = Surveyor::Common.csv_impl.generate do |csv|
57
+ if print_header
58
+ csv << (access_code ? ["response set access code"] : []) +
59
+ qcols.map{|qcol| "question.#{qcol}"} +
60
+ acols.map{|acol| "answer.#{acol}"} +
61
+ rcols.map{|rcol| "response.#{rcol}"}
62
+ end
63
+ responses.each do |response|
64
+ csv << (access_code ? [self.access_code] : []) +
65
+ qcols.map{|qcol| response.question.send(qcol)} +
66
+ acols.map{|acol| response.answer.send(acol)} +
67
+ rcols.map{|rcol| response.send(rcol)}
68
+ end
69
+ end
70
+ result
71
+ end
72
+
73
+ def as_json(options = nil)
74
+ template_paths = ActionController::Base.view_paths.collect(&:to_path)
75
+ Rabl.render(self, 'surveyor/show.json', :view_path => template_paths, :format => "hash")
76
+ end
77
+
78
+ def complete!
79
+ self.completed_at = Time.now
80
+ end
81
+
82
+ def complete?
83
+ !completed_at.nil?
84
+ end
85
+
86
+ def correct?
87
+ responses.all?(&:correct?)
88
+ end
89
+ def correctness_hash
90
+ { :questions => survey.sections_with_questions.map(&:questions).flatten.compact.size,
91
+ :responses => responses.compact.size,
92
+ :correct => responses.find_all(&:correct?).compact.size
93
+ }
94
+ end
95
+ def mandatory_questions_complete?
96
+ progress_hash[:triggered_mandatory] == progress_hash[:triggered_mandatory_completed]
97
+ end
98
+ def progress_hash
99
+ qs = survey.sections_with_questions.map(&:questions).flatten
100
+ ds = dependencies(qs.map(&:id))
101
+ triggered = qs - ds.select{|d| !d.is_met?(self)}.map(&:question)
102
+ { :questions => qs.compact.size,
103
+ :triggered => triggered.compact.size,
104
+ :triggered_mandatory => triggered.select{|q| q.mandatory?}.compact.size,
105
+ :triggered_mandatory_completed => triggered.select{|q| q.mandatory? and is_answered?(q)}.compact.size
106
+ }
107
+ end
108
+ def is_answered?(question)
109
+ %w(label image).include?(question.display_type) or !is_unanswered?(question)
110
+ end
111
+ def is_unanswered?(question)
112
+ self.responses.detect{|r| r.question_id == question.id}.nil?
113
+ end
114
+ def is_group_unanswered?(group)
115
+ group.questions.any?{|question| is_unanswered?(question)}
116
+ end
117
+
118
+ # Returns the number of response groups (count of group responses enterted) for this question group
119
+ def count_group_responses(questions)
120
+ questions.map { |q|
121
+ responses.select { |r|
122
+ (r.question_id.to_i == q.id.to_i) && !r.response_group.nil?
123
+ }.group_by(&:response_group).size
124
+ }.max
125
+ end
126
+
127
+ def unanswered_dependencies
128
+ unanswered_question_dependencies + unanswered_question_group_dependencies
129
+ end
130
+
131
+ def unanswered_question_dependencies
132
+ dependencies.select{ |d| d.question && self.is_unanswered?(d.question) && d.is_met?(self) }.map(&:question)
133
+ end
134
+
135
+ def unanswered_question_group_dependencies
136
+ dependencies.
137
+ select{ |d| d.question_group && self.is_group_unanswered?(d.question_group) && d.is_met?(self) }.
138
+ map(&:question_group)
139
+ end
140
+
141
+ def all_dependencies(question_ids = nil)
142
+ arr = dependencies(question_ids).partition{|d| d.is_met?(self) }
143
+ {
144
+ :show => arr[0].map{|d| d.question_group_id.nil? ? "q_#{d.question_id}" : "g_#{d.question_group_id}"},
145
+ :hide => arr[1].map{|d| d.question_group_id.nil? ? "q_#{d.question_id}" : "g_#{d.question_group_id}"}
146
+ }
147
+ end
148
+
149
+ # Check existence of responses to questions from a given survey_section
150
+ def no_responses_for_section?(section)
151
+ !responses.any?{|r| r.survey_section_id == section.id}
152
+ end
153
+
154
+ def update_from_ui_hash(ui_hash)
155
+ transaction do
156
+ ui_hash.each do |ord, response_hash|
157
+ api_id = response_hash['api_id']
158
+ fail "api_id missing from response #{ord}" unless api_id
159
+
160
+ existing = Response.where(:api_id => api_id).first
161
+ updateable_attributes = response_hash.reject { |k, v| k == 'api_id' }
162
+
163
+ if self.class.has_blank_value?(response_hash)
164
+ existing.destroy if existing
165
+ elsif existing
166
+ if existing.question_id.to_s != updateable_attributes['question_id']
167
+ fail "Illegal attempt to change question for response #{api_id}."
168
+ end
169
+
170
+ existing.update_attributes(updateable_attributes)
171
+ else
172
+ responses.build(updateable_attributes).tap do |r|
173
+ r.api_id = api_id
174
+ r.save!
175
+ end
176
+ end
177
+
178
+ end
179
+ end
180
+ end
181
+
182
+ protected
183
+
184
+ def dependencies(question_ids = nil)
185
+ question_ids = survey.sections.map(&:questions).flatten.map(&:id) if responses.blank? and question_ids.blank?
186
+ deps = Dependency.all(:include => :dependency_conditions,
187
+ :conditions => {:dependency_conditions => {:question_id => question_ids || responses.map(&:question_id)}})
188
+
189
+ # this is a work around for a bug in active_record in rails 2.3 which incorrectly eager-loads associatins when a
190
+ # condition clause includes an association limiter
191
+ deps.each{|d| d.dependency_conditions.reload}
192
+ deps
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,103 @@
1
+ require 'surveyor/common'
2
+ require 'rabl'
3
+
4
+ module Surveyor
5
+ module Models
6
+ module SurveyMethods
7
+ def self.included(base)
8
+ # Associations
9
+ base.send :has_many, :sections, :class_name => "SurveySection", :order => "#{SurveySection.quoted_table_name}.display_order", :dependent => :destroy
10
+ base.send :has_many, :sections_with_questions, :include => :questions, :class_name => "SurveySection", :order => "#{SurveySection.quoted_table_name}.display_order"
11
+ base.send :has_many, :response_sets
12
+ base.send :has_many, :translations, :class_name => "SurveyTranslation"
13
+
14
+ # Scopes
15
+ base.send :scope, :with_sections, {:include => :sections}
16
+
17
+ @@validations_already_included ||= nil
18
+ unless @@validations_already_included
19
+ # Validations
20
+ base.send :validates_presence_of, :title
21
+ base.send :validates_uniqueness_of, :survey_version, :scope => :access_code, :message => "survey with matching access code and version already exists"
22
+
23
+ @@validations_already_included = true
24
+ end
25
+
26
+ # Whitelisting attributes
27
+ base.send :attr_accessible, :title, :description, :reference_identifier, :data_export_identifier, :common_namespace, :common_identifier, :css_url, :custom_class, :display_order
28
+
29
+ # Derived attributes
30
+ base.send :before_save, :generate_access_code
31
+ base.send :before_save, :increment_version
32
+
33
+ # Class methods
34
+ base.instance_eval do
35
+ def to_normalized_string(value)
36
+ # replace non-alphanumeric with "-". remove repeat "-"s. don't start or end with "-"
37
+ value.to_s.downcase.gsub(/[^a-z0-9]/,"-").gsub(/-+/,"-").gsub(/-$|^-/,"")
38
+ end
39
+ end
40
+ end
41
+
42
+ # Instance methods
43
+ def initialize(*args)
44
+ super(*args)
45
+ default_args
46
+ end
47
+
48
+ def default_args
49
+ self.api_id ||= Surveyor::Common.generate_api_id
50
+ self.display_order ||= Survey.count
51
+ end
52
+
53
+ def active?
54
+ self.active_as_of?(DateTime.now)
55
+ end
56
+ def active_as_of?(date)
57
+ (active_at && active_at < date && (!inactive_at || inactive_at > date)) ? true : false
58
+ end
59
+ def activate!
60
+ self.active_at = DateTime.now
61
+ self.inactive_at = nil
62
+ end
63
+ def deactivate!
64
+ self.inactive_at = DateTime.now
65
+ self.active_at = nil
66
+ end
67
+
68
+ def as_json(options = nil)
69
+ template_paths = ActionController::Base.view_paths.collect(&:to_path)
70
+ Rabl.render(filtered_for_json, 'surveyor/export.json', :view_path => template_paths, :format => "hash")
71
+ end
72
+
73
+ ##
74
+ # A hook that allows the survey object to be modified before it is
75
+ # serialized by the #as_json method.
76
+ def filtered_for_json
77
+ self
78
+ end
79
+
80
+ def default_access_code
81
+ self.class.to_normalized_string(title)
82
+ end
83
+
84
+ def generate_access_code
85
+ self.access_code ||= default_access_code
86
+ end
87
+
88
+ def increment_version
89
+ surveys = self.class.select(:survey_version).where(:access_code => access_code).order("survey_version DESC")
90
+ next_version = surveys.any? ? surveys.first.survey_version.to_i + 1 : 0
91
+
92
+ self.survey_version = next_version
93
+ end
94
+
95
+ def translation(locale_symbol)
96
+ t = self.translations.where(:locale => locale_symbol.to_s).first
97
+ {:title => self.title, :description => self.description}.with_indifferent_access.merge(
98
+ t ? YAML.load(t.translation || "{}").with_indifferent_access : {}
99
+ )
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,56 @@
1
+ module Surveyor
2
+ module Models
3
+ module SurveySectionMethods
4
+ def self.included(base)
5
+ # Associations
6
+ base.send :has_many, :questions, :dependent => :destroy
7
+ base.send :belongs_to, :survey
8
+
9
+ # Scopes
10
+ base.send :scope, :with_includes, { :include => {:questions => [:answers, :question_group, {:dependency => :dependency_conditions}]}}
11
+
12
+ @@validations_already_included ||= nil
13
+ unless @@validations_already_included
14
+ # Validations
15
+ base.send :validates_presence_of, :title, :display_order
16
+ # this causes issues with building and saving
17
+ #, :survey
18
+
19
+ @@validations_already_included = true
20
+ end
21
+
22
+ # Whitelisting attributes
23
+ base.send :attr_accessible, :survey, :survey_id, :title, :description, :reference_identifier, :data_export_identifier, :common_namespace, :common_identifier, :display_order, :custom_class
24
+ end
25
+
26
+ # Instance Methods
27
+ def initialize(*args)
28
+ super(*args)
29
+ default_args
30
+ end
31
+
32
+ def default_args
33
+ self.data_export_identifier ||= Surveyor::Common.normalize(title)
34
+ end
35
+
36
+ def questions_and_groups
37
+ qs = []
38
+ questions.each_with_index.map do |q,i|
39
+ if q.part_of_group?
40
+ if (i+1 >= questions.size) or (q.question_group_id != questions[i+1].question_group_id)
41
+ q.question_group
42
+ end
43
+ else
44
+ q
45
+ end
46
+ end.compact
47
+ end
48
+
49
+ def translation(locale)
50
+ {:title => self.title, :description => self.description}.with_indifferent_access.merge(
51
+ (self.survey.translation(locale)[:survey_sections] || {})[self.reference_identifier] || {}
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,33 @@
1
+ module Surveyor
2
+ module Models
3
+ module SurveyTranslationMethods
4
+ def self.included(base)
5
+ # Associations
6
+ base.send :belongs_to, :survey
7
+
8
+ @@validations_already_included ||= nil
9
+ unless @@validations_already_included
10
+ # Validations
11
+ base.send :validates_presence_of, :locale, :translation
12
+ base.send :validates_uniqueness_of, :locale, :scope => :survey_id
13
+ # this causes issues with building and saving
14
+ #, :survey
15
+
16
+ @@validations_already_included = true
17
+ end
18
+
19
+ # Whitelisting attributes
20
+ base.send :attr_accessible, :survey, :survey_id, :locale, :translation
21
+ end
22
+
23
+ # Instance Methods
24
+ def initialize(*args)
25
+ super(*args)
26
+ default_args
27
+ end
28
+
29
+ def default_args
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,56 @@
1
+ module Surveyor
2
+ module Models
3
+ module ValidationConditionMethods
4
+ def self.included(base)
5
+ # Associations
6
+ base.send :belongs_to, :validation
7
+
8
+ # Scopes
9
+ @@validations_already_included ||= nil
10
+ unless @@validations_already_included
11
+ # Validations
12
+ base.send :validates_presence_of, :operator, :rule_key
13
+ base.send :validates_inclusion_of, :operator, :in => Surveyor::Common::OPERATORS
14
+ base.send :validates_uniqueness_of, :rule_key, :scope => :validation_id
15
+ # this causes issues with building and saving
16
+ # base.send :validates_numericality_of, :validation_id #, :question_id, :answer_id
17
+
18
+ @@validations_already_included = true
19
+ end
20
+
21
+ base.send :include, Surveyor::ActsAsResponse # includes "as" instance method
22
+
23
+ # Whitelisting attributes
24
+ base.send :attr_accessible, :validation, :validation_id, :rule_key, :operator, :question_id, :answer_id, :datetime_value, :integer_value, :float_value, :unit, :text_value, :string_value, :response_other, :regexp
25
+
26
+ # Class methods
27
+ base.instance_eval do
28
+ def operators
29
+ Surveyor::Common::OPERATORS
30
+ end
31
+ end
32
+ end
33
+
34
+ # Instance Methods
35
+ def to_hash(response)
36
+ {rule_key.to_sym => (response.nil? ? false : self.is_valid?(response))}
37
+ end
38
+
39
+ def is_valid?(response)
40
+ klass = response.answer.response_class
41
+ compare_to = Response.find_by_question_id_and_answer_id(self.question_id, self.answer_id) || self
42
+ case self.operator
43
+ when "==", "<", ">", "<=", ">="
44
+ response.as(klass).send(self.operator, compare_to.as(klass))
45
+ when "!="
46
+ !(response.as(klass) == compare_to.as(klass))
47
+ when "=~"
48
+ return false if compare_to != self
49
+ !(response.as(klass).to_s =~ Regexp.new(self.regexp || "")).nil?
50
+ else
51
+ false
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,48 @@
1
+ module Surveyor
2
+ module Models
3
+ module ValidationMethods
4
+ def self.included(base)
5
+ # Associations
6
+ base.send :belongs_to, :answer
7
+ base.send :has_many, :validation_conditions, :dependent => :destroy
8
+
9
+ # Scopes
10
+
11
+ @@validations_already_included ||= nil
12
+ unless @@validations_already_included
13
+ # Validations
14
+ base.send :validates_presence_of, :rule
15
+ base.send :validates_format_of, :rule, :with => /^(?:and|or|\)|\(|[A-Z]|\s)+$/
16
+ # this causes issues with building and saving
17
+ # base.send :validates_numericality_of, :answer_id
18
+
19
+ @@validations_already_included = true
20
+ end
21
+
22
+ # Whitelisting attributes
23
+ base.send :attr_accessible, :answer, :answer_id, :rule, :message
24
+ end
25
+
26
+ # Instance Methods
27
+ def is_valid?(response_set)
28
+ ch = conditions_hash(response_set)
29
+ rgx = Regexp.new(self.validation_conditions.map{|vc| ["a","o"].include?(vc.rule_key) ? "#{vc.rule_key}(?!nd|r)" : vc.rule_key}.join("|")) # exclude and, or
30
+ # logger.debug "v: #{self.inspect}"
31
+ # logger.debug "rule: #{self.rule.inspect}"
32
+ # logger.debug "rexp: #{rgx.inspect}"
33
+ # logger.debug "keyp: #{ch.inspect}"
34
+ # logger.debug "subd: #{self.rule.gsub(rgx){|m| ch[m.to_sym]}}"
35
+ eval(self.rule.gsub(rgx){|m| ch[m.to_sym]})
36
+ end
37
+
38
+ # A hash of the conditions (keyed by rule_key) and their evaluation (boolean) in the context of response_set
39
+ def conditions_hash(response_set)
40
+ hash = {}
41
+ response = response_set.responses.detect{|r| r.answer_id.to_i == self.answer_id.to_i}
42
+ # logger.debug "r: #{response.inspect}"
43
+ self.validation_conditions.each{|vc| hash.merge!(vc.to_hash(response))}
44
+ return hash
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,11 @@
1
+ module Surveyor
2
+ module MustacheContext
3
+ def in_context(text, context=nil)
4
+ case context
5
+ when NilClass then text
6
+ when Hash then Mustache.render(text, context)
7
+ else context.render(text)
8
+ end
9
+ end
10
+ end
11
+ end