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.
- checksums.yaml +7 -0
- data/.gitignore +28 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +198 -0
- data/Gemfile +5 -0
- data/Gemfile.rails_version +27 -0
- data/MIT-LICENSE +20 -0
- data/README.md +166 -0
- data/Rakefile +106 -0
- data/app/controllers/surveyor_controller.rb +5 -0
- data/app/helpers/results_helper.rb +20 -0
- data/app/helpers/survey_form_builder.rb +37 -0
- data/app/helpers/surveyor_helper.rb +3 -0
- data/app/inputs/quiet_input.rb +5 -0
- data/app/inputs/surveyor_check_boxes_input.rb +35 -0
- data/app/inputs/surveyor_radio_input.rb +18 -0
- data/app/models/answer.rb +3 -0
- data/app/models/dependency.rb +3 -0
- data/app/models/dependency_condition.rb +3 -0
- data/app/models/question.rb +3 -0
- data/app/models/question_group.rb +4 -0
- data/app/models/response.rb +4 -0
- data/app/models/response_set.rb +3 -0
- data/app/models/survey.rb +3 -0
- data/app/models/survey_section.rb +4 -0
- data/app/models/survey_section_sweeper.rb +15 -0
- data/app/models/survey_translation.rb +4 -0
- data/app/models/validation.rb +3 -0
- data/app/models/validation_condition.rb +3 -0
- data/app/views/layouts/results.html.erb +13 -0
- data/app/views/layouts/surveyor_default.html.erb +12 -0
- data/app/views/partials/_answer.html.haml +25 -0
- data/app/views/partials/_dependents.html.haml +5 -0
- data/app/views/partials/_question.html.haml +28 -0
- data/app/views/partials/_question_group.html.haml +44 -0
- data/app/views/partials/_section.html.haml +12 -0
- data/app/views/partials/_section_menu.html.haml +12 -0
- data/app/views/surveyor/edit.html.haml +24 -0
- data/app/views/surveyor/export.json.rabl +85 -0
- data/app/views/surveyor/new.html.haml +24 -0
- data/app/views/surveyor/show.html.haml +74 -0
- data/app/views/surveyor/show.json.rabl +15 -0
- data/ci-exec.sh +56 -0
- data/config/routes.rb +8 -0
- data/cucumber.yml +10 -0
- data/doc/REPRESENTATIONS.md +34 -0
- data/doc/api_id_schema.json +7 -0
- data/doc/question types.png +0 -0
- data/doc/response_set_schema.json +54 -0
- data/doc/surveyor question combinations.png +0 -0
- data/doc/surveyor reject or delete decision matrix.png +0 -0
- data/doc/surveyor_models.png +0 -0
- data/doc/surveyor_models2.png +0 -0
- data/doc/surveyor_timestamp_schema.json +9 -0
- data/features/ajax_submissions.feature +140 -0
- data/features/export_to_json.feature +344 -0
- data/features/internationalization.feature +121 -0
- data/features/no_duplicates.feature +110 -0
- data/features/show_survey.feature +71 -0
- data/features/step_definitions/parser_steps.rb +145 -0
- data/features/step_definitions/surveyor_steps.rb +325 -0
- data/features/step_definitions/ui_steps.rb +25 -0
- data/features/step_definitions/web_steps.rb +225 -0
- data/features/support/REDCapDemoDatabase_DataDictionary.csv +127 -0
- data/features/support/database_cleaner.rb +16 -0
- data/features/support/env.rb +56 -0
- data/features/support/hooks.rb +4 -0
- data/features/support/paths.rb +39 -0
- data/features/support/redcap_new_headers.csv +1 -0
- data/features/support/redcap_siblings.csv +1 -0
- data/features/support/redcap_whitespace.csv +1 -0
- data/features/support/selectors.rb +39 -0
- data/features/support/simultaneous_ajax.rb +101 -0
- data/features/support/single_quit_selenium_driver.rb +23 -0
- data/features/support/slow_updates.rb +18 -0
- data/features/surveyor.feature +895 -0
- data/features/surveyor_dependencies.feature +476 -0
- data/features/surveyor_parser.feature +504 -0
- data/features/z_redcap_parser.feature +62 -0
- data/lib/assets/images/surveyor/next.gif +0 -0
- data/lib/assets/images/surveyor/prev.gif +0 -0
- data/lib/assets/images/surveyor/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/lib/assets/images/surveyor/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/lib/assets/images/surveyor/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/lib/assets/images/surveyor/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/lib/assets/images/surveyor/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/lib/assets/images/surveyor/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/lib/assets/images/surveyor/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/lib/assets/images/surveyor/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/lib/assets/images/surveyor/ui-icons_222222_256x240.png +0 -0
- data/lib/assets/images/surveyor/ui-icons_2e83ff_256x240.png +0 -0
- data/lib/assets/images/surveyor/ui-icons_454545_256x240.png +0 -0
- data/lib/assets/images/surveyor/ui-icons_888888_256x240.png +0 -0
- data/lib/assets/images/surveyor/ui-icons_cd0a0a_256x240.png +0 -0
- data/lib/assets/javascripts/surveyor/jquery-1.9.0.js +9555 -0
- data/lib/assets/javascripts/surveyor/jquery-ui-1.10.0.custom.js +14850 -0
- data/lib/assets/javascripts/surveyor/jquery-ui-timepicker-addon.js +1919 -0
- data/lib/assets/javascripts/surveyor/jquery.maskedinput.js +338 -0
- data/lib/assets/javascripts/surveyor/jquery.selectToUISlider.js +240 -0
- data/lib/assets/javascripts/surveyor/jquery.surveyor.js +154 -0
- data/lib/assets/stylesheets/surveyor.sass +132 -0
- data/lib/assets/stylesheets/surveyor/jquery-ui-1.10.0.custom.css +1174 -0
- data/lib/assets/stylesheets/surveyor/jquery-ui-timepicker-addon.css +11 -0
- data/lib/assets/stylesheets/surveyor/reset.css +48 -0
- data/lib/assets/stylesheets/surveyor/results.css +125 -0
- data/lib/assets/stylesheets/surveyor/ui.slider.extras.css +110 -0
- data/lib/generators/surveyor/custom_generator.rb +18 -0
- data/lib/generators/surveyor/install_generator.rb +101 -0
- data/lib/generators/surveyor/templates/app/assets/javascripts/surveyor_all.js +6 -0
- data/lib/generators/surveyor/templates/app/assets/stylesheets/surveyor_all.css +9 -0
- data/lib/generators/surveyor/templates/app/controllers/surveyor_controller.rb +40 -0
- data/lib/generators/surveyor/templates/app/views/layouts/surveyor_custom.html.erb +13 -0
- data/lib/generators/surveyor/templates/config/locales/surveyor_en.yml +19 -0
- data/lib/generators/surveyor/templates/config/locales/surveyor_es.yml +19 -0
- data/lib/generators/surveyor/templates/config/locales/surveyor_he.yml +19 -0
- data/lib/generators/surveyor/templates/config/locales/surveyor_ko.yml +19 -0
- data/lib/generators/surveyor/templates/db/migrate/add_api_id_to_question_groups.rb +10 -0
- data/lib/generators/surveyor/templates/db/migrate/add_api_ids.rb +14 -0
- data/lib/generators/surveyor/templates/db/migrate/add_api_ids_to_response_sets_and_responses.rb +12 -0
- data/lib/generators/surveyor/templates/db/migrate/add_correct_answer_id_to_questions.rb +10 -0
- data/lib/generators/surveyor/templates/db/migrate/add_default_value_to_answers.rb +10 -0
- data/lib/generators/surveyor/templates/db/migrate/add_display_order_to_surveys.rb +10 -0
- data/lib/generators/surveyor/templates/db/migrate/add_display_type_to_answers.rb +14 -0
- data/lib/generators/surveyor/templates/db/migrate/add_index_to_response_sets.rb +10 -0
- data/lib/generators/surveyor/templates/db/migrate/add_index_to_surveys.rb +10 -0
- data/lib/generators/surveyor/templates/db/migrate/add_input_mask_attributes_to_answer.rb +12 -0
- data/lib/generators/surveyor/templates/db/migrate/add_section_id_to_responses.rb +12 -0
- data/lib/generators/surveyor/templates/db/migrate/add_unique_index_on_access_code_and_version_in_surveys.rb +10 -0
- data/lib/generators/surveyor/templates/db/migrate/add_unique_indicies.rb +18 -0
- data/lib/generators/surveyor/templates/db/migrate/add_version_to_surveys.rb +10 -0
- data/lib/generators/surveyor/templates/db/migrate/api_ids_must_be_unique.rb +23 -0
- data/lib/generators/surveyor/templates/db/migrate/create_answers.rb +38 -0
- data/lib/generators/surveyor/templates/db/migrate/create_dependencies.rb +23 -0
- data/lib/generators/surveyor/templates/db/migrate/create_dependency_conditions.rb +30 -0
- data/lib/generators/surveyor/templates/db/migrate/create_question_groups.rb +28 -0
- data/lib/generators/surveyor/templates/db/migrate/create_questions.rb +37 -0
- data/lib/generators/surveyor/templates/db/migrate/create_response_sets.rb +23 -0
- data/lib/generators/surveyor/templates/db/migrate/create_responses.rb +34 -0
- data/lib/generators/surveyor/templates/db/migrate/create_survey_sections.rb +30 -0
- data/lib/generators/surveyor/templates/db/migrate/create_survey_translations.rb +19 -0
- data/lib/generators/surveyor/templates/db/migrate/create_surveys.rb +32 -0
- data/lib/generators/surveyor/templates/db/migrate/create_validation_conditions.rb +33 -0
- data/lib/generators/surveyor/templates/db/migrate/create_validations.rb +21 -0
- data/lib/generators/surveyor/templates/db/migrate/drop_unique_index_on_access_code_in_surveys.rb +10 -0
- data/lib/generators/surveyor/templates/db/migrate/update_blank_api_ids_on_question_group.rb +22 -0
- data/lib/generators/surveyor/templates/db/migrate/update_blank_versions_on_surveys.rb +13 -0
- data/lib/generators/surveyor/templates/surveys/EXTENDING_SURVEYOR.md +52 -0
- data/lib/generators/surveyor/templates/surveys/MODIFYING_SURVEYOR.md +91 -0
- data/lib/generators/surveyor/templates/surveys/date_survey.rb +16 -0
- data/lib/generators/surveyor/templates/surveys/kitchen_sink_survey.rb +280 -0
- data/lib/generators/surveyor/templates/surveys/languages.rb +14 -0
- data/lib/generators/surveyor/templates/surveys/quiz.rb +11 -0
- data/lib/generators/surveyor/templates/surveys/translations/languages.es.yml +18 -0
- data/lib/generators/surveyor/templates/surveys/translations/languages.he.yml +18 -0
- data/lib/generators/surveyor/templates/surveys/translations/languages.ko.yml +18 -0
- data/lib/generators/surveyor/templates/vendor/assets/stylesheets/custom.sass +5 -0
- data/lib/surveyor.rb +13 -0
- data/lib/surveyor/acts_as_response.rb +17 -0
- data/lib/surveyor/common.rb +59 -0
- data/lib/surveyor/engine.rb +10 -0
- data/lib/surveyor/helpers/asset_pipeline.rb +18 -0
- data/lib/surveyor/helpers/formtastic_custom_input.rb +13 -0
- data/lib/surveyor/helpers/surveyor_helper_methods.rb +103 -0
- data/lib/surveyor/models/answer_methods.rb +83 -0
- data/lib/surveyor/models/dependency_condition_methods.rb +69 -0
- data/lib/surveyor/models/dependency_methods.rb +57 -0
- data/lib/surveyor/models/question_group_methods.rb +58 -0
- data/lib/surveyor/models/question_methods.rb +112 -0
- data/lib/surveyor/models/response_methods.rb +126 -0
- data/lib/surveyor/models/response_set_methods.rb +188 -0
- data/lib/surveyor/models/survey_methods.rb +100 -0
- data/lib/surveyor/models/survey_section_methods.rb +53 -0
- data/lib/surveyor/models/survey_translation_methods.rb +30 -0
- data/lib/surveyor/models/validation_condition_methods.rb +54 -0
- data/lib/surveyor/models/validation_methods.rb +45 -0
- data/lib/surveyor/mustache_context.rb +11 -0
- data/lib/surveyor/parser.rb +427 -0
- data/lib/surveyor/redcap_parser.rb +288 -0
- data/lib/surveyor/surveyor_controller_methods.rb +236 -0
- data/lib/surveyor/unparser.rb +147 -0
- data/lib/surveyor/version.rb +3 -0
- data/lib/tasks/surveyor_tasks.rake +88 -0
- data/rails/init.rb +1 -0
- data/spec/controllers/surveyor_controller_spec.rb +311 -0
- data/spec/factories.rb +166 -0
- data/spec/helpers/formtastic_custom_input_spec.rb +15 -0
- data/spec/helpers/surveyor_helper_spec.rb +117 -0
- data/spec/lib/benchmark_spec.rb +22 -0
- data/spec/lib/chinese_survey.rb +14 -0
- data/spec/lib/common_spec.rb +34 -0
- data/spec/lib/parser_spec.rb +204 -0
- data/spec/lib/rake_kitchen_sink.rb +40 -0
- data/spec/lib/redcap_parser_spec.rb +75 -0
- data/spec/lib/tasks_spec.rake +26 -0
- data/spec/lib/unparser_spec.rb +126 -0
- data/spec/models/answer_spec.rb +174 -0
- data/spec/models/dependency_condition_spec.rb +439 -0
- data/spec/models/dependency_spec.rb +101 -0
- data/spec/models/question_group_spec.rb +93 -0
- data/spec/models/question_spec.rb +206 -0
- data/spec/models/response_set_spec.rb +477 -0
- data/spec/models/response_spec.rb +218 -0
- data/spec/models/survey_section_spec.rb +85 -0
- data/spec/models/survey_spec.rb +191 -0
- data/spec/models/validation_condition_spec.rb +113 -0
- data/spec/models/validation_spec.rb +74 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +56 -0
- data/stacktests.sh +65 -0
- data/surveyor.gemspec +53 -0
- metadata +601 -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,236 @@
|
|
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
|
+
def self.included(base)
|
8
|
+
base.send :before_filter, :get_current_user, :only => [:new, :create]
|
9
|
+
base.send :before_filter, :determine_if_javascript_is_enabled, :only => [:create, :update]
|
10
|
+
base.send :before_filter, :set_response_set_and_render_context, :only => [:edit, :show]
|
11
|
+
base.send :layout, 'surveyor_default'
|
12
|
+
base.send :before_filter, :set_locale
|
13
|
+
end
|
14
|
+
|
15
|
+
# Actions
|
16
|
+
def new
|
17
|
+
@surveys_by_access_code = Survey.order("created_at DESC, survey_version DESC").group_by(&:access_code)
|
18
|
+
redirect_to surveyor_index unless surveyor_index == surveyor.available_surveys_path
|
19
|
+
end
|
20
|
+
|
21
|
+
def create
|
22
|
+
surveys = Survey.where(:access_code => params[:survey_code]).order("survey_version DESC")
|
23
|
+
if params[:survey_version].blank?
|
24
|
+
@survey = surveys.first
|
25
|
+
else
|
26
|
+
@survey = surveys.where(:survey_version => params[:survey_version]).first
|
27
|
+
end
|
28
|
+
@response_set = ResponseSet.
|
29
|
+
create(:survey => @survey, :user_id => (@current_user.nil? ? @current_user : @current_user.id))
|
30
|
+
if (@survey && @response_set)
|
31
|
+
flash[:notice] = t('surveyor.survey_started_success')
|
32
|
+
redirect_to(surveyor.edit_my_survey_path(
|
33
|
+
:survey_code => @survey.access_code, :response_set_code => @response_set.access_code))
|
34
|
+
else
|
35
|
+
flash[:notice] = t('surveyor.Unable_to_find_that_survey')
|
36
|
+
redirect_to surveyor_index
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def show
|
41
|
+
# @response_set is set in before_filter - set_response_set_and_render_context
|
42
|
+
if @response_set
|
43
|
+
@survey = @response_set.survey
|
44
|
+
respond_to do |format|
|
45
|
+
format.html #{render :action => :show}
|
46
|
+
format.csv {
|
47
|
+
send_data(@response_set.to_csv, :type => 'text/csv; charset=utf-8; header=present',
|
48
|
+
:filename => "#{@response_set.updated_at.strftime('%Y-%m-%d')}_#{@response_set.access_code}.csv")
|
49
|
+
}
|
50
|
+
format.json
|
51
|
+
end
|
52
|
+
else
|
53
|
+
flash[:notice] = t('surveyor.unable_to_find_your_responses')
|
54
|
+
redirect_to surveyor_index
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def edit
|
59
|
+
# @response_set is set in before_filter - set_response_set_and_render_context
|
60
|
+
if @response_set
|
61
|
+
@survey = Survey.with_sections.find_by_id(@response_set.survey_id)
|
62
|
+
@sections = @survey.sections
|
63
|
+
@section = section_id_from(params) ? @sections.with_includes.find(section_id_from(params)) : @sections.with_includes.first
|
64
|
+
set_dependents
|
65
|
+
else
|
66
|
+
flash[:notice] = t('surveyor.unable_to_find_your_responses')
|
67
|
+
redirect_to surveyor_index
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def update
|
72
|
+
question_ids_for_dependencies = (params[:r] || []).map{|k,v| v["question_id"] }.compact.uniq
|
73
|
+
saved = load_and_update_response_set_with_retries
|
74
|
+
|
75
|
+
return redirect_with_message(surveyor_finish, :notice, t('surveyor.completed_survey')) if saved && params[:finish]
|
76
|
+
|
77
|
+
respond_to do |format|
|
78
|
+
format.html do
|
79
|
+
if @response_set.nil?
|
80
|
+
return redirect_with_message(surveyor.available_surveys_path, :notice, t('surveyor.unable_to_find_your_responses'))
|
81
|
+
else
|
82
|
+
flash[:notice] = t('surveyor.unable_to_update_survey') unless saved
|
83
|
+
redirect_to surveyor.edit_my_survey_path(:anchor => anchor_from(params[:section]), :section => section_id_from(params))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
format.js do
|
87
|
+
if @response_set
|
88
|
+
render :json => @response_set.reload.all_dependencies(question_ids_for_dependencies)
|
89
|
+
else
|
90
|
+
render :text => "No response set #{params[:response_set_code]}",
|
91
|
+
:status => 404
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def load_and_update_response_set_with_retries(remaining=2)
|
98
|
+
begin
|
99
|
+
load_and_update_response_set
|
100
|
+
rescue ActiveRecord::StatementInvalid => e
|
101
|
+
if remaining > 0
|
102
|
+
load_and_update_response_set_with_retries(remaining - 1)
|
103
|
+
else
|
104
|
+
raise e
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def load_and_update_response_set
|
110
|
+
ResponseSet.transaction do
|
111
|
+
@response_set = ResponseSet.includes(:responses => :answer).find_by(:access_code => params[:response_set_code])
|
112
|
+
if @response_set
|
113
|
+
saved = true
|
114
|
+
if params[:r]
|
115
|
+
@response_set.update_from_ui_hash(params[:r])
|
116
|
+
end
|
117
|
+
if params[:finish]
|
118
|
+
@response_set.complete!
|
119
|
+
saved &= @response_set.save
|
120
|
+
end
|
121
|
+
saved
|
122
|
+
else
|
123
|
+
false
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
private :load_and_update_response_set
|
128
|
+
|
129
|
+
def export
|
130
|
+
surveys = Survey.where(:access_code => params[:survey_code]).order("survey_version DESC")
|
131
|
+
s = params[:survey_version].blank? ? surveys.first : surveys.where(:survey_version => params[:survey_version]).first
|
132
|
+
render_404 and return if s.blank?
|
133
|
+
@survey = s.filtered_for_json
|
134
|
+
end
|
135
|
+
|
136
|
+
def render_404
|
137
|
+
head :status => 404
|
138
|
+
true
|
139
|
+
end
|
140
|
+
|
141
|
+
def url_options
|
142
|
+
((I18n.locale == I18n.default_locale) ? {} : {:locale => I18n.locale}).merge(super)
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
# This is a hook method for surveyor-using applications to override and provide the context object
|
148
|
+
def render_context
|
149
|
+
nil
|
150
|
+
end
|
151
|
+
|
152
|
+
# Filters
|
153
|
+
def get_current_user
|
154
|
+
@current_user = self.respond_to?(:current_user) ? self.current_user : nil
|
155
|
+
end
|
156
|
+
|
157
|
+
def set_response_set_and_render_context
|
158
|
+
@response_set = ResponseSet.includes(:responses => [:question, :answer])
|
159
|
+
.find_by(:access_code => params[:response_set_code])
|
160
|
+
@render_context = render_context
|
161
|
+
end
|
162
|
+
|
163
|
+
def set_locale
|
164
|
+
if params[:new_locale]
|
165
|
+
I18n.locale = params[:new_locale]
|
166
|
+
elsif params[:locale]
|
167
|
+
I18n.locale = params[:locale]
|
168
|
+
else
|
169
|
+
I18n.locale = I18n.default_locale
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Params: the name of some submit buttons store the section we'd like to go
|
174
|
+
# to. for repeater questions, an anchor to the repeater group is also stored
|
175
|
+
# e.g. params[:section] = {"1"=>{"question_group_1"=>"<= add row"}}
|
176
|
+
def section_id_from(p = {})
|
177
|
+
if p[:section] && p[:section].respond_to?(:keys)
|
178
|
+
p[:section].keys.first
|
179
|
+
elsif p[:section]
|
180
|
+
p[:section]
|
181
|
+
elsif p[:current_section]
|
182
|
+
p[:current_section]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def anchor_from(p)
|
187
|
+
p.respond_to?(:keys) && p[p.keys.first].respond_to?(:keys) ? p[p.keys.first].keys.first : nil
|
188
|
+
end
|
189
|
+
|
190
|
+
def surveyor_index
|
191
|
+
surveyor.available_surveys_path
|
192
|
+
end
|
193
|
+
def surveyor_finish
|
194
|
+
surveyor.available_surveys_path
|
195
|
+
end
|
196
|
+
|
197
|
+
def redirect_with_message(path, message_type, message)
|
198
|
+
respond_to do |format|
|
199
|
+
format.html do
|
200
|
+
flash[message_type] = message if !message.blank? and !message_type.blank?
|
201
|
+
redirect_to path
|
202
|
+
end
|
203
|
+
format.js do
|
204
|
+
render :text => message, :status => 403
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# @dependents are necessary in case the client does not have javascript enabled
|
211
|
+
# Whether or not javascript is enabled is determined by a hidden field set in the surveyor/edit.html form
|
212
|
+
def set_dependents
|
213
|
+
if session[:surveyor_javascript] && session[:surveyor_javascript] == "enabled"
|
214
|
+
@dependents = []
|
215
|
+
else
|
216
|
+
@dependents = get_unanswered_dependencies_minus_section_questions
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def get_unanswered_dependencies_minus_section_questions
|
221
|
+
@response_set.unanswered_dependencies - @section.questions || []
|
222
|
+
end
|
223
|
+
|
224
|
+
##
|
225
|
+
# If the hidden field surveyor_javascript_enabled is set to true
|
226
|
+
# cf. surveyor/edit.html.haml
|
227
|
+
# the set the session variable [:surveyor_javascript] to "enabled"
|
228
|
+
def determine_if_javascript_is_enabled
|
229
|
+
if params[:surveyor_javascript_enabled] && params[:surveyor_javascript_enabled].to_s == "true"
|
230
|
+
session[:surveyor_javascript] = "enabled"
|
231
|
+
else
|
232
|
+
session[:surveyor_javascript] = "not_enabled"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|