affectiva-surveyor 1.5.0.pre.disco.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (212) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +28 -0
  3. data/.rspec +1 -0
  4. data/CHANGELOG.md +198 -0
  5. data/Gemfile +5 -0
  6. data/Gemfile.rails_version +27 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +166 -0
  9. data/Rakefile +106 -0
  10. data/app/controllers/surveyor_controller.rb +5 -0
  11. data/app/helpers/results_helper.rb +20 -0
  12. data/app/helpers/survey_form_builder.rb +37 -0
  13. data/app/helpers/surveyor_helper.rb +3 -0
  14. data/app/inputs/quiet_input.rb +5 -0
  15. data/app/inputs/surveyor_check_boxes_input.rb +35 -0
  16. data/app/inputs/surveyor_radio_input.rb +18 -0
  17. data/app/models/answer.rb +3 -0
  18. data/app/models/dependency.rb +3 -0
  19. data/app/models/dependency_condition.rb +3 -0
  20. data/app/models/question.rb +3 -0
  21. data/app/models/question_group.rb +4 -0
  22. data/app/models/response.rb +4 -0
  23. data/app/models/response_set.rb +3 -0
  24. data/app/models/survey.rb +3 -0
  25. data/app/models/survey_section.rb +4 -0
  26. data/app/models/survey_section_sweeper.rb +15 -0
  27. data/app/models/survey_translation.rb +4 -0
  28. data/app/models/validation.rb +3 -0
  29. data/app/models/validation_condition.rb +3 -0
  30. data/app/views/layouts/results.html.erb +13 -0
  31. data/app/views/layouts/surveyor_default.html.erb +12 -0
  32. data/app/views/partials/_answer.html.haml +25 -0
  33. data/app/views/partials/_dependents.html.haml +5 -0
  34. data/app/views/partials/_question.html.haml +28 -0
  35. data/app/views/partials/_question_group.html.haml +44 -0
  36. data/app/views/partials/_section.html.haml +12 -0
  37. data/app/views/partials/_section_menu.html.haml +12 -0
  38. data/app/views/surveyor/edit.html.haml +24 -0
  39. data/app/views/surveyor/export.json.rabl +85 -0
  40. data/app/views/surveyor/new.html.haml +24 -0
  41. data/app/views/surveyor/show.html.haml +74 -0
  42. data/app/views/surveyor/show.json.rabl +15 -0
  43. data/ci-exec.sh +56 -0
  44. data/config/routes.rb +8 -0
  45. data/cucumber.yml +10 -0
  46. data/doc/REPRESENTATIONS.md +34 -0
  47. data/doc/api_id_schema.json +7 -0
  48. data/doc/question types.png +0 -0
  49. data/doc/response_set_schema.json +54 -0
  50. data/doc/surveyor question combinations.png +0 -0
  51. data/doc/surveyor reject or delete decision matrix.png +0 -0
  52. data/doc/surveyor_models.png +0 -0
  53. data/doc/surveyor_models2.png +0 -0
  54. data/doc/surveyor_timestamp_schema.json +9 -0
  55. data/features/ajax_submissions.feature +140 -0
  56. data/features/export_to_json.feature +344 -0
  57. data/features/internationalization.feature +121 -0
  58. data/features/no_duplicates.feature +110 -0
  59. data/features/show_survey.feature +71 -0
  60. data/features/step_definitions/parser_steps.rb +145 -0
  61. data/features/step_definitions/surveyor_steps.rb +325 -0
  62. data/features/step_definitions/ui_steps.rb +25 -0
  63. data/features/step_definitions/web_steps.rb +225 -0
  64. data/features/support/REDCapDemoDatabase_DataDictionary.csv +127 -0
  65. data/features/support/database_cleaner.rb +16 -0
  66. data/features/support/env.rb +56 -0
  67. data/features/support/hooks.rb +4 -0
  68. data/features/support/paths.rb +39 -0
  69. data/features/support/redcap_new_headers.csv +1 -0
  70. data/features/support/redcap_siblings.csv +1 -0
  71. data/features/support/redcap_whitespace.csv +1 -0
  72. data/features/support/selectors.rb +39 -0
  73. data/features/support/simultaneous_ajax.rb +101 -0
  74. data/features/support/single_quit_selenium_driver.rb +23 -0
  75. data/features/support/slow_updates.rb +18 -0
  76. data/features/surveyor.feature +895 -0
  77. data/features/surveyor_dependencies.feature +476 -0
  78. data/features/surveyor_parser.feature +504 -0
  79. data/features/z_redcap_parser.feature +62 -0
  80. data/lib/assets/images/surveyor/next.gif +0 -0
  81. data/lib/assets/images/surveyor/prev.gif +0 -0
  82. data/lib/assets/images/surveyor/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  83. data/lib/assets/images/surveyor/ui-bg_flat_75_ffffff_40x100.png +0 -0
  84. data/lib/assets/images/surveyor/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  85. data/lib/assets/images/surveyor/ui-bg_glass_65_ffffff_1x400.png +0 -0
  86. data/lib/assets/images/surveyor/ui-bg_glass_75_dadada_1x400.png +0 -0
  87. data/lib/assets/images/surveyor/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  88. data/lib/assets/images/surveyor/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  89. data/lib/assets/images/surveyor/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  90. data/lib/assets/images/surveyor/ui-icons_222222_256x240.png +0 -0
  91. data/lib/assets/images/surveyor/ui-icons_2e83ff_256x240.png +0 -0
  92. data/lib/assets/images/surveyor/ui-icons_454545_256x240.png +0 -0
  93. data/lib/assets/images/surveyor/ui-icons_888888_256x240.png +0 -0
  94. data/lib/assets/images/surveyor/ui-icons_cd0a0a_256x240.png +0 -0
  95. data/lib/assets/javascripts/surveyor/jquery-1.9.0.js +9555 -0
  96. data/lib/assets/javascripts/surveyor/jquery-ui-1.10.0.custom.js +14850 -0
  97. data/lib/assets/javascripts/surveyor/jquery-ui-timepicker-addon.js +1919 -0
  98. data/lib/assets/javascripts/surveyor/jquery.maskedinput.js +338 -0
  99. data/lib/assets/javascripts/surveyor/jquery.selectToUISlider.js +240 -0
  100. data/lib/assets/javascripts/surveyor/jquery.surveyor.js +154 -0
  101. data/lib/assets/stylesheets/surveyor.sass +132 -0
  102. data/lib/assets/stylesheets/surveyor/jquery-ui-1.10.0.custom.css +1174 -0
  103. data/lib/assets/stylesheets/surveyor/jquery-ui-timepicker-addon.css +11 -0
  104. data/lib/assets/stylesheets/surveyor/reset.css +48 -0
  105. data/lib/assets/stylesheets/surveyor/results.css +125 -0
  106. data/lib/assets/stylesheets/surveyor/ui.slider.extras.css +110 -0
  107. data/lib/generators/surveyor/custom_generator.rb +18 -0
  108. data/lib/generators/surveyor/install_generator.rb +101 -0
  109. data/lib/generators/surveyor/templates/app/assets/javascripts/surveyor_all.js +6 -0
  110. data/lib/generators/surveyor/templates/app/assets/stylesheets/surveyor_all.css +9 -0
  111. data/lib/generators/surveyor/templates/app/controllers/surveyor_controller.rb +40 -0
  112. data/lib/generators/surveyor/templates/app/views/layouts/surveyor_custom.html.erb +13 -0
  113. data/lib/generators/surveyor/templates/config/locales/surveyor_en.yml +19 -0
  114. data/lib/generators/surveyor/templates/config/locales/surveyor_es.yml +19 -0
  115. data/lib/generators/surveyor/templates/config/locales/surveyor_he.yml +19 -0
  116. data/lib/generators/surveyor/templates/config/locales/surveyor_ko.yml +19 -0
  117. data/lib/generators/surveyor/templates/db/migrate/add_api_id_to_question_groups.rb +10 -0
  118. data/lib/generators/surveyor/templates/db/migrate/add_api_ids.rb +14 -0
  119. data/lib/generators/surveyor/templates/db/migrate/add_api_ids_to_response_sets_and_responses.rb +12 -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 +280 -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.rb +13 -0
  158. data/lib/surveyor/acts_as_response.rb +17 -0
  159. data/lib/surveyor/common.rb +59 -0
  160. data/lib/surveyor/engine.rb +10 -0
  161. data/lib/surveyor/helpers/asset_pipeline.rb +18 -0
  162. data/lib/surveyor/helpers/formtastic_custom_input.rb +13 -0
  163. data/lib/surveyor/helpers/surveyor_helper_methods.rb +103 -0
  164. data/lib/surveyor/models/answer_methods.rb +83 -0
  165. data/lib/surveyor/models/dependency_condition_methods.rb +69 -0
  166. data/lib/surveyor/models/dependency_methods.rb +57 -0
  167. data/lib/surveyor/models/question_group_methods.rb +58 -0
  168. data/lib/surveyor/models/question_methods.rb +112 -0
  169. data/lib/surveyor/models/response_methods.rb +126 -0
  170. data/lib/surveyor/models/response_set_methods.rb +188 -0
  171. data/lib/surveyor/models/survey_methods.rb +100 -0
  172. data/lib/surveyor/models/survey_section_methods.rb +53 -0
  173. data/lib/surveyor/models/survey_translation_methods.rb +30 -0
  174. data/lib/surveyor/models/validation_condition_methods.rb +54 -0
  175. data/lib/surveyor/models/validation_methods.rb +45 -0
  176. data/lib/surveyor/mustache_context.rb +11 -0
  177. data/lib/surveyor/parser.rb +427 -0
  178. data/lib/surveyor/redcap_parser.rb +288 -0
  179. data/lib/surveyor/surveyor_controller_methods.rb +236 -0
  180. data/lib/surveyor/unparser.rb +147 -0
  181. data/lib/surveyor/version.rb +3 -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 +311 -0
  185. data/spec/factories.rb +166 -0
  186. data/spec/helpers/formtastic_custom_input_spec.rb +15 -0
  187. data/spec/helpers/surveyor_helper_spec.rb +117 -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 +174 -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 +206 -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 +53 -0
  212. metadata +601 -0
@@ -0,0 +1,147 @@
1
+ %w(survey survey_section question_group question dependency dependency_condition answer validation validation_condition).each {|model| require model }
2
+ module Surveyor
3
+ class Unparser
4
+ # Class methods
5
+ def self.unparse(survey)
6
+ survey.unparse(dsl = "")
7
+ dsl
8
+ end
9
+ end
10
+ end
11
+
12
+ # Surveyor models with extra parsing methods
13
+ class Survey < ActiveRecord::Base
14
+ # block
15
+
16
+ def unparse(dsl)
17
+ attrs = (self.attributes.diff Survey.new(:title => title).attributes).delete_if{|k,v| %w(created_at updated_at inactive_at id title access_code api_id).include? k}.symbolize_keys!
18
+ dsl << "survey \"#{title}\""
19
+ dsl << (attrs.blank? ? " do\n" : ", #{attrs.inspect.gsub(/\{|\}/, "")} do\n")
20
+ sections.each{|section| section.unparse(dsl)}
21
+ dsl << "end\n"
22
+ end
23
+ end
24
+ class SurveySection < ActiveRecord::Base
25
+ # block
26
+
27
+ def unparse(dsl)
28
+ attrs = (self.attributes.diff SurveySection.new(:title => title).attributes).delete_if{|k,v| %w(created_at updated_at id survey_id).include? k}.symbolize_keys!
29
+ group_questions = []
30
+ dsl << " section \"#{title}\""
31
+ dsl << (attrs.blank? ? " do\n" : ", #{attrs.inspect.gsub(/\{|\}/, "")} do\n")
32
+ questions.each_with_index do |question, index|
33
+ if question.solo?
34
+ question.unparse(dsl)
35
+ else # gather up the group questions
36
+ group_questions << question
37
+ if (index + 1 >= questions.size) or (question.question_group != questions[index + 1].question_group)
38
+ # this is the last question of the section, or the group
39
+ question.question_group.unparse(dsl)
40
+ end
41
+ group_questions = []
42
+ end
43
+ end
44
+ dsl << " end\n"
45
+ end
46
+ end
47
+ class QuestionGroup < ActiveRecord::Base
48
+ # block
49
+
50
+ def unparse(dsl)
51
+ attrs = (self.attributes.diff QuestionGroup.new(:text => text).attributes).delete_if{|k,v| %w(created_at updated_at id api_id).include?(k) or (k == "display_type" && %w(grid repeater default).include?(v))}.symbolize_keys!
52
+ method = (%w(grid repeater).include?(display_type) ? display_type : "group")
53
+ dsl << "\n"
54
+ dsl << " #{method} \"#{text}\""
55
+ dsl << (attrs.blank? ? " do\n" : ", #{attrs.inspect.gsub(/\{|\}/, "")} do\n")
56
+ questions.first.answers.each{|answer| answer.unparse(dsl)} if display_type == "grid"
57
+ questions.each{|question| question.unparse(dsl)}
58
+ dsl << " end\n"
59
+ end
60
+ end
61
+ class Question < ActiveRecord::Base
62
+ # nonblock
63
+
64
+ def unparse(dsl)
65
+ attrs = (self.attributes.diff Question.new(:text => text).attributes).delete_if{|k,v| %w(created_at updated_at reference_identifier id survey_section_id question_group_id api_id).include?(k) or (k == "display_type" && v == "label")}.symbolize_keys!
66
+ dsl << (solo? ? "\n" : " ")
67
+ if display_type == "label"
68
+ dsl << " label"
69
+ else
70
+ dsl << " q"
71
+ end
72
+ dsl << "_#{reference_identifier}" unless reference_identifier.blank?
73
+ dsl << " \"#{text}\""
74
+ dsl << (attrs.blank? ? "\n" : ", #{attrs.inspect.gsub(/\{|\}/, "")}\n")
75
+ if solo? or question_group.display_type != "grid"
76
+ answers.each{|answer| answer.unparse(dsl)}
77
+ end
78
+ dependency.unparse(dsl) if dependency
79
+ end
80
+ end
81
+ class Dependency < ActiveRecord::Base
82
+ # nonblock
83
+
84
+ def unparse(dsl)
85
+ attrs = (self.attributes.diff Dependency.new.attributes).delete_if{|k,v| %w(created_at updated_at id question_id).include?(k) }.symbolize_keys!
86
+ dsl << " " if question.part_of_group?
87
+ dsl << " dependency"
88
+ dsl << (attrs.blank? ? "\n" : " #{attrs.inspect.gsub(/\{|\}/, "")}\n")
89
+ dependency_conditions.each{|dependency_condition| dependency_condition.unparse(dsl)}
90
+ end
91
+ end
92
+ class DependencyCondition < ActiveRecord::Base
93
+ # nonblock
94
+
95
+ def unparse(dsl)
96
+ attrs = (self.attributes.diff Dependency.new.attributes).delete_if{|k,v| %w(created_at updated_at question_id question_group_id rule_key rule operator id dependency_id answer_id).include? k}.symbolize_keys!
97
+ dsl << " " if dependency.question.part_of_group?
98
+ dsl << " condition"
99
+ dsl << "_#{rule_key}" unless rule_key.blank?
100
+ dsl << " :q_#{question.reference_identifier}, \"#{operator}\""
101
+ dsl << (attrs.blank? ? ", {:answer_reference=>\"#{answer && answer.reference_identifier}\"}\n" : ", {#{attrs.inspect.gsub(/\{|\}/, "")}, :answer_reference=>\"#{answer && answer.reference_identifier}\"}\n")
102
+ end
103
+ end
104
+ class Answer < ActiveRecord::Base
105
+ # nonblock
106
+
107
+ def unparse(dsl)
108
+ attrs = (self.attributes.diff Answer.new(:text => text).attributes).delete_if{|k,v| %w(created_at updated_at reference_identifier response_class id question_id api_id).include? k}.symbolize_keys!
109
+ attrs.delete(:is_exclusive) if text == "Omit" && is_exclusive == true
110
+ attrs.merge!({:is_exclusive => false}) if text == "Omit" && is_exclusive == false
111
+ dsl << " " if question.part_of_group?
112
+ dsl << " a"
113
+ dsl << "_#{reference_identifier}" unless reference_identifier.blank?
114
+ if response_class.to_s.titlecase == text && attrs == {:display_type => "hidden_label"}
115
+ dsl << " :#{response_class}"
116
+ else
117
+ dsl << [ text.blank? ? nil : text == "Other" ? " :other" : text == "Omit" ? " :omit" : " \"#{text}\"",
118
+ (response_class.blank? or response_class == "answer") ? nil : " #{response_class.to_sym.inspect}",
119
+ attrs.blank? ? nil : " #{attrs.inspect.gsub(/\{|\}/, "")}\n"].compact.join(",")
120
+ end
121
+ dsl << "\n"
122
+ validations.each{|validation| validation.unparse(dsl)}
123
+ end
124
+ end
125
+ class Validation < ActiveRecord::Base
126
+ # nonblock
127
+
128
+ def unparse(dsl)
129
+ attrs = (self.attributes.diff Validation.new.attributes).delete_if{|k,v| %w(created_at updated_at id answer_id).include?(k) }.symbolize_keys!
130
+ dsl << " " if answer.question.part_of_group?
131
+ dsl << " validation"
132
+ dsl << (attrs.blank? ? "\n" : " #{attrs.inspect.gsub(/\{|\}/, "")}\n")
133
+ validation_conditions.each{|validation_condition| validation_condition.unparse(dsl)}
134
+ end
135
+ end
136
+ class ValidationCondition < ActiveRecord::Base
137
+ # nonblock
138
+
139
+ def unparse(dsl)
140
+ attrs = (self.attributes.diff ValidationCondition.new.attributes).delete_if{|k,v| %w(created_at updated_at operator rule_key id validation_id).include? k}.symbolize_keys!
141
+ dsl << " " if validation.answer.question.part_of_group?
142
+ dsl << " condition"
143
+ dsl << "_#{rule_key}" unless rule_key.blank?
144
+ dsl << " \"#{operator}\""
145
+ dsl << (attrs.blank? ? "\n" : ", #{attrs.inspect.gsub(/\{|\}/, "")}\n")
146
+ end
147
+ end
@@ -0,0 +1,3 @@
1
+ module Surveyor
2
+ VERSION = '1.5.0-disco.2'
3
+ end
@@ -0,0 +1,88 @@
1
+ desc "generate and load survey (specify FILE=surveys/your_survey.rb)"
2
+ task :surveyor => :"surveyor:parse"
3
+
4
+ namespace :surveyor do
5
+ task :parse => :environment do
6
+ raise "USAGE: file name required e.g. 'FILE=surveys/kitchen_sink_survey.rb'" if ENV["FILE"].blank?
7
+ file = File.join(Rails.root, ENV["FILE"])
8
+ raise "File does not exist: #{file}" unless FileTest.exists?(file)
9
+ puts "--- Parsing #{file} ---"
10
+ Surveyor::Parser.parse_file(file, {:trace => Rake.application.options.trace})
11
+ puts "--- Done #{file} ---"
12
+ end
13
+ desc "generate and load survey from REDCap Data Dictionary (specify FILE=surveys/redcap.csv)"
14
+ task :redcap => :environment do
15
+ raise "USAGE: file name required e.g. 'FILE=surveys/redcap_demo_survey.csv'" if ENV["FILE"].blank?
16
+ file = File.join(Rails.root, ENV["FILE"])
17
+ raise "File does not exist: #{file}" unless FileTest.exists?(file)
18
+ puts "--- Parsing #{file} ---"
19
+ Surveyor::RedcapParser.parse File.read(file), File.basename(file, ".csv"), {:trace => Rake.application.options.trace}
20
+ puts "--- Done #{file} ---"
21
+ end
22
+ desc "generate a surveyor DSL file from a survey"
23
+ task :unparse => :environment do
24
+ surveys = Survey.all
25
+ if surveys
26
+ puts "The following surveys are available"
27
+ surveys.each do |survey|
28
+ puts "#{survey.id} #{survey.title}"
29
+ end
30
+ print "Which survey would you like to unparse? "
31
+ id = $stdin.gets.to_i
32
+ if survey_to_unparse = surveys.detect{|s| s.id == id}
33
+ filename = "surveys/#{survey_to_unparse.access_code}_#{Date.today.to_s(:db)}.rb"
34
+ puts "unparsing #{survey_to_unparse.title} to #{filename}"
35
+ File.open(filename, 'w') {|f| f.write(Surveyor::Unparser.unparse(survey_to_unparse))}
36
+ else
37
+ puts "not found"
38
+ end
39
+ else
40
+ puts "There are no surveys available"
41
+ end
42
+ end
43
+ desc "remove surveys (that don't have response sets)"
44
+ task :remove => :environment do
45
+ surveys = Survey.all.delete_if{|s| !s.response_sets.blank?}
46
+ if surveys
47
+ puts "The following surveys do not have any response sets"
48
+ surveys.each do |survey|
49
+ puts "#{survey.id} #{survey.title}"
50
+ end
51
+ print "Which survey would you like to remove? "
52
+ id = $stdin.gets.to_i
53
+ if survey_to_delete = surveys.detect{|s| s.id == id}
54
+ puts "removing #{survey_to_delete.title}"
55
+ survey_to_delete.destroy
56
+ else
57
+ put "not found"
58
+ end
59
+ else
60
+ puts "There are no surveys without response sets"
61
+ end
62
+ end
63
+ desc "dump all responses to a given survey"
64
+ task :dump => :environment do
65
+ require 'fileutils.rb'
66
+ survey_version = ENV["SURVEY_VERSION"]
67
+ access_code = ENV["SURVEY_ACCESS_CODE"]
68
+
69
+ raise "USAGE: rake surveyor:dump SURVEY_ACCESS_CODE=<access_code> [OUTPUT_DIR=<dir>] [SURVEY_VERSION=<survey_version>]" unless access_code
70
+ params_string = "code #{access_code}"
71
+
72
+ surveys = Survey.where(:access_code => access_code).order("survey_version ASC")
73
+ if survey_version.blank?
74
+ survey = surveys.last
75
+ else
76
+ params_string += " and survey_version #{survey_version}"
77
+ survey = surveys.where(:survey_version => survey_version).first
78
+ end
79
+
80
+ raise "No Survey found with #{params_string}" unless survey
81
+ dir = ENV["OUTPUT_DIR"] || Rails.root
82
+ mkpath(dir) # Create all non-existent directories
83
+ full_path = File.join(dir,"#{survey.access_code}_v#{survey.survey_version}_#{Time.now.to_i}.csv")
84
+ File.open(full_path, 'w') do |f|
85
+ survey.response_sets.each_with_index{|r,i| f.write(r.to_csv(true, i == 0)) } # print access code every time, print_header first time
86
+ end
87
+ end
88
+ end
@@ -0,0 +1 @@
1
+ require 'surveyor'
@@ -0,0 +1,311 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe SurveyorController do
4
+ include Surveyor::Engine.routes.url_helpers
5
+ before do
6
+ @routes = Surveyor::Engine.routes
7
+ end
8
+
9
+ let!(:survey) { FactoryGirl.create(:survey, :title => "Alphabet", :access_code => "alpha", :survey_version => 0)}
10
+ let!(:survey_beta) { FactoryGirl.create(:survey, :title => "Alphabet", :access_code => "alpha", :survey_version => 1)}
11
+ let!(:response_set) { FactoryGirl.create(:response_set, :survey => survey, :access_code => "pdq")}
12
+ let!(:response_set_beta) { FactoryGirl.create(:response_set, :survey => survey_beta, :access_code => "rst")}
13
+ before { ResponseSet.stub(:create).and_return(response_set) }
14
+
15
+ # match '/', :to => 'surveyor#new', :as => 'available_surveys', :via => :get
16
+ # match '/:survey_code', :to => 'surveyor#create', :as => 'take_survey', :via => :post
17
+ # match '/:survey_code', :to => 'surveyor#export', :as => 'export_survey', :via => :get
18
+ # match '/:survey_code/:response_set_code', :to => 'surveyor#show', :as => 'view_my_survey', :via => :get
19
+ # match '/:survey_code/:response_set_code/take', :to => 'surveyor#edit', :as => 'edit_my_survey', :via => :get
20
+ # match '/:survey_code/:response_set_code', :to => 'surveyor#update', :as => 'update_my_survey', :via => :put
21
+
22
+ context "#new" do
23
+ def do_get
24
+ get :new
25
+ end
26
+ it "renders new" do
27
+ do_get
28
+ response.should be_success
29
+ response.should render_template('new')
30
+ end
31
+ it "assigns surveys_by_access_code" do
32
+ do_get
33
+ assigns(:surveys_by_access_code).should == {"alpha" => [survey_beta,survey]}
34
+ end
35
+ end
36
+
37
+ context "#create" do
38
+ def do_post(params = {})
39
+ post :create, {:survey_code => "alpha"}.merge(params)
40
+ end
41
+ it "finds latest version" do
42
+ do_post
43
+ assigns(:survey).should == survey_beta
44
+ end
45
+ it "finds specified survey_version" do
46
+ do_post :survey_version => 0
47
+ assigns(:survey).should == survey
48
+ end
49
+ it "creates a new response_set" do
50
+ ResponseSet.should_receive(:create)
51
+ do_post
52
+ end
53
+ it "should redirects to the new response_set" do
54
+ do_post
55
+ response.should redirect_to( edit_my_survey_path(:survey_code => "alpha", :response_set_code => "pdq"))
56
+ end
57
+
58
+ context "with failures" do
59
+ it "redirect to #new on failed ResponseSet#create" do
60
+ ResponseSet.should_receive(:create).and_return(false)
61
+ do_post
62
+ response.should redirect_to(available_surveys_path)
63
+ end
64
+ it "redirect to #new on failed Survey#find" do
65
+ do_post :survey_code => "missing"
66
+ response.should redirect_to(available_surveys_path)
67
+ end
68
+ end
69
+
70
+ context "with javascript check, assigned in session" do
71
+ it "enabled" do
72
+ do_post :surveyor_javascript_enabled => "true"
73
+ session[:surveyor_javascript].should_not be_nil
74
+ session[:surveyor_javascript].should == "enabled"
75
+ end
76
+ it "disabled" do
77
+ post :create, :survey_code => "xyz", :surveyor_javascript_enabled => "not_true"
78
+ session[:surveyor_javascript].should_not be_nil
79
+ session[:surveyor_javascript].should == "not_enabled"
80
+ end
81
+ end
82
+ end
83
+
84
+ context "#show" do
85
+ def do_get(params = {})
86
+ get :show, {:survey_code => "alpha", :response_set_code => "pdq"}.merge(params)
87
+ end
88
+ it "renders show" do
89
+ do_get
90
+ response.should be_success
91
+ response.should render_template('show')
92
+ end
93
+ it "finds ResponseSet with includes" do
94
+ ResponseSet.should_receive(:includes).with(:responses => [:question, :answer]).and_return(response_set)
95
+ response_set.should_receive(:find_by).with(:access_code => "pdq").and_return(response_set)
96
+ do_get
97
+ end
98
+ it "redirects for missing response set" do
99
+ do_get :response_set_code => "DIFFERENT"
100
+ response.should redirect_to(available_surveys_path)
101
+ end
102
+ it "assigns earlier survey_version" do
103
+ response_set
104
+ do_get
105
+ assigns[:response_set].should == response_set
106
+ assigns[:survey].should == survey
107
+ end
108
+ it "assigns later survey_version" do
109
+ response_set_beta
110
+ do_get :response_set_code => "rst"
111
+ assigns[:response_set].should == response_set_beta
112
+ assigns[:survey].should == survey_beta
113
+ end
114
+ end
115
+
116
+ context "#edit" do
117
+ def do_get(params = {})
118
+ survey.sections = [FactoryGirl.create(:survey_section, :survey => survey)]
119
+ get :edit, {:survey_code => "alpha", :response_set_code => "pdq"}.merge(params)
120
+ end
121
+ it "renders edit" do
122
+ do_get
123
+ response.should be_success
124
+ response.should render_template('edit')
125
+ end
126
+ it "assigns survey and response set" do
127
+ do_get
128
+ assigns[:survey].should == survey
129
+ assigns[:response_set].should == response_set
130
+ end
131
+ it "redirects for missing response set" do
132
+ do_get :response_set_code => "DIFFERENT"
133
+ response.should redirect_to(available_surveys_path)
134
+ end
135
+ it "assigns dependents if javascript not enabled" do
136
+ controller.stub(:get_unanswered_dependencies_minus_section_questions).and_return([FactoryGirl.create(:question)])
137
+ session[:surveyor_javascript].should be_nil
138
+ do_get
139
+ assigns[:dependents].should_not be_empty
140
+ end
141
+ it "does not assign dependents if javascript is enabled" do
142
+ controller.stub(:get_unanswered_dependencies_minus_section_questions).and_return([FactoryGirl.create(:question)])
143
+ session[:surveyor_javascript] = "enabled"
144
+ do_get
145
+ assigns[:dependents].should be_empty
146
+ end
147
+ it "assigns earlier survey_version" do
148
+ do_get
149
+ assigns[:response_set].should == response_set
150
+ assigns[:survey].should == survey
151
+ end
152
+ it "assigns later survey_version" do
153
+ survey_beta.sections = [FactoryGirl.create(:survey_section, :survey => survey_beta)]
154
+ do_get :response_set_code => "rst"
155
+ assigns[:survey].should == survey_beta
156
+ assigns[:response_set].should == response_set_beta
157
+
158
+ end
159
+ end
160
+
161
+ context "#update" do
162
+ let(:responses_ui_hash) { {} }
163
+ let(:update_params) {
164
+ {
165
+ :survey_code => "alpha",
166
+ :response_set_code => "pdq"
167
+ }
168
+ }
169
+ shared_examples "#update action" do
170
+ before do
171
+ ResponseSet.stub(:includes).and_return(response_set)
172
+ response_set.stub(:find_by).and_return(response_set)
173
+ responses_ui_hash['11'] = {'api_id' => 'something', 'answer_id' => '56', 'question_id' => '9'}
174
+ end
175
+ it "finds a response set" do
176
+ ResponseSet.should_receive(:includes).and_return(response_set)
177
+ response_set.should_receive(:find_by).and_return(response_set)
178
+ do_put
179
+ end
180
+ it "saves responses" do
181
+ response_set.should_receive(:update_from_ui_hash).with(responses_ui_hash)
182
+
183
+ do_put(:r => responses_ui_hash)
184
+ end
185
+ it "does not fail when there are no responses" do
186
+ lambda { do_put }.should_not raise_error
187
+ end
188
+ context "with update exceptions" do
189
+ it 'retries the update on a constraint violation' do
190
+ response_set.should_receive(:update_from_ui_hash).ordered.with(responses_ui_hash).and_raise(ActiveRecord::StatementInvalid.new('statement invalid'))
191
+ response_set.should_receive(:update_from_ui_hash).ordered.with(responses_ui_hash)
192
+
193
+ expect { do_put(:r => responses_ui_hash) }.to_not raise_error
194
+ end
195
+
196
+ it 'only retries three times' do
197
+ response_set.should_receive(:update_from_ui_hash).exactly(3).times.with(responses_ui_hash).and_raise(ActiveRecord::StatementInvalid.new('statement invalid'))
198
+
199
+ expect { do_put(:r => responses_ui_hash) }.to raise_error(ActiveRecord::StatementInvalid)
200
+ end
201
+
202
+ it 'does not retry for other errors' do
203
+ response_set.should_receive(:update_from_ui_hash).once.with(responses_ui_hash).and_raise('Bad news')
204
+
205
+ expect { do_put(:r => responses_ui_hash) }.to raise_error('Bad news')
206
+ end
207
+ end
208
+ end
209
+
210
+ context "with form submission" do
211
+ def do_put(extra_params = {})
212
+ put :update, update_params.merge(extra_params)
213
+ end
214
+
215
+ it_behaves_like "#update action"
216
+ it "redirects to #edit without params" do
217
+ do_put
218
+ response.should redirect_to(edit_my_survey_path(:survey_code => "alpha", :response_set_code => "pdq"))
219
+ end
220
+ it "completes the found response set on finish" do
221
+ do_put :finish => 'finish'
222
+ response_set.reload.should be_complete
223
+ end
224
+ it 'flashes completion' do
225
+ do_put :finish => 'finish'
226
+ flash[:notice].should == "Completed survey"
227
+ end
228
+ it "redirects for missing response set" do
229
+ do_put :response_set_code => "DIFFERENT"
230
+ response.should redirect_to(available_surveys_path)
231
+ flash[:notice].should == "Unable to find your responses to the survey"
232
+ end
233
+ end
234
+
235
+ context 'with ajax' do
236
+ def do_put(extra_params = {})
237
+ xhr :put, :update, update_params.merge(extra_params)
238
+ end
239
+
240
+ it_behaves_like "#update action"
241
+ it "returns dependencies" do
242
+ ResponseSet.should_receive(:includes).and_return(response_set)
243
+ response_set.should_receive(:find_by).and_return(response_set)
244
+ response_set.should_receive(:all_dependencies).and_return({"show" => ['q_1'], "hide" => ['q_2']})
245
+
246
+ do_put
247
+ JSON.parse(response.body).should == {"show" => ['q_1'], "hide" => ["q_2"]}
248
+ end
249
+ it "returns 404 for missing response set" do
250
+ do_put :response_set_code => "DIFFERENT"
251
+ response.status.should == 404
252
+ end
253
+ end
254
+ end
255
+
256
+ context "#export" do
257
+ render_views
258
+
259
+ let(:json) {
260
+ get :export, :survey_code => survey.access_code, :format => 'json'
261
+ JSON.parse(response.body)
262
+ }
263
+
264
+ context "question inside and outside a question group" do
265
+ def question_text(refid)
266
+ <<-SURVEY
267
+ q "Where is a foo?", :pick => :one, :help_text => 'Look around.', :reference_identifier => #{refid.inspect},
268
+ :data_export_identifier => 'X.FOO', :common_namespace => 'F', :common_identifier => 'f'
269
+ a_L 'To the left', :data_export_identifier => 'X.L', :common_namespace => 'F', :common_identifier => 'l'
270
+ a_R 'To the right', :data_export_identifier => 'X.R', :common_namespace => 'F', :common_identifier => 'r'
271
+ a_O 'Elsewhere', :string
272
+
273
+ dependency :rule => 'R'
274
+ condition_R :q_bar, "==", :a_1
275
+ SURVEY
276
+ end
277
+ let(:survey_text) {
278
+ <<-SURVEY
279
+ survey 'xyz' do
280
+ section 'Sole' do
281
+ q_bar "Should that other question show up?", :pick => :one
282
+ a_1 'Yes'
283
+ a_2 'No'
284
+
285
+ #{question_text('foo_solo')}
286
+
287
+ group do
288
+ #{question_text('foo_grouped')}
289
+ end
290
+ end
291
+ end
292
+ SURVEY
293
+ }
294
+ let(:survey) { Surveyor::Parser.new.parse(survey_text) }
295
+ let(:solo_question_json) { json['sections'][0]['questions_and_groups'][1] }
296
+ let(:grouped_question_json) { json['sections'][0]['questions_and_groups'][2]['questions'][0] }
297
+
298
+ it "produces identical JSON except for API IDs and question reference identifers" do
299
+ solo_question_json['answers'].to_json.should be_json_eql( grouped_question_json['answers'].to_json).excluding("uuid", "reference_identifier")
300
+ solo_question_json['dependency'].to_json.should be_json_eql( grouped_question_json['dependency'].to_json).excluding("uuid", "reference_identifier")
301
+ solo_question_json.to_json.should be_json_eql( grouped_question_json.to_json).excluding("uuid", "reference_identifier")
302
+ end
303
+ it "produces the expected reference identifier for the solo question" do
304
+ solo_question_json['reference_identifier'].should == 'foo_solo'
305
+ end
306
+ it "produces the expected reference identifer for the question in the group" do
307
+ grouped_question_json['reference_identifier'].should == 'foo_grouped'
308
+ end
309
+ end
310
+ end
311
+ end