hssc_surveyor 1.4.1.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +28 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +198 -0
- data/Gemfile +5 -0
- data/Gemfile.rails_version +19 -0
- data/MIT-LICENSE +20 -0
- data/README.md +186 -0
- data/Rakefile +105 -0
- data/app/controllers/surveyor_controller.rb +6 -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 +4 -0
- data/app/models/dependency.rb +4 -0
- data/app/models/dependency_condition.rb +4 -0
- data/app/models/question.rb +4 -0
- data/app/models/question_group.rb +5 -0
- data/app/models/response.rb +5 -0
- data/app/models/response_set.rb +4 -0
- data/app/models/survey.rb +4 -0
- data/app/models/survey_section.rb +5 -0
- data/app/models/survey_section_sweeper.rb +15 -0
- data/app/models/survey_translation.rb +5 -0
- data/app/models/validation.rb +4 -0
- data/app/models/validation_condition.rb +4 -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 +41 -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/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/lib/assets/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/lib/assets/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/lib/assets/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/lib/assets/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/lib/assets/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/lib/assets/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/lib/assets/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/lib/assets/images/ui-icons_222222_256x240.png +0 -0
- data/lib/assets/images/ui-icons_2e83ff_256x240.png +0 -0
- data/lib/assets/images/ui-icons_454545_256x240.png +0 -0
- data/lib/assets/images/ui-icons_888888_256x240.png +0 -0
- data/lib/assets/images/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 +156 -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/assets/stylesheets/surveyor.sass +132 -0
- data/lib/generators/surveyor/custom_generator.rb +18 -0
- data/lib/generators/surveyor/install_generator.rb +102 -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_attachment_to_response.rb +5 -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 +284 -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/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 +13 -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 +86 -0
- data/lib/surveyor/models/dependency_condition_methods.rb +72 -0
- data/lib/surveyor/models/dependency_methods.rb +60 -0
- data/lib/surveyor/models/question_group_methods.rb +61 -0
- data/lib/surveyor/models/question_methods.rb +115 -0
- data/lib/surveyor/models/response_methods.rb +132 -0
- data/lib/surveyor/models/response_set_methods.rb +196 -0
- data/lib/surveyor/models/survey_methods.rb +103 -0
- data/lib/surveyor/models/survey_section_methods.rb +56 -0
- data/lib/surveyor/models/survey_translation_methods.rb +33 -0
- data/lib/surveyor/models/validation_condition_methods.rb +56 -0
- data/lib/surveyor/models/validation_methods.rb +48 -0
- data/lib/surveyor/mustache_context.rb +11 -0
- data/lib/surveyor/parser.rb +428 -0
- data/lib/surveyor/redcap_parser.rb +289 -0
- data/lib/surveyor/surveyor_controller_methods.rb +237 -0
- data/lib/surveyor/unparser.rb +147 -0
- data/lib/surveyor/version.rb +3 -0
- data/lib/surveyor.rb +13 -0
- data/lib/tasks/surveyor_tasks.rake +88 -0
- data/rails/init.rb +1 -0
- data/spec/controllers/surveyor_controller_spec.rb +307 -0
- data/spec/factories.rb +161 -0
- data/spec/helpers/formtastic_custom_input_spec.rb +15 -0
- data/spec/helpers/surveyor_helper_spec.rb +118 -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 +175 -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 +207 -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 +45 -0
- metadata +657 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'surveyor/helpers/asset_pipeline'
|
2
|
+
|
3
|
+
module Surveyor
|
4
|
+
module Helpers
|
5
|
+
module SurveyorHelperMethods
|
6
|
+
include AssetPipeline
|
7
|
+
|
8
|
+
# Layout: stylsheets and javascripts
|
9
|
+
def surveyor_includes
|
10
|
+
if asset_pipeline_enabled?
|
11
|
+
stylesheet_link_tag('surveyor_all') + javascript_include_tag('surveyor_all')
|
12
|
+
else
|
13
|
+
stylesheet_link_tag('surveyor/reset',
|
14
|
+
'surveyor/jquery-ui-1.10.0.custom',
|
15
|
+
'surveyor/jquery-ui-timepicker-addon',
|
16
|
+
'surveyor/ui.slider.extras',
|
17
|
+
'surveyor/results',
|
18
|
+
'surveyor',
|
19
|
+
'custom') +
|
20
|
+
javascript_include_tag('surveyor/jquery-1.9.0',
|
21
|
+
'surveyor/jquery-ui-1.10.0.custom',
|
22
|
+
'surveyor/jquery-ui-timepicker-addon',
|
23
|
+
'surveyor/jquery.selectToUISlider',
|
24
|
+
'surveyor/jquery.surveyor',
|
25
|
+
'surveyor/jquery.maskedinput')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
# Helper for displaying warning/notice/error flash messages
|
29
|
+
def flash_messages(types)
|
30
|
+
types.map{|type| content_tag(:div, "#{flash[type]}".html_safe, :class => type.to_s)}.join.html_safe
|
31
|
+
end
|
32
|
+
# Section: dependencies, menu, previous and next
|
33
|
+
def dependency_explanation_helper(question,response_set)
|
34
|
+
# Attempts to explain why this dependent question needs to be answered by referenced the dependent question and users response
|
35
|
+
trigger_responses = []
|
36
|
+
dependent_questions = Question.find_all_by_id(question.dependency.dependency_conditions.map(&:question_id)).uniq
|
37
|
+
response_set.responses.find_all_by_question_id(dependent_questions.map(&:id)).uniq.each do |resp|
|
38
|
+
trigger_responses << resp.to_s
|
39
|
+
end
|
40
|
+
" You answered "#{trigger_responses.join("" and "")}" to the question "#{dependent_questions.map(&:text).join("","")}""
|
41
|
+
end
|
42
|
+
def menu_button_for(section)
|
43
|
+
submit_tag(section.translation(I18n.locale)[:title], :name => "section[#{section.id}]")
|
44
|
+
end
|
45
|
+
def previous_section
|
46
|
+
# use copy in memory instead of making extra db calls
|
47
|
+
submit_tag(t('surveyor.previous_section').html_safe, :name => "section[#{@sections[@sections.index(@section)-1].id}]") unless @sections.first == @section
|
48
|
+
end
|
49
|
+
def next_section
|
50
|
+
# use copy in memory instead of making extra db calls
|
51
|
+
@sections.last == @section ? submit_tag(t('surveyor.click_here_to_finish').html_safe, :name => "finish") : submit_tag(t('surveyor.next_section').html_safe, :name => "section[#{@sections[@sections.index(@section)+1].id}]")
|
52
|
+
end
|
53
|
+
|
54
|
+
# Questions
|
55
|
+
def q_text(q, context=nil, locale=nil)
|
56
|
+
"#{next_question_number(q) unless (q.dependent? or q.display_type == "label" or q.display_type == "image" or q.part_of_group?)}#{q.text_for(nil, context, locale)}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def next_question_number(question)
|
60
|
+
@n ||= 0
|
61
|
+
"<span class='qnum'>#{@n += 1}) </span>"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Responses
|
65
|
+
def rc_to_attr(type_sym)
|
66
|
+
case type_sym.to_s
|
67
|
+
when /^answer$/ then :answer_id
|
68
|
+
else "#{type_sym.to_s}_value".to_sym
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def rc_to_as(type_sym)
|
73
|
+
case type_sym.to_s
|
74
|
+
when /(integer|float|date|time|datetime)/ then :string
|
75
|
+
else type_sym
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def generate_pick_none_input_html(value, default_value, css_class, response_class, disabled, input_mask, input_mask_placeholder)
|
80
|
+
html = {}
|
81
|
+
html[:class] = [response_class,css_class].reject{ |c| c.blank? }
|
82
|
+
html[:value] = value.blank? ? default_value : value
|
83
|
+
html[:disabled] = disabled unless disabled.blank?
|
84
|
+
if input_mask
|
85
|
+
html[:'data-input-mask'] = input_mask
|
86
|
+
html[:'data-input-mask-placeholder'] = input_mask_placeholder unless input_mask_placeholder.blank?
|
87
|
+
end
|
88
|
+
html
|
89
|
+
end
|
90
|
+
|
91
|
+
# Responses
|
92
|
+
def response_for(response_set, question, answer = nil, response_group = nil)
|
93
|
+
return nil unless response_set && question && question.id
|
94
|
+
result = response_set.responses.detect{|r| (r.question_id == question.id) && (answer.blank? ? true : r.answer_id == answer.id) && (r.response_group.blank? ? true : r.response_group.to_i == response_group.to_i)}
|
95
|
+
result.blank? ? response_set.responses.build(:question_id => question.id, :response_group => response_group) : result
|
96
|
+
end
|
97
|
+
def response_idx(increment = true)
|
98
|
+
@rc ||= 0
|
99
|
+
(increment ? @rc += 1 : @rc).to_s
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'surveyor/common'
|
2
|
+
|
3
|
+
module Surveyor
|
4
|
+
module Models
|
5
|
+
module AnswerMethods
|
6
|
+
def self.included(base)
|
7
|
+
# Associations
|
8
|
+
base.send :belongs_to, :question
|
9
|
+
base.send :has_many, :responses
|
10
|
+
base.send :has_many, :validations, :dependent => :destroy
|
11
|
+
|
12
|
+
# Scopes
|
13
|
+
base.send :default_scope, :order => "#{base.quoted_table_name}.display_order ASC"
|
14
|
+
|
15
|
+
# Mustache
|
16
|
+
base.send :include, MustacheContext
|
17
|
+
|
18
|
+
@@validations_already_included ||= nil
|
19
|
+
unless @@validations_already_included
|
20
|
+
# Validations
|
21
|
+
base.send :validates_presence_of, :text
|
22
|
+
# this causes issues with building and saving
|
23
|
+
# base.send :validates_numericality_of, :question_id, :allow_nil => false, :only_integer => true
|
24
|
+
@@validations_already_included = true
|
25
|
+
end
|
26
|
+
|
27
|
+
# Whitelisting attributes
|
28
|
+
base.send :attr_accessible, :question, :question_id, :text, :short_text, :help_text, :weight, :response_class, :reference_identifier, :data_export_identifier, :common_namespace, :common_identifier, :display_order, :is_exclusive, :display_length, :custom_class, :custom_renderer, :default_value, :display_type, :input_mask, :input_mask_placeholder
|
29
|
+
end
|
30
|
+
|
31
|
+
# Instance Methods
|
32
|
+
def initialize(*args)
|
33
|
+
super(*args)
|
34
|
+
default_args
|
35
|
+
end
|
36
|
+
|
37
|
+
def default_args
|
38
|
+
self.is_exclusive ||= false
|
39
|
+
self.display_type ||= "default"
|
40
|
+
self.response_class ||= "answer"
|
41
|
+
self.short_text ||= text
|
42
|
+
self.data_export_identifier ||= Surveyor::Common.normalize(text)
|
43
|
+
self.api_id ||= Surveyor::Common.generate_api_id
|
44
|
+
end
|
45
|
+
def display_type=(val)
|
46
|
+
write_attribute(:display_type, val.nil? ? nil : val.to_s)
|
47
|
+
end
|
48
|
+
|
49
|
+
def css_class
|
50
|
+
[(is_exclusive ? "exclusive" : nil), custom_class].compact.join(" ")
|
51
|
+
end
|
52
|
+
|
53
|
+
def text_for(position = nil, context = nil, locale = nil)
|
54
|
+
return "" if display_type == "hidden_label"
|
55
|
+
imaged(split(in_context(translation(locale)[:text], context), position))
|
56
|
+
end
|
57
|
+
def help_text_for(context = nil, locale = nil)
|
58
|
+
in_context(translation(locale)[:help_text], context)
|
59
|
+
end
|
60
|
+
def default_value_for(context = nil, locale = nil)
|
61
|
+
in_context(translation(locale)[:default_value], context)
|
62
|
+
end
|
63
|
+
def split(text, position=nil)
|
64
|
+
case position
|
65
|
+
when :pre
|
66
|
+
text.split("|",2)[0]
|
67
|
+
when :post
|
68
|
+
text.split("|",2)[1]
|
69
|
+
else
|
70
|
+
text
|
71
|
+
end.to_s
|
72
|
+
end
|
73
|
+
def translation(locale)
|
74
|
+
{:text => self.text, :help_text => self.help_text, :default_value => self.default_value}.with_indifferent_access.merge(
|
75
|
+
(self.question.translation(locale)[:answers] || {})[self.reference_identifier] || {}
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def imaged(text)
|
82
|
+
self.display_type == "image" && !text.blank? ? ActionController::Base.helpers.image_tag(text) : text
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module DependencyConditionMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :answer
|
7
|
+
base.send :belongs_to, :dependency
|
8
|
+
base.send :belongs_to, :dependent_question, :foreign_key => :question_id, :class_name => :question
|
9
|
+
base.send :belongs_to, :question
|
10
|
+
|
11
|
+
@@validations_already_included ||= nil
|
12
|
+
unless @@validations_already_included
|
13
|
+
# Validations
|
14
|
+
base.send :validates_presence_of, :operator, :rule_key
|
15
|
+
base.send :validate, :validates_operator
|
16
|
+
base.send :validates_uniqueness_of, :rule_key, :scope => :dependency_id
|
17
|
+
# this causes issues with building and saving
|
18
|
+
# base.send :validates_numericality_of, :question_id, :dependency_id
|
19
|
+
|
20
|
+
@@validations_already_included = true
|
21
|
+
end
|
22
|
+
|
23
|
+
base.send :include, Surveyor::ActsAsResponse # includes "as" instance method
|
24
|
+
|
25
|
+
# Whitelisting attributes
|
26
|
+
base.send :attr_accessible, :dependency, :question, :answer, :dependency_id, :rule_key, :question_id, :operator, :answer_id, :datetime_value, :integer_value, :float_value, :unit, :text_value, :string_value, :response_other
|
27
|
+
|
28
|
+
# Class methods
|
29
|
+
base.instance_eval do
|
30
|
+
def operators
|
31
|
+
Surveyor::Common::OPERATORS
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Instance methods
|
37
|
+
def to_hash(response_set)
|
38
|
+
# all responses to associated question
|
39
|
+
responses = question.blank? ? [] : response_set.responses.where("responses.answer_id in (?)", question.answer_ids).all
|
40
|
+
if self.operator.match /^count(>|>=|<|<=|==|!=)\d+$/
|
41
|
+
op, i = self.operator.scan(/^count(>|>=|<|<=|==|!=)(\d+)$/).flatten
|
42
|
+
# logger.warn({rule_key.to_sym => responses.count.send(op, i.to_i)})
|
43
|
+
return {rule_key.to_sym => (op == "!=" ? !responses.count.send("==", i.to_i) : responses.count.send(op, i.to_i))}
|
44
|
+
elsif operator == "!=" and (responses.blank? or responses.none?{|r| r.answer.id == self.answer.id})
|
45
|
+
# logger.warn( {rule_key.to_sym => true})
|
46
|
+
return {rule_key.to_sym => true}
|
47
|
+
elsif response = responses.detect{|r| r.answer.id == self.answer.id}
|
48
|
+
klass = response.answer.response_class
|
49
|
+
klass = "answer" if self.as(klass).nil? # it should compare answer ids when the dependency condition *_value is nil
|
50
|
+
case self.operator
|
51
|
+
when "==", "<", ">", "<=", ">="
|
52
|
+
# logger.warn( {rule_key.to_sym => response.as(klass).send(self.operator, self.as(klass))})
|
53
|
+
return {rule_key.to_sym => !response.as(klass).nil? && response.as(klass).send(self.operator, self.as(klass))}
|
54
|
+
when "!="
|
55
|
+
# logger.warn( {rule_key.to_sym => !response.as(klass).send("==", self.as(klass))})
|
56
|
+
return {rule_key.to_sym => !response.as(klass).send("==", self.as(klass))}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
# logger.warn({rule_key.to_sym => false})
|
60
|
+
{rule_key.to_sym => false}
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def validates_operator
|
66
|
+
errors.add(:operator, "Invalid operator") unless
|
67
|
+
Surveyor::Common::OPERATORS.include?(self.operator) ||
|
68
|
+
self.operator && self.operator.match(/^count(<|>|==|>=|<=|!=)(\d+)/)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module DependencyMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :question
|
7
|
+
base.send :belongs_to, :question_group
|
8
|
+
base.send :has_many, :dependency_conditions, :dependent => :destroy
|
9
|
+
|
10
|
+
@@validations_already_included ||= nil
|
11
|
+
unless @@validations_already_included
|
12
|
+
# Validations
|
13
|
+
base.send :validates_presence_of, :rule
|
14
|
+
base.send :validates_format_of, :rule, :with => /^(?:and|or|\)|\(|[A-Z]|\s)+$/ #TODO properly formed parenthesis etc.
|
15
|
+
base.send :validates_numericality_of, :question_id, :if => Proc.new { |d| d.question_group_id.nil? }
|
16
|
+
base.send :validates_numericality_of, :question_group_id, :if => Proc.new { |d| d.question_id.nil? }
|
17
|
+
|
18
|
+
@@validations_already_included = true
|
19
|
+
end
|
20
|
+
|
21
|
+
# Whitelisting attributes
|
22
|
+
base.send :attr_accessible, :question, :question_group, :question_id, :question_group_id, :rule
|
23
|
+
|
24
|
+
# Attribute aliases
|
25
|
+
base.send :alias_attribute, :dependent_question_id, :question_id
|
26
|
+
end
|
27
|
+
|
28
|
+
# Instance Methods
|
29
|
+
def question_group_id=(i)
|
30
|
+
write_attribute(:question_id, nil) unless i.nil?
|
31
|
+
write_attribute(:question_group_id, i)
|
32
|
+
end
|
33
|
+
|
34
|
+
def question_id=(i)
|
35
|
+
write_attribute(:question_group_id, nil) unless i.nil?
|
36
|
+
write_attribute(:question_id, i)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Has this dependency has been met in the context of response_set?
|
40
|
+
# Substitutes the conditions hash into the rule and evaluates it
|
41
|
+
def is_met?(response_set)
|
42
|
+
ch = conditions_hash(response_set)
|
43
|
+
return false if ch.blank?
|
44
|
+
# logger.debug "rule: #{self.rule.inspect}"
|
45
|
+
# logger.debug "rexp: #{rgx.inspect}"
|
46
|
+
# logger.debug "keyp: #{ch.inspect}"
|
47
|
+
# logger.debug "subd: #{self.rule.gsub(rgx){|m| ch[m.to_sym]}}"
|
48
|
+
rgx = Regexp.new(self.dependency_conditions.map{|dc| ["a","o"].include?(dc.rule_key) ? "\\b#{dc.rule_key}(?!nd|r)\\b" : "\\b#{dc.rule_key}\\b"}.join("|")) # exclude and, or
|
49
|
+
eval(self.rule.gsub(rgx){|m| ch[m.to_sym]})
|
50
|
+
end
|
51
|
+
|
52
|
+
# A hash of the conditions (keyed by rule_key) and their evaluation (boolean) in the context of response_set
|
53
|
+
def conditions_hash(response_set)
|
54
|
+
hash = {}
|
55
|
+
self.dependency_conditions.each{|dc| hash.merge!(dc.to_hash(response_set))}
|
56
|
+
return hash
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'surveyor/common'
|
2
|
+
|
3
|
+
module Surveyor
|
4
|
+
module Models
|
5
|
+
module QuestionGroupMethods
|
6
|
+
def self.included(base)
|
7
|
+
# Associations
|
8
|
+
base.send :has_many, :questions
|
9
|
+
base.send :has_one, :dependency
|
10
|
+
|
11
|
+
# Whitelisting attributes
|
12
|
+
base.send :attr_accessible, :text, :help_text, :reference_identifier, :data_export_identifier, :common_namespace, :common_identifier, :display_type, :custom_class, :custom_renderer
|
13
|
+
end
|
14
|
+
|
15
|
+
include MustacheContext
|
16
|
+
|
17
|
+
# Instance Methods
|
18
|
+
def initialize(*args)
|
19
|
+
super(*args)
|
20
|
+
default_args
|
21
|
+
end
|
22
|
+
|
23
|
+
def default_args
|
24
|
+
self.display_type ||= "inline"
|
25
|
+
self.api_id ||= Surveyor::Common.generate_api_id
|
26
|
+
end
|
27
|
+
|
28
|
+
def renderer
|
29
|
+
display_type.blank? ? :default : display_type.to_sym
|
30
|
+
end
|
31
|
+
|
32
|
+
def display_type=(val)
|
33
|
+
write_attribute(:display_type, val.nil? ? nil : val.to_s)
|
34
|
+
end
|
35
|
+
|
36
|
+
def dependent?
|
37
|
+
self.dependency != nil
|
38
|
+
end
|
39
|
+
def triggered?(response_set)
|
40
|
+
dependent? ? self.dependency.is_met?(response_set) : true
|
41
|
+
end
|
42
|
+
def css_class(response_set)
|
43
|
+
[(dependent? ? "g_dependent" : nil), (triggered?(response_set) ? nil : "g_hidden"), custom_class].compact.join(" ")
|
44
|
+
end
|
45
|
+
|
46
|
+
def text_for(context = nil, locale = nil)
|
47
|
+
return "" if display_type == "hidden_label"
|
48
|
+
in_context(translation(locale)[:text], context)
|
49
|
+
end
|
50
|
+
def help_text_for(context = nil, locale = nil)
|
51
|
+
in_context(translation(locale)[:help_text], context)
|
52
|
+
end
|
53
|
+
|
54
|
+
def translation(locale)
|
55
|
+
{:text => self.text, :help_text => self.help_text}.with_indifferent_access.merge(
|
56
|
+
(self.questions.first.survey_section.survey.translation(locale)[:question_groups] || {})[self.reference_identifier] || {}
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'surveyor/common'
|
2
|
+
|
3
|
+
module Surveyor
|
4
|
+
module Models
|
5
|
+
module QuestionMethods
|
6
|
+
def self.included(base)
|
7
|
+
# Associations
|
8
|
+
base.send :belongs_to, :survey_section
|
9
|
+
base.send :belongs_to, :question_group, :dependent => :destroy
|
10
|
+
base.send :has_many, :answers, :dependent => :destroy # it might not always have answers
|
11
|
+
base.send :has_one, :dependency, :dependent => :destroy
|
12
|
+
base.send :belongs_to, :correct_answer, :class_name => "Answer", :dependent => :destroy
|
13
|
+
|
14
|
+
# Scopes
|
15
|
+
base.send :default_scope, :order => "#{base.quoted_table_name}.display_order ASC"
|
16
|
+
|
17
|
+
# Mustache
|
18
|
+
base.send :include, MustacheContext
|
19
|
+
|
20
|
+
@@validations_already_included ||= nil
|
21
|
+
unless @@validations_already_included
|
22
|
+
# Validations
|
23
|
+
base.send :validates_presence_of, :text, :display_order
|
24
|
+
# this causes issues with building and saving
|
25
|
+
#, :survey_section_id
|
26
|
+
base.send :validates_inclusion_of, :is_mandatory, :in => [true, false]
|
27
|
+
|
28
|
+
@@validations_already_included = true
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
# Whitelisting attributes
|
33
|
+
base.send :attr_accessible, :survey_section, :question_group, :survey_section_id, :question_group_id, :text, :short_text, :help_text, :pick, :reference_identifier, :data_export_identifier, :common_namespace, :common_identifier, :display_order, :display_type, :is_mandatory, :display_width, :custom_class, :custom_renderer, :correct_answer_id
|
34
|
+
end
|
35
|
+
|
36
|
+
# Instance Methods
|
37
|
+
def initialize(*args)
|
38
|
+
super(*args)
|
39
|
+
default_args
|
40
|
+
end
|
41
|
+
|
42
|
+
def default_args
|
43
|
+
self.is_mandatory ||= false
|
44
|
+
self.display_type ||= "default"
|
45
|
+
self.pick ||= "none"
|
46
|
+
self.data_export_identifier ||= Surveyor::Common.normalize(text)
|
47
|
+
self.short_text ||= text
|
48
|
+
self.api_id ||= Surveyor::Common.generate_api_id
|
49
|
+
end
|
50
|
+
|
51
|
+
def pick=(val)
|
52
|
+
write_attribute(:pick, val.nil? ? nil : val.to_s)
|
53
|
+
end
|
54
|
+
def display_type=(val)
|
55
|
+
write_attribute(:display_type, val.nil? ? nil : val.to_s)
|
56
|
+
end
|
57
|
+
|
58
|
+
def mandatory?
|
59
|
+
self.is_mandatory == true
|
60
|
+
end
|
61
|
+
|
62
|
+
def dependent?
|
63
|
+
self.dependency != nil
|
64
|
+
end
|
65
|
+
def triggered?(response_set)
|
66
|
+
dependent? ? self.dependency.is_met?(response_set) : true
|
67
|
+
end
|
68
|
+
def css_class(response_set)
|
69
|
+
[(dependent? ? "q_dependent" : nil), (triggered?(response_set) ? nil : "q_hidden"), custom_class].compact.join(" ")
|
70
|
+
end
|
71
|
+
|
72
|
+
def part_of_group?
|
73
|
+
!self.question_group.nil?
|
74
|
+
end
|
75
|
+
def solo?
|
76
|
+
self.question_group.nil?
|
77
|
+
end
|
78
|
+
|
79
|
+
def text_for(position = nil, context = nil, locale = nil)
|
80
|
+
return "" if display_type == "hidden_label"
|
81
|
+
imaged(split(in_context(translation(locale)[:text], context), position))
|
82
|
+
end
|
83
|
+
def help_text_for(context = nil, locale = nil)
|
84
|
+
in_context(translation(locale)[:help_text], context)
|
85
|
+
end
|
86
|
+
def split(text, position=nil)
|
87
|
+
case position
|
88
|
+
when :pre
|
89
|
+
text.split("|",2)[0]
|
90
|
+
when :post
|
91
|
+
text.split("|",2)[1]
|
92
|
+
else
|
93
|
+
text
|
94
|
+
end.to_s
|
95
|
+
end
|
96
|
+
def renderer(g = question_group)
|
97
|
+
r = [g ? g.renderer.to_s : nil, display_type].compact.join("_")
|
98
|
+
r.blank? ? :default : r.to_sym
|
99
|
+
end
|
100
|
+
def translation(locale)
|
101
|
+
{:text => self.text, :help_text => self.help_text}.with_indifferent_access.merge(
|
102
|
+
(self.survey_section.survey.translation(locale)[:questions] || {})[self.reference_identifier] || {}
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
|
109
|
+
def imaged(text)
|
110
|
+
self.display_type == "image" && !text.blank? ? ActionController::Base.helpers.image_tag(text) : text
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module ResponseMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :response_set
|
7
|
+
base.send :belongs_to, :question
|
8
|
+
base.send :belongs_to, :answer
|
9
|
+
|
10
|
+
base.send :has_attached_file, :file
|
11
|
+
|
12
|
+
@@validations_already_included ||= nil
|
13
|
+
unless @@validations_already_included
|
14
|
+
# Validations
|
15
|
+
base.send :validates_presence_of, :question_id, :answer_id
|
16
|
+
|
17
|
+
@@validations_already_included = true
|
18
|
+
end
|
19
|
+
base.send :include, Surveyor::ActsAsResponse # includes "as" instance method
|
20
|
+
|
21
|
+
# Whitelisting attributes
|
22
|
+
base.send :attr_accessible, :response_set, :question, :answer, :date_value, :time_value, :response_set_id, :question_id, :answer_id, :datetime_value, :integer_value, :float_value,
|
23
|
+
:unit, :text_value, :string_value, :response_other, :response_group, :survey_section_id, :file
|
24
|
+
|
25
|
+
# Class methods
|
26
|
+
base.instance_eval do
|
27
|
+
def applicable_attributes(attrs)
|
28
|
+
result = HashWithIndifferentAccess.new(attrs)
|
29
|
+
answer_id = result[:answer_id].is_a?(Array) ? result[:answer_id].last : result[:answer_id] # checkboxes are arrays / radio buttons are not arrays
|
30
|
+
if result[:string_value] && !answer_id.blank? && Answer.exists?(answer_id)
|
31
|
+
answer = Answer.find(answer_id)
|
32
|
+
result.delete(:string_value) unless answer.response_class && answer.response_class.to_sym == :string
|
33
|
+
end
|
34
|
+
result
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Instance Methods
|
40
|
+
def initialize(*args)
|
41
|
+
super(*args)
|
42
|
+
default_args
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_args
|
46
|
+
self.api_id ||= Surveyor::Common.generate_api_id
|
47
|
+
end
|
48
|
+
|
49
|
+
def answer_id=(val)
|
50
|
+
write_attribute :answer_id, (val.is_a?(Array) ? val.detect{|x| !x.to_s.blank?} : val)
|
51
|
+
end
|
52
|
+
def correct?
|
53
|
+
question.correct_answer.nil? or self.answer.response_class != "answer" or (question.correct_answer.id.to_i == answer.id.to_i)
|
54
|
+
end
|
55
|
+
|
56
|
+
def time_value
|
57
|
+
read_attribute(:datetime_value).strftime( time_format ) unless read_attribute(:datetime_value).blank?
|
58
|
+
end
|
59
|
+
|
60
|
+
def time_value=(val)
|
61
|
+
self.datetime_value =
|
62
|
+
if val && time = Time.zone.parse("#{Date.today.to_s} #{val}")
|
63
|
+
time.to_datetime
|
64
|
+
else
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def date_value
|
70
|
+
read_attribute(:datetime_value).strftime( date_format ) unless read_attribute(:datetime_value).blank?
|
71
|
+
end
|
72
|
+
|
73
|
+
def date_value=(val)
|
74
|
+
self.datetime_value =
|
75
|
+
if val && time = Time.zone.parse(val)
|
76
|
+
time.to_datetime
|
77
|
+
else
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def time_format
|
83
|
+
'%H:%M'
|
84
|
+
end
|
85
|
+
|
86
|
+
def date_format
|
87
|
+
'%Y-%m-%d'
|
88
|
+
end
|
89
|
+
|
90
|
+
def datetime_format
|
91
|
+
'%Y-%m-%d %H:%M:%S'
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_formatted_s
|
95
|
+
return "" if answer.nil? || answer.response_class.nil?
|
96
|
+
return case t = answer.response_class.to_sym
|
97
|
+
when :string, :text, :integer, :float
|
98
|
+
send("#{t}_value".to_sym).to_s
|
99
|
+
when :date
|
100
|
+
date_value
|
101
|
+
when :time
|
102
|
+
time_value
|
103
|
+
when :datetime
|
104
|
+
(read_attribute(:datetime_value).strftime( datetime_format ) unless read_attribute(:datetime_value).blank?) || ''
|
105
|
+
else
|
106
|
+
to_s
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_s # used in dependency_explanation_helper
|
111
|
+
if self.answer.response_class == "answer" and self.answer_id
|
112
|
+
return self.answer.text
|
113
|
+
else
|
114
|
+
return "#{(self.string_value || self.text_value || self.integer_value || self.float_value || nil).to_s}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def json_value
|
119
|
+
return nil if answer.response_class == "answer"
|
120
|
+
|
121
|
+
formats = {
|
122
|
+
'datetime' => '%Y-%m-%dT%H:%M%:z',
|
123
|
+
'date' => '%Y-%m-%d',
|
124
|
+
'time' => '%H:%M'
|
125
|
+
}
|
126
|
+
|
127
|
+
found = formats[answer.response_class]
|
128
|
+
found ? datetime_value.try{|d| d.utc.strftime(found)} : as(answer.response_class)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|