upgrade_surveyor 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (203) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +30 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +18 -0
  5. data/CHANGELOG.md +253 -0
  6. data/Gemfile +5 -0
  7. data/Gemfile.rails_version +25 -0
  8. data/MIT-LICENSE +20 -0
  9. data/README.md +164 -0
  10. data/Rakefile +65 -0
  11. data/app/controllers/surveyor_controller.rb +5 -0
  12. data/app/helpers/results_helper.rb +20 -0
  13. data/app/helpers/survey_form_builder.rb +37 -0
  14. data/app/helpers/surveyor_helper.rb +3 -0
  15. data/app/inputs/quiet_input.rb +5 -0
  16. data/app/inputs/surveyor_check_boxes_input.rb +35 -0
  17. data/app/inputs/surveyor_radio_input.rb +18 -0
  18. data/app/models/answer.rb +3 -0
  19. data/app/models/dependency.rb +3 -0
  20. data/app/models/dependency_condition.rb +3 -0
  21. data/app/models/permitted_params.rb +102 -0
  22. data/app/models/question.rb +3 -0
  23. data/app/models/question_group.rb +3 -0
  24. data/app/models/response.rb +4 -0
  25. data/app/models/response_set.rb +3 -0
  26. data/app/models/survey.rb +3 -0
  27. data/app/models/survey_section.rb +4 -0
  28. data/app/models/survey_translation.rb +4 -0
  29. data/app/models/validation.rb +3 -0
  30. data/app/models/validation_condition.rb +3 -0
  31. data/app/views/layouts/results.html.erb +13 -0
  32. data/app/views/layouts/surveyor_default.html.erb +12 -0
  33. data/app/views/partials/_answer.html.haml +25 -0
  34. data/app/views/partials/_dependents.html.haml +5 -0
  35. data/app/views/partials/_question.html.haml +28 -0
  36. data/app/views/partials/_question_group.html.haml +44 -0
  37. data/app/views/partials/_section.html.haml +12 -0
  38. data/app/views/partials/_section_menu.html.haml +12 -0
  39. data/app/views/surveyor/edit.html.haml +21 -0
  40. data/app/views/surveyor/export.json.rabl +85 -0
  41. data/app/views/surveyor/new.html.haml +24 -0
  42. data/app/views/surveyor/show.html.haml +74 -0
  43. data/app/views/surveyor/show.json.rabl +15 -0
  44. data/config/routes.rb +8 -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/lib/assets/images/surveyor/next.gif +0 -0
  55. data/lib/assets/images/surveyor/prev.gif +0 -0
  56. data/lib/assets/images/surveyor/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  57. data/lib/assets/images/surveyor/ui-bg_flat_75_ffffff_40x100.png +0 -0
  58. data/lib/assets/images/surveyor/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  59. data/lib/assets/images/surveyor/ui-bg_glass_65_ffffff_1x400.png +0 -0
  60. data/lib/assets/images/surveyor/ui-bg_glass_75_dadada_1x400.png +0 -0
  61. data/lib/assets/images/surveyor/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  62. data/lib/assets/images/surveyor/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  63. data/lib/assets/images/surveyor/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  64. data/lib/assets/images/surveyor/ui-icons_222222_256x240.png +0 -0
  65. data/lib/assets/images/surveyor/ui-icons_2e83ff_256x240.png +0 -0
  66. data/lib/assets/images/surveyor/ui-icons_454545_256x240.png +0 -0
  67. data/lib/assets/images/surveyor/ui-icons_888888_256x240.png +0 -0
  68. data/lib/assets/images/surveyor/ui-icons_cd0a0a_256x240.png +0 -0
  69. data/lib/assets/javascripts/surveyor/jquery-1.9.0.js +9555 -0
  70. data/lib/assets/javascripts/surveyor/jquery-ui-1.10.0.custom.js +14850 -0
  71. data/lib/assets/javascripts/surveyor/jquery-ui-timepicker-addon.js +1919 -0
  72. data/lib/assets/javascripts/surveyor/jquery.maskedinput.js +338 -0
  73. data/lib/assets/javascripts/surveyor/jquery.selectToUISlider.js +240 -0
  74. data/lib/assets/javascripts/surveyor/jquery.surveyor.js +154 -0
  75. data/lib/assets/stylesheets/surveyor/jquery-ui-1.10.0.custom.sass +1380 -0
  76. data/lib/assets/stylesheets/surveyor/jquery-ui-timepicker-addon.css +11 -0
  77. data/lib/assets/stylesheets/surveyor/reset.css +48 -0
  78. data/lib/assets/stylesheets/surveyor/results.css +125 -0
  79. data/lib/assets/stylesheets/surveyor/ui.slider.extras.css +110 -0
  80. data/lib/assets/stylesheets/surveyor.sass +132 -0
  81. data/lib/generators/surveyor/custom_generator.rb +18 -0
  82. data/lib/generators/surveyor/install_generator.rb +98 -0
  83. data/lib/generators/surveyor/templates/app/assets/javascripts/surveyor_all.js +6 -0
  84. data/lib/generators/surveyor/templates/app/assets/stylesheets/surveyor_all.css +9 -0
  85. data/lib/generators/surveyor/templates/app/controllers/surveyor_controller.rb +40 -0
  86. data/lib/generators/surveyor/templates/app/views/layouts/surveyor_custom.html.erb +13 -0
  87. data/lib/generators/surveyor/templates/config/locales/surveyor_en.yml +19 -0
  88. data/lib/generators/surveyor/templates/config/locales/surveyor_es.yml +19 -0
  89. data/lib/generators/surveyor/templates/config/locales/surveyor_he.yml +19 -0
  90. data/lib/generators/surveyor/templates/config/locales/surveyor_ko.yml +19 -0
  91. data/lib/generators/surveyor/templates/db/migrate/add_api_id_to_question_groups.rb +10 -0
  92. data/lib/generators/surveyor/templates/db/migrate/add_api_ids.rb +14 -0
  93. data/lib/generators/surveyor/templates/db/migrate/add_api_ids_to_response_sets_and_responses.rb +12 -0
  94. data/lib/generators/surveyor/templates/db/migrate/add_correct_answer_id_to_questions.rb +10 -0
  95. data/lib/generators/surveyor/templates/db/migrate/add_default_value_to_answers.rb +10 -0
  96. data/lib/generators/surveyor/templates/db/migrate/add_display_order_to_surveys.rb +10 -0
  97. data/lib/generators/surveyor/templates/db/migrate/add_display_type_to_answers.rb +14 -0
  98. data/lib/generators/surveyor/templates/db/migrate/add_index_to_response_sets.rb +10 -0
  99. data/lib/generators/surveyor/templates/db/migrate/add_index_to_surveys.rb +10 -0
  100. data/lib/generators/surveyor/templates/db/migrate/add_input_mask_attributes_to_answer.rb +12 -0
  101. data/lib/generators/surveyor/templates/db/migrate/add_section_id_to_responses.rb +12 -0
  102. data/lib/generators/surveyor/templates/db/migrate/add_unique_index_on_access_code_and_version_in_surveys.rb +10 -0
  103. data/lib/generators/surveyor/templates/db/migrate/add_unique_indicies.rb +18 -0
  104. data/lib/generators/surveyor/templates/db/migrate/add_version_to_surveys.rb +10 -0
  105. data/lib/generators/surveyor/templates/db/migrate/api_ids_must_be_unique.rb +23 -0
  106. data/lib/generators/surveyor/templates/db/migrate/create_answers.rb +38 -0
  107. data/lib/generators/surveyor/templates/db/migrate/create_dependencies.rb +23 -0
  108. data/lib/generators/surveyor/templates/db/migrate/create_dependency_conditions.rb +30 -0
  109. data/lib/generators/surveyor/templates/db/migrate/create_question_groups.rb +28 -0
  110. data/lib/generators/surveyor/templates/db/migrate/create_questions.rb +37 -0
  111. data/lib/generators/surveyor/templates/db/migrate/create_response_sets.rb +23 -0
  112. data/lib/generators/surveyor/templates/db/migrate/create_responses.rb +34 -0
  113. data/lib/generators/surveyor/templates/db/migrate/create_survey_sections.rb +30 -0
  114. data/lib/generators/surveyor/templates/db/migrate/create_survey_translations.rb +19 -0
  115. data/lib/generators/surveyor/templates/db/migrate/create_surveys.rb +32 -0
  116. data/lib/generators/surveyor/templates/db/migrate/create_validation_conditions.rb +33 -0
  117. data/lib/generators/surveyor/templates/db/migrate/create_validations.rb +21 -0
  118. data/lib/generators/surveyor/templates/db/migrate/drop_unique_index_on_access_code_in_surveys.rb +10 -0
  119. data/lib/generators/surveyor/templates/db/migrate/update_blank_api_ids_on_question_group.rb +22 -0
  120. data/lib/generators/surveyor/templates/db/migrate/update_blank_versions_on_surveys.rb +13 -0
  121. data/lib/generators/surveyor/templates/surveys/EXTENDING_SURVEYOR.md +52 -0
  122. data/lib/generators/surveyor/templates/surveys/MODIFYING_SURVEYOR.md +91 -0
  123. data/lib/generators/surveyor/templates/surveys/date_survey.rb +16 -0
  124. data/lib/generators/surveyor/templates/surveys/kitchen_sink_survey.rb +280 -0
  125. data/lib/generators/surveyor/templates/surveys/languages.rb +14 -0
  126. data/lib/generators/surveyor/templates/surveys/quiz.rb +11 -0
  127. data/lib/generators/surveyor/templates/surveys/translations/languages.es.yml +18 -0
  128. data/lib/generators/surveyor/templates/surveys/translations/languages.he.yml +18 -0
  129. data/lib/generators/surveyor/templates/surveys/translations/languages.ko.yml +18 -0
  130. data/lib/generators/surveyor/templates/vendor/assets/stylesheets/custom.sass +5 -0
  131. data/lib/surveyor/acts_as_response.rb +19 -0
  132. data/lib/surveyor/common.rb +59 -0
  133. data/lib/surveyor/engine.rb +15 -0
  134. data/lib/surveyor/helpers/formtastic_custom_input.rb +13 -0
  135. data/lib/surveyor/helpers/surveyor_helper_methods.rb +86 -0
  136. data/lib/surveyor/models/answer_methods.rb +76 -0
  137. data/lib/surveyor/models/dependency_condition_methods.rb +65 -0
  138. data/lib/surveyor/models/dependency_methods.rb +57 -0
  139. data/lib/surveyor/models/question_group_methods.rb +62 -0
  140. data/lib/surveyor/models/question_methods.rb +103 -0
  141. data/lib/surveyor/models/response_methods.rb +125 -0
  142. data/lib/surveyor/models/response_set_methods.rb +187 -0
  143. data/lib/surveyor/models/survey_methods.rb +95 -0
  144. data/lib/surveyor/models/survey_section_methods.rb +48 -0
  145. data/lib/surveyor/models/survey_translation_methods.rb +28 -0
  146. data/lib/surveyor/models/validation_condition_methods.rb +42 -0
  147. data/lib/surveyor/models/validation_methods.rb +41 -0
  148. data/lib/surveyor/mustache_context.rb +13 -0
  149. data/lib/surveyor/parser.rb +427 -0
  150. data/lib/surveyor/redcap_parser.rb +288 -0
  151. data/lib/surveyor/surveyor_controller_methods.rb +237 -0
  152. data/lib/surveyor/unparser.rb +161 -0
  153. data/lib/surveyor/version.rb +3 -0
  154. data/lib/surveyor.rb +13 -0
  155. data/lib/tasks/surveyor_tasks.rake +88 -0
  156. data/rails/init.rb +1 -0
  157. data/spec/controllers/surveyor_controller_spec.rb +304 -0
  158. data/spec/factories.rb +166 -0
  159. data/spec/features/ajax_spec.rb +205 -0
  160. data/spec/features/api_spec.rb +157 -0
  161. data/spec/features/duplicate_prevention_spec.rb +50 -0
  162. data/spec/features/translations_spec.rb +116 -0
  163. data/spec/features/ui_spec.rb +466 -0
  164. data/spec/fixtures/REDCapDemoDatabase_DataDictionary.csv +127 -0
  165. data/spec/fixtures/chinese_survey.rb +14 -0
  166. data/spec/fixtures/everything.rb +215 -0
  167. data/spec/fixtures/favorites-ish.rb +22 -0
  168. data/spec/fixtures/favorites.rb +22 -0
  169. data/spec/fixtures/feelings.rb +38 -0
  170. data/spec/fixtures/lifestyle.rb +55 -0
  171. data/spec/fixtures/numbers.rb +21 -0
  172. data/spec/fixtures/redcap_new_headers.csv +1 -0
  173. data/spec/fixtures/redcap_siblings.csv +1 -0
  174. data/spec/fixtures/redcap_whitespace.csv +1 -0
  175. data/spec/helpers/formtastic_custom_input_spec.rb +15 -0
  176. data/spec/helpers/surveyor_helper_spec.rb +114 -0
  177. data/spec/lib/common_spec.rb +37 -0
  178. data/spec/lib/parser_spec.rb +393 -0
  179. data/spec/lib/rake_kitchen_sink.rb +40 -0
  180. data/spec/lib/redcap_parser_spec.rb +129 -0
  181. data/spec/lib/tasks_spec.rake +26 -0
  182. data/spec/lib/unparser_spec.rb +126 -0
  183. data/spec/models/answer_spec.rb +144 -0
  184. data/spec/models/dependency_condition_spec.rb +428 -0
  185. data/spec/models/dependency_spec.rb +90 -0
  186. data/spec/models/question_group_spec.rb +66 -0
  187. data/spec/models/question_spec.rb +176 -0
  188. data/spec/models/response_set_spec.rb +447 -0
  189. data/spec/models/response_spec.rb +205 -0
  190. data/spec/models/survey_section_spec.rb +58 -0
  191. data/spec/models/survey_spec.rb +155 -0
  192. data/spec/models/validation_condition_spec.rb +98 -0
  193. data/spec/models/validation_spec.rb +64 -0
  194. data/spec/rcov.opts +2 -0
  195. data/spec/spec.opts +4 -0
  196. data/spec/spec_helper.rb +99 -0
  197. data/spec/support/shared.rb +5 -0
  198. data/spec/support/surveyor_api_helpers.rb +15 -0
  199. data/spec/support/surveyor_ui_helpers.rb +108 -0
  200. data/spec/support/wait_for_ajax.rb +11 -0
  201. data/surveyor.gemspec +40 -0
  202. data/switch.sh +53 -0
  203. metadata +510 -0
@@ -0,0 +1,288 @@
1
+ %w(survey survey_section question_group question dependency dependency_condition answer validation validation_condition).each {|model| require model }
2
+ require 'active_support' # for humanize
3
+ module Surveyor
4
+ class RedcapParserError < StandardError; end
5
+ class RedcapParser
6
+ class << self; attr_accessor :options end
7
+
8
+ # Attributes
9
+ attr_accessor :context
10
+
11
+ # Class methods
12
+ def self.parse(str, filename, options={})
13
+ self.options = options
14
+ Surveyor::RedcapParser.rake_trace "\n"
15
+ Surveyor::RedcapParser.new.parse(str, filename)
16
+ Surveyor::RedcapParser.rake_trace "\n"
17
+ end
18
+ def self.rake_trace(str)
19
+ self.options ||= {}
20
+ print str if self.options[:trace] == true
21
+ end
22
+
23
+ # Instance methods
24
+ def initialize
25
+ self.context = {}
26
+ self.context[:dependency_conditions] = []
27
+ end
28
+ def parse(str, filename)
29
+ csvlib = Surveyor::Common.csv_impl
30
+ begin
31
+ csvlib.parse(str, :headers => :first_row, :return_headers => true, :header_converters => :symbol) do |r|
32
+ if r.header_row? # header row
33
+ return Surveyor::RedcapParser.rake_trace "Missing headers: #{missing_columns(r.headers).inspect}\n\n" unless missing_columns(r.headers).blank?
34
+ context[:survey] = Survey.new(:title => filename)
35
+ Surveyor::RedcapParser.rake_trace "survey_#{context[:survey].access_code} "
36
+ else # non-header rows
37
+ SurveySection.new.extend(SurveyorRedcapParserSurveySectionMethods).build_or_set(context, r)
38
+ Question.new.extend(SurveyorRedcapParserQuestionMethods).build_and_set(context, r)
39
+ Answer.new.extend(SurveyorRedcapParserAnswerMethods).build_and_set(context, r)
40
+ Validation.new.extend(SurveyorRedcapParserValidationMethods).build_and_set(context, r)
41
+ Dependency.new.extend(SurveyorRedcapParserDependencyMethods).build_and_set(context, r)
42
+ end
43
+ end
44
+ resolve_references
45
+ Surveyor::RedcapParser.rake_trace context[:survey].save ? "saved. " : " not saved! #{context[:survey].errors.full_messages.join(", ")} "
46
+ # Surveyor::RedcapParser.rake_trace context[:survey].sections.map(&:questions).flatten.map(&:answers).flatten.map{|x| x.errors.each_full{|y| y}.join}.join
47
+ rescue csvlib::MalformedCSVError
48
+ raise Surveyor::RedcapParserError, "Oops. Not a valid CSV file."
49
+ # ensure
50
+ end
51
+ return context[:survey]
52
+ end
53
+ def missing_columns(r)
54
+ missing = []
55
+ missing << "choices_or_calculations" unless r.map(&:to_s).include?("choices_or_calculations") or r.map(&:to_s).include?("choices_calculations_or_slider_labels")
56
+ missing << "text_validation_type" unless r.map(&:to_s).include?("text_validation_type") or r.map(&:to_s).include?("text_validation_type_or_show_slider_number")
57
+ missing += (static_required_columns - r.map(&:to_s))
58
+ end
59
+ def static_required_columns
60
+ # no longer requiring field_units
61
+ %w(variable__field_name form_name section_header field_type field_label field_note text_validation_min text_validation_max identifier branching_logic_show_field_only_if required_field)
62
+ end
63
+ def resolve_references
64
+ context[:dependency_conditions].each do |dc|
65
+ Surveyor::RedcapParser.rake_trace "resolve(#{dc.question_reference},#{dc.answer_reference})"
66
+ if dc.answer_reference.blank? and (context[:question_references][dc.question_reference].answers.size == 1)
67
+ Surveyor::RedcapParser.rake_trace "...found "
68
+ dc.question = context[:question_references][dc.question_reference]
69
+ dc.answer = dc.question.answers.first
70
+ elsif answer = context[:answer_references][dc.question_reference][dc.answer_reference]
71
+ Surveyor::RedcapParser.rake_trace "...found "
72
+ dc.answer = answer
73
+ dc.question = context[:question_references][dc.question_reference]
74
+ else
75
+ Surveyor::RedcapParser.rake_trace "\n!!! failed lookup for dependency_condition q: #{question_reference} a: #{question_reference}"
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ # Surveyor models with extra parsing methods
83
+
84
+ # SurveySection model
85
+ module SurveyorRedcapParserSurveySectionMethods
86
+ def build_or_set(context, r)
87
+ unless context[:survey_section] && context[:survey_section].reference_identifier == r[:form_name]
88
+ if match = context[:survey].sections.detect{|ss| ss.reference_identifier == r[:form_name]}
89
+ context[:current_survey_section] = match
90
+ else
91
+ self.attributes = (
92
+ {:title => r[:form_name].to_s.humanize,
93
+ :reference_identifier => r[:form_name],
94
+ :display_order => context[:survey].sections.size })
95
+ context[:survey].sections << context[:survey_section] = self
96
+ Surveyor::RedcapParser.rake_trace "survey_section_#{context[:survey_section].reference_identifier} "
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ # Question model
103
+ module SurveyorRedcapParserQuestionMethods
104
+ def build_and_set(context, r)
105
+ if !r[:section_header].blank?
106
+ context[:survey_section].questions.build({:display_type => "label", :text => r[:section_header], :display_order => context[:survey_section].questions.size})
107
+ Surveyor::RedcapParser.rake_trace "label_ "
108
+ end
109
+ self.attributes = ({
110
+ :reference_identifier => r[:variable__field_name],
111
+ :text => r[:field_label],
112
+ :help_text => r[:field_note],
113
+ :is_mandatory => (/^y/i.match r[:required_field]) ? true : false,
114
+ :pick => pick_from_field_type(r[:field_type]),
115
+ :display_type => display_type_from_field_type(r[:field_type]),
116
+ :display_order => context[:survey_section].questions.size
117
+ })
118
+ context[:survey_section].questions << context[:question] = self
119
+ unless context[:question].reference_identifier.blank?
120
+ context[:question_references] ||= {}
121
+ context[:question_references][context[:question].reference_identifier] = context[:question]
122
+ end
123
+ Surveyor::RedcapParser.rake_trace "question_#{context[:question].reference_identifier} "
124
+ end
125
+ def pick_from_field_type(ft)
126
+ {"checkbox" => :any, "radio" => :one}[ft] || :none
127
+ end
128
+ def display_type_from_field_type(ft)
129
+ {"text" => :string, "dropdown" => :dropdown, "notes" => :text}[ft]
130
+ end
131
+ end
132
+
133
+ # Dependency model
134
+ module SurveyorRedcapParserDependencyMethods
135
+ def build_and_set(context, r)
136
+ unless (bl = r[:branching_logic_show_field_only_if]).blank?
137
+ # TODO: forgot to tie rule key to component, counting on the sequence of components
138
+ letters = ('A'..'Z').to_a
139
+ hash = decompose_rule(bl)
140
+ self.attributes = {:rule => hash[:rule]}
141
+ context[:question].dependency = context[:dependency] = self
142
+ hash[:components].each do |component|
143
+ dc = context[:dependency].dependency_conditions.build(decompose_component(component).merge({ :rule_key => letters.shift } ))
144
+ context[:dependency_conditions] << dc
145
+ end
146
+ Surveyor::RedcapParser.rake_trace "dependency(#{hash[:rule]}) "
147
+ end
148
+ end
149
+ def decompose_component(str)
150
+ # [initial_52] = "1" or [f1_q15] = '' or [f1_q15] = '-2' or [hi_event1_type] <> ''
151
+ if match = str.match(/^\[(\w+)\] ?([!=><]+) ?['"](-?\w*)['"]$/)
152
+ {:question_reference => match[1], :operator => match[2].gsub(/^=$/, "==").gsub(/^<>$/, "!="), :answer_reference => match[3]}
153
+ # [initial_119(2)] = "1" or [hiprep_heat2(97)] = '1'
154
+ elsif match = str.match(/^\[(\w+)\((\w+)\)\] ?([!=><]+) ?['"]1['"]$/)
155
+ {:question_reference => match[1], :operator => match[3].gsub(/^=$/, "==").gsub(/^<>$/, "!="), :answer_reference => match[2]}
156
+ # [f1_q15] >= 21 or [f1_q15] >= -21
157
+ elsif match = str.match(/^\[(\w+)\] ?([!=><]+) ?(-?\d+)$/)
158
+ {:question_reference => match[1], :operator => match[2].gsub(/^=$/, "==").gsub(/^<>$/, "!="), :integer_value => match[3]}
159
+ else
160
+ Surveyor::RedcapParser.rake_trace "\n!!! skipping dependency_condition #{str}"
161
+ end
162
+ end
163
+ def decompose_rule(str)
164
+ # see spec/lib/redcap_parser_spec.rb for examples
165
+ letters = ('A'..'Z').to_a
166
+ rule = str
167
+ components = str.split(/\band\b|\bor\b|\((?!\d)|\)(?!\(|\])/).reject(&:blank?).map(&:strip)
168
+ components.each_with_index do |part, i|
169
+ # internal commas on the right side of the operator e.g. '[initial_189] = "1, 2, 3"'
170
+ if match = part.match(/^(\[[^\]]+\][^\"]+)"([0-9 ]+,[0-9 ,]+)"$/)
171
+ nums = match[2].split(",").map(&:strip)
172
+ components[i] = nums.map{|x| "#{match[1]}\"#{x}\""}
173
+ # sub in rule key
174
+ rule = rule.gsub(part, "(#{nums.map{letters.shift}.join(' and ')})")
175
+ # multiple internal parenthesis on the left e.g. '[initial_119(1)(2)(3)(4)(6)] = "1"'
176
+ elsif match = part.match(/^\[(\w+)(\(\d+\)\([\d\(\)]+)\]([^\"]+"\d+")$/)
177
+ nums = match[2].split(/\(|\)/).reject(&:blank?).map(&:strip)
178
+ components[i] = nums.map{|x| "[#{match[1]}(#{x})]#{match[3]}"}
179
+ # sub in rule key
180
+ rule = rule.gsub(part, "(#{nums.map{letters.shift}.join(' and ')})")
181
+ else
182
+ # 'or' on the right of the operator
183
+ components[i] = components[i-1].gsub(/"(\d+)"/, part) if part.match(/^"(\d+)"$/) && i != 0
184
+ # sub in rule key
185
+ rule = rule.gsub(part){letters.shift}
186
+ end
187
+ end
188
+ {:rule => rule, :components => components.flatten}
189
+ end
190
+ end
191
+
192
+ # DependencyCondition model
193
+ module SurveyorRedcapParserDependencyConditionMethods
194
+ DependencyCondition.instance_eval do
195
+ attr_accessor :question_reference, :answer_reference
196
+ end
197
+ end
198
+
199
+ # Answer model
200
+ module SurveyorRedcapParserAnswerMethods
201
+ def build_and_set(context, r)
202
+ case r[:field_type]
203
+ when "text"
204
+ self.attributes = {
205
+ :response_class => "string",
206
+ :text => "Text",
207
+ :display_order => context[:question].answers.size }
208
+ context[:question].answers << context[:answer] = self
209
+ when "notes"
210
+ self.attributes = {
211
+ :response_class => "text",
212
+ :text => "Notes",
213
+ :display_order => context[:question].answers.size }
214
+ context[:question].answers << context[:answer] = self
215
+ when "file"
216
+ Surveyor::RedcapParser.rake_trace "\n!!! skipping answer: file"
217
+ end
218
+ (r[:choices_or_calculations] || r[:choices_calculations_or_slider_labels]).to_s.split("|").each do |pair|
219
+ aref, atext = pair.split(",").map(&:strip)
220
+ if aref.blank? or atext.blank? or (aref.to_i.to_s != aref)
221
+ Surveyor::RedcapParser.rake_trace "\n!!! skipping answer #{pair}"
222
+ else
223
+ a = Answer.new({
224
+ :reference_identifier => aref,
225
+ :text => atext,
226
+ :display_order => context[:question].answers.size })
227
+ context[:question].answers << context[:answer] = a
228
+ unless context[:question].reference_identifier.blank? or aref.blank? or !context[:answer].valid?
229
+ context[:answer_references] ||= {}
230
+ context[:answer_references][context[:question].reference_identifier] ||= {}
231
+ context[:answer_references][context[:question].reference_identifier][aref] = context[:answer]
232
+ end
233
+ Surveyor::RedcapParser.rake_trace "#{context[:answer].errors.full_messages}, #{context[:answer].inspect}" unless context[:answer].valid?
234
+ Surveyor::RedcapParser.rake_trace "answer_#{context[:answer].reference_identifier} "
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+ # Validation model
241
+ module SurveyorRedcapParserValidationMethods
242
+ def build_and_set(context, r)
243
+ # text_validation_type text_validation_min text_validation_max
244
+ min = r[:text_validation_min].to_s.blank? ? nil : r[:text_validation_min].to_s
245
+ max = r[:text_validation_max].to_s.blank? ? nil : r[:text_validation_max].to_s
246
+ type = r[:text_validation_type].to_s.blank? ? nil : r[:text_validation_type].to_s
247
+ if min or max
248
+ context[:question].answers.each do |a|
249
+ self.rule = (min ? max ? "A and B" : "A" : "B")
250
+ a.validations << context[:validation] = self
251
+ context[:validation].validation_conditions.build(:rule_key => "A", :operator => ">=", :integer_value => min) if min
252
+ context[:validation].validation_conditions.build(:rule_key => "B", :operator => "<=", :integer_value => max) if max
253
+ end
254
+ elsif type
255
+ # date email integer number phone
256
+ case r[:text_validation_type]
257
+ when "date"
258
+ context[:question].display_type = :date if context[:question].display_type == :string
259
+ when "email"
260
+ context[:question].answers.each do |a|
261
+ self.rule = "A"
262
+ a.validations << context[:validation] = self
263
+ context[:validation].validation_conditions.build(:rule_key => "A", :operator => "=~", :regexp => "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$")
264
+ end
265
+ when "integer"
266
+ context[:question].display_type = :integer if context[:question].display_type == :string
267
+ context[:question].answers.each do |a|
268
+ self.rule = "A"
269
+ a.validations << context[:validation] = self
270
+ context[:validation].validation_conditions.build(:rule_key => "A", :operator => "=~", :regexp => "\d+")
271
+ end
272
+ when "number"
273
+ context[:question].display_type = :float if context[:question].display_type == :string
274
+ context[:question].answers.each do |a|
275
+ self.rule = "A"
276
+ a.validations << context[:validation] = self
277
+ context[:validation].validation_conditions.build(:rule_key => "A", :operator => "=~", :regexp => "^\d*(,\d{3})*(\.\d*)?$")
278
+ end
279
+ when "phone"
280
+ context[:question].answers.each do |a|
281
+ self.rule = "A"
282
+ a.validations << context[:validation] = self
283
+ context[:validation].validation_conditions.build(:rule_key => "A", :operator => "=~", :regexp => "\d{3}.*\d{4}")
284
+ end
285
+ end
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,237 @@
1
+ require 'rabl'
2
+ Rabl.register!
3
+ Rabl.configure {|config| config.include_child_root = false }
4
+ Rabl.configure {|config| config.include_json_root = false }
5
+ module Surveyor
6
+ module SurveyorControllerMethods
7
+ extend ActiveSupport::Concern
8
+ included do
9
+ before_action :get_current_user, :only => [:new, :create]
10
+ before_action :determine_if_javascript_is_enabled, :only => [:create, :update]
11
+ before_action :set_response_set_and_render_context, :only => [:edit, :show]
12
+
13
+ layout 'surveyor_default'
14
+ before_action :set_locale
15
+ end
16
+
17
+ # Actions
18
+ def new
19
+ @surveys_by_access_code = Survey.order("created_at DESC, survey_version DESC").to_a.group_by(&:access_code)
20
+ redirect_to surveyor_index unless surveyor_index == surveyor.available_surveys_path
21
+ end
22
+
23
+ def create
24
+ surveys = Survey.where(:access_code => params[:survey_code]).order("survey_version DESC")
25
+ if params[:survey_version].blank?
26
+ @survey = surveys.first
27
+ else
28
+ @survey = surveys.where(:survey_version => params[:survey_version]).first
29
+ end
30
+ @response_set = ResponseSet.
31
+ create(:survey => @survey, :user_id => (@current_user.nil? ? @current_user : @current_user.id))
32
+ if (@survey && @response_set)
33
+ flash[:notice] = t('surveyor.survey_started_success')
34
+ redirect_to(surveyor.edit_my_survey_path(
35
+ :survey_code => @survey.access_code, :response_set_code => @response_set.access_code))
36
+ else
37
+ flash[:notice] = t('surveyor.Unable_to_find_that_survey')
38
+ redirect_to surveyor_index
39
+ end
40
+ end
41
+
42
+ def show
43
+ # @response_set is set in before_action - set_response_set_and_render_context
44
+ if @response_set
45
+ @survey = @response_set.survey
46
+ respond_to do |format|
47
+ format.html #{render :action => :show}
48
+ format.csv {
49
+ send_data(@response_set.to_csv, :type => 'text/csv; charset=utf-8; header=present',
50
+ :filename => "#{@response_set.updated_at.strftime('%Y-%m-%d')}_#{@response_set.access_code}.csv")
51
+ }
52
+ format.json
53
+ end
54
+ else
55
+ flash[:notice] = t('surveyor.unable_to_find_your_responses')
56
+ redirect_to surveyor_index
57
+ end
58
+ end
59
+
60
+ def edit
61
+ # @response_set is set in before_action - set_response_set_and_render_context
62
+ if @response_set
63
+ @sections = SurveySection.where(survey_id: @response_set.survey_id).includes([:survey, {questions: [{answers: :question}, {question_group: :dependency}, :dependency]}])
64
+ @section = (section_id_from(params) ? @sections.where(id: section_id_from(params)).first : @sections.first) || @sections.first
65
+ @survey = @section.survey
66
+ set_dependents
67
+ else
68
+ flash[:notice] = t('surveyor.unable_to_find_your_responses')
69
+ redirect_to surveyor_index
70
+ end
71
+ end
72
+
73
+ def update
74
+ question_ids_for_dependencies = (params[:r] || []).map{|k,v| v["question_id"] }.compact.uniq
75
+ saved = load_and_update_response_set_with_retries
76
+
77
+ return redirect_with_message(surveyor_finish, :notice, t('surveyor.completed_survey')) if saved && params[:finish]
78
+
79
+ respond_to do |format|
80
+ format.html do
81
+ if @response_set.nil?
82
+ return redirect_with_message(surveyor.available_surveys_path, :notice, t('surveyor.unable_to_find_your_responses'))
83
+ else
84
+ flash[:notice] = t('surveyor.unable_to_update_survey') unless saved
85
+ redirect_to surveyor.edit_my_survey_path(:anchor => anchor_from(params[:section]), :section => section_id_from(params))
86
+ end
87
+ end
88
+ format.js do
89
+ if @response_set
90
+ render :json => @response_set.reload.all_dependencies(question_ids_for_dependencies)
91
+ else
92
+ render :text => "No response set #{params[:response_set_code]}",
93
+ :status => 404
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ def load_and_update_response_set_with_retries(remaining=2)
100
+ begin
101
+ load_and_update_response_set
102
+ rescue ActiveRecord::StatementInvalid => e
103
+ if remaining > 0
104
+ load_and_update_response_set_with_retries(remaining - 1)
105
+ else
106
+ raise e
107
+ end
108
+ end
109
+ end
110
+
111
+ def load_and_update_response_set
112
+ ResponseSet.transaction do
113
+ @response_set = ResponseSet.includes({:responses => :answer}).where(:access_code => params[:response_set_code]).first
114
+ if @response_set
115
+ saved = true
116
+ if params[:r]
117
+ @response_set.update_from_ui_hash(params[:r])
118
+ end
119
+ if params[:finish]
120
+ @response_set.complete!
121
+ saved &= @response_set.save
122
+ end
123
+ saved
124
+ else
125
+ false
126
+ end
127
+ end
128
+ end
129
+ private :load_and_update_response_set
130
+
131
+ def export
132
+ surveys = Survey.where(:access_code => params[:survey_code]).order("survey_version DESC")
133
+ s = params[:survey_version].blank? ? surveys.first : surveys.where(:survey_version => params[:survey_version]).first
134
+ render_404 and return if s.blank?
135
+ @survey = s.filtered_for_json
136
+ end
137
+
138
+ def render_404
139
+ head :status => 404
140
+ true
141
+ end
142
+
143
+ def url_options
144
+ ((I18n.locale == I18n.default_locale) ? {} : {:locale => I18n.locale}).merge(super)
145
+ end
146
+
147
+ private
148
+
149
+ # This is a hook method for surveyor-using applications to override and provide the context object
150
+ def render_context
151
+ nil
152
+ end
153
+
154
+ # Filters
155
+ def get_current_user
156
+ @current_user = self.respond_to?(:current_user) ? self.current_user : nil
157
+ end
158
+
159
+ def set_response_set_and_render_context
160
+ @response_set = ResponseSet.includes({:responses => [:question, :answer]}).where(:access_code => params[:response_set_code]).first
161
+ @render_context = render_context
162
+ end
163
+
164
+ def set_locale
165
+ if params[:new_locale]
166
+ I18n.locale = params[:new_locale]
167
+ elsif params[:locale]
168
+ I18n.locale = params[:locale]
169
+ else
170
+ I18n.locale = I18n.default_locale
171
+ end
172
+ end
173
+
174
+ # Params: the name of some submit buttons store the section we'd like to go
175
+ # to. for repeater questions, an anchor to the repeater group is also stored
176
+ # e.g. params[:section] = {"1"=>{"question_group_1"=>"<= add row"}}
177
+ def section_id_from(p = {})
178
+ if p[:section] && p[:section].respond_to?(:keys)
179
+ p[:section].keys.first
180
+ elsif p[:section]
181
+ p[:section]
182
+ elsif p[:current_section]
183
+ p[:current_section]
184
+ end
185
+ end
186
+
187
+ def anchor_from(p)
188
+ p.respond_to?(:keys) && p[p.keys.first].respond_to?(:keys) ? p[p.keys.first].keys.first : nil
189
+ end
190
+
191
+ def surveyor_index
192
+ surveyor.available_surveys_path
193
+ end
194
+ def surveyor_finish
195
+ surveyor.available_surveys_path
196
+ end
197
+
198
+ def redirect_with_message(path, message_type, message)
199
+ respond_to do |format|
200
+ format.html do
201
+ flash[message_type] = message if !message.blank? and !message_type.blank?
202
+ redirect_to path
203
+ end
204
+ format.js do
205
+ render :text => message, :status => 403
206
+ end
207
+ end
208
+ end
209
+
210
+ ##
211
+ # @dependents are necessary in case the client does not have javascript enabled
212
+ # Whether or not javascript is enabled is determined by a hidden field set in the surveyor/edit.html form
213
+ def set_dependents
214
+ if session[:surveyor_javascript] && session[:surveyor_javascript] == "enabled"
215
+ @dependents = []
216
+ else
217
+ @dependents = get_unanswered_dependencies_minus_section_questions
218
+ end
219
+ end
220
+
221
+ def get_unanswered_dependencies_minus_section_questions
222
+ @response_set.unanswered_dependencies - @section.questions || []
223
+ end
224
+
225
+ ##
226
+ # If the hidden field surveyor_javascript_enabled is set to true
227
+ # cf. surveyor/edit.html.haml
228
+ # the set the session variable [:surveyor_javascript] to "enabled"
229
+ def determine_if_javascript_is_enabled
230
+ if params[:surveyor_javascript_enabled] && params[:surveyor_javascript_enabled].to_s == "true"
231
+ session[:surveyor_javascript] = "enabled"
232
+ else
233
+ session[:surveyor_javascript] = "not_enabled"
234
+ end
235
+ end
236
+ end
237
+ end