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,196 @@
|
|
1
|
+
require 'rabl'
|
2
|
+
require 'paperclip'
|
3
|
+
|
4
|
+
module Surveyor
|
5
|
+
module Models
|
6
|
+
module ResponseSetMethods
|
7
|
+
def self.included(base)
|
8
|
+
# Associations
|
9
|
+
base.send :belongs_to, :survey
|
10
|
+
base.send :belongs_to, :user
|
11
|
+
base.send :has_many, :responses, :order => "#{Response.quoted_table_name}.created_at ASC", :dependent => :destroy
|
12
|
+
base.send :accepts_nested_attributes_for, :responses, :allow_destroy => true
|
13
|
+
|
14
|
+
@@validations_already_included ||= nil
|
15
|
+
unless @@validations_already_included
|
16
|
+
# Validations
|
17
|
+
base.send :validates_presence_of, :survey_id
|
18
|
+
base.send :validates_associated, :responses
|
19
|
+
base.send :validates_uniqueness_of, :access_code
|
20
|
+
|
21
|
+
@@validations_already_included = true
|
22
|
+
end
|
23
|
+
|
24
|
+
# Attributes
|
25
|
+
base.send :attr_protected, :completed_at
|
26
|
+
|
27
|
+
# Whitelisting attributes
|
28
|
+
base.send :attr_accessible, :survey, :responses_attributes, :user_id, :survey_id
|
29
|
+
|
30
|
+
base.send :before_create, :ensure_start_timestamp
|
31
|
+
base.send :before_create, :ensure_identifiers
|
32
|
+
|
33
|
+
# Class methods
|
34
|
+
base.instance_eval do
|
35
|
+
def has_blank_value?(hash)
|
36
|
+
return true if hash["answer_id"].blank?
|
37
|
+
return false if (q = Question.find_by_id(hash["question_id"])) and q.pick == "one"
|
38
|
+
hash.any?{|k,v| v.is_a?(Array) ? v.all?{|x| x.to_s.blank?} : v.to_s.blank?}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def ensure_start_timestamp
|
44
|
+
self.started_at ||= Time.now
|
45
|
+
end
|
46
|
+
|
47
|
+
def ensure_identifiers
|
48
|
+
self.access_code ||= Surveyor::Common.make_tiny_code
|
49
|
+
self.api_id ||= Surveyor::Common.generate_api_id
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_csv(access_code = false, print_header = true)
|
53
|
+
qcols = Question.content_columns.map(&:name) - %w(created_at updated_at)
|
54
|
+
acols = Answer.content_columns.map(&:name) - %w(created_at updated_at)
|
55
|
+
rcols = Response.content_columns.map(&:name)
|
56
|
+
result = Surveyor::Common.csv_impl.generate do |csv|
|
57
|
+
if print_header
|
58
|
+
csv << (access_code ? ["response set access code"] : []) +
|
59
|
+
qcols.map{|qcol| "question.#{qcol}"} +
|
60
|
+
acols.map{|acol| "answer.#{acol}"} +
|
61
|
+
rcols.map{|rcol| "response.#{rcol}"}
|
62
|
+
end
|
63
|
+
responses.each do |response|
|
64
|
+
csv << (access_code ? [self.access_code] : []) +
|
65
|
+
qcols.map{|qcol| response.question.send(qcol)} +
|
66
|
+
acols.map{|acol| response.answer.send(acol)} +
|
67
|
+
rcols.map{|rcol| response.send(rcol)}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
result
|
71
|
+
end
|
72
|
+
|
73
|
+
def as_json(options = nil)
|
74
|
+
template_paths = ActionController::Base.view_paths.collect(&:to_path)
|
75
|
+
Rabl.render(self, 'surveyor/show.json', :view_path => template_paths, :format => "hash")
|
76
|
+
end
|
77
|
+
|
78
|
+
def complete!
|
79
|
+
self.completed_at = Time.now
|
80
|
+
end
|
81
|
+
|
82
|
+
def complete?
|
83
|
+
!completed_at.nil?
|
84
|
+
end
|
85
|
+
|
86
|
+
def correct?
|
87
|
+
responses.all?(&:correct?)
|
88
|
+
end
|
89
|
+
def correctness_hash
|
90
|
+
{ :questions => survey.sections_with_questions.map(&:questions).flatten.compact.size,
|
91
|
+
:responses => responses.compact.size,
|
92
|
+
:correct => responses.find_all(&:correct?).compact.size
|
93
|
+
}
|
94
|
+
end
|
95
|
+
def mandatory_questions_complete?
|
96
|
+
progress_hash[:triggered_mandatory] == progress_hash[:triggered_mandatory_completed]
|
97
|
+
end
|
98
|
+
def progress_hash
|
99
|
+
qs = survey.sections_with_questions.map(&:questions).flatten
|
100
|
+
ds = dependencies(qs.map(&:id))
|
101
|
+
triggered = qs - ds.select{|d| !d.is_met?(self)}.map(&:question)
|
102
|
+
{ :questions => qs.compact.size,
|
103
|
+
:triggered => triggered.compact.size,
|
104
|
+
:triggered_mandatory => triggered.select{|q| q.mandatory?}.compact.size,
|
105
|
+
:triggered_mandatory_completed => triggered.select{|q| q.mandatory? and is_answered?(q)}.compact.size
|
106
|
+
}
|
107
|
+
end
|
108
|
+
def is_answered?(question)
|
109
|
+
%w(label image).include?(question.display_type) or !is_unanswered?(question)
|
110
|
+
end
|
111
|
+
def is_unanswered?(question)
|
112
|
+
self.responses.detect{|r| r.question_id == question.id}.nil?
|
113
|
+
end
|
114
|
+
def is_group_unanswered?(group)
|
115
|
+
group.questions.any?{|question| is_unanswered?(question)}
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns the number of response groups (count of group responses enterted) for this question group
|
119
|
+
def count_group_responses(questions)
|
120
|
+
questions.map { |q|
|
121
|
+
responses.select { |r|
|
122
|
+
(r.question_id.to_i == q.id.to_i) && !r.response_group.nil?
|
123
|
+
}.group_by(&:response_group).size
|
124
|
+
}.max
|
125
|
+
end
|
126
|
+
|
127
|
+
def unanswered_dependencies
|
128
|
+
unanswered_question_dependencies + unanswered_question_group_dependencies
|
129
|
+
end
|
130
|
+
|
131
|
+
def unanswered_question_dependencies
|
132
|
+
dependencies.select{ |d| d.question && self.is_unanswered?(d.question) && d.is_met?(self) }.map(&:question)
|
133
|
+
end
|
134
|
+
|
135
|
+
def unanswered_question_group_dependencies
|
136
|
+
dependencies.
|
137
|
+
select{ |d| d.question_group && self.is_group_unanswered?(d.question_group) && d.is_met?(self) }.
|
138
|
+
map(&:question_group)
|
139
|
+
end
|
140
|
+
|
141
|
+
def all_dependencies(question_ids = nil)
|
142
|
+
arr = dependencies(question_ids).partition{|d| d.is_met?(self) }
|
143
|
+
{
|
144
|
+
:show => arr[0].map{|d| d.question_group_id.nil? ? "q_#{d.question_id}" : "g_#{d.question_group_id}"},
|
145
|
+
:hide => arr[1].map{|d| d.question_group_id.nil? ? "q_#{d.question_id}" : "g_#{d.question_group_id}"}
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
# Check existence of responses to questions from a given survey_section
|
150
|
+
def no_responses_for_section?(section)
|
151
|
+
!responses.any?{|r| r.survey_section_id == section.id}
|
152
|
+
end
|
153
|
+
|
154
|
+
def update_from_ui_hash(ui_hash)
|
155
|
+
transaction do
|
156
|
+
ui_hash.each do |ord, response_hash|
|
157
|
+
api_id = response_hash['api_id']
|
158
|
+
fail "api_id missing from response #{ord}" unless api_id
|
159
|
+
|
160
|
+
existing = Response.where(:api_id => api_id).first
|
161
|
+
updateable_attributes = response_hash.reject { |k, v| k == 'api_id' }
|
162
|
+
|
163
|
+
if self.class.has_blank_value?(response_hash)
|
164
|
+
existing.destroy if existing
|
165
|
+
elsif existing
|
166
|
+
if existing.question_id.to_s != updateable_attributes['question_id']
|
167
|
+
fail "Illegal attempt to change question for response #{api_id}."
|
168
|
+
end
|
169
|
+
|
170
|
+
existing.update_attributes(updateable_attributes)
|
171
|
+
else
|
172
|
+
responses.build(updateable_attributes).tap do |r|
|
173
|
+
r.api_id = api_id
|
174
|
+
r.save!
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
protected
|
183
|
+
|
184
|
+
def dependencies(question_ids = nil)
|
185
|
+
question_ids = survey.sections.map(&:questions).flatten.map(&:id) if responses.blank? and question_ids.blank?
|
186
|
+
deps = Dependency.all(:include => :dependency_conditions,
|
187
|
+
:conditions => {:dependency_conditions => {:question_id => question_ids || responses.map(&:question_id)}})
|
188
|
+
|
189
|
+
# this is a work around for a bug in active_record in rails 2.3 which incorrectly eager-loads associatins when a
|
190
|
+
# condition clause includes an association limiter
|
191
|
+
deps.each{|d| d.dependency_conditions.reload}
|
192
|
+
deps
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'surveyor/common'
|
2
|
+
require 'rabl'
|
3
|
+
|
4
|
+
module Surveyor
|
5
|
+
module Models
|
6
|
+
module SurveyMethods
|
7
|
+
def self.included(base)
|
8
|
+
# Associations
|
9
|
+
base.send :has_many, :sections, :class_name => "SurveySection", :order => "#{SurveySection.quoted_table_name}.display_order", :dependent => :destroy
|
10
|
+
base.send :has_many, :sections_with_questions, :include => :questions, :class_name => "SurveySection", :order => "#{SurveySection.quoted_table_name}.display_order"
|
11
|
+
base.send :has_many, :response_sets
|
12
|
+
base.send :has_many, :translations, :class_name => "SurveyTranslation"
|
13
|
+
|
14
|
+
# Scopes
|
15
|
+
base.send :scope, :with_sections, {:include => :sections}
|
16
|
+
|
17
|
+
@@validations_already_included ||= nil
|
18
|
+
unless @@validations_already_included
|
19
|
+
# Validations
|
20
|
+
base.send :validates_presence_of, :title
|
21
|
+
base.send :validates_uniqueness_of, :survey_version, :scope => :access_code, :message => "survey with matching access code and version already exists"
|
22
|
+
|
23
|
+
@@validations_already_included = true
|
24
|
+
end
|
25
|
+
|
26
|
+
# Whitelisting attributes
|
27
|
+
base.send :attr_accessible, :title, :description, :reference_identifier, :data_export_identifier, :common_namespace, :common_identifier, :css_url, :custom_class, :display_order
|
28
|
+
|
29
|
+
# Derived attributes
|
30
|
+
base.send :before_save, :generate_access_code
|
31
|
+
base.send :before_save, :increment_version
|
32
|
+
|
33
|
+
# Class methods
|
34
|
+
base.instance_eval do
|
35
|
+
def to_normalized_string(value)
|
36
|
+
# replace non-alphanumeric with "-". remove repeat "-"s. don't start or end with "-"
|
37
|
+
value.to_s.downcase.gsub(/[^a-z0-9]/,"-").gsub(/-+/,"-").gsub(/-$|^-/,"")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Instance methods
|
43
|
+
def initialize(*args)
|
44
|
+
super(*args)
|
45
|
+
default_args
|
46
|
+
end
|
47
|
+
|
48
|
+
def default_args
|
49
|
+
self.api_id ||= Surveyor::Common.generate_api_id
|
50
|
+
self.display_order ||= Survey.count
|
51
|
+
end
|
52
|
+
|
53
|
+
def active?
|
54
|
+
self.active_as_of?(DateTime.now)
|
55
|
+
end
|
56
|
+
def active_as_of?(date)
|
57
|
+
(active_at && active_at < date && (!inactive_at || inactive_at > date)) ? true : false
|
58
|
+
end
|
59
|
+
def activate!
|
60
|
+
self.active_at = DateTime.now
|
61
|
+
self.inactive_at = nil
|
62
|
+
end
|
63
|
+
def deactivate!
|
64
|
+
self.inactive_at = DateTime.now
|
65
|
+
self.active_at = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def as_json(options = nil)
|
69
|
+
template_paths = ActionController::Base.view_paths.collect(&:to_path)
|
70
|
+
Rabl.render(filtered_for_json, 'surveyor/export.json', :view_path => template_paths, :format => "hash")
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# A hook that allows the survey object to be modified before it is
|
75
|
+
# serialized by the #as_json method.
|
76
|
+
def filtered_for_json
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def default_access_code
|
81
|
+
self.class.to_normalized_string(title)
|
82
|
+
end
|
83
|
+
|
84
|
+
def generate_access_code
|
85
|
+
self.access_code ||= default_access_code
|
86
|
+
end
|
87
|
+
|
88
|
+
def increment_version
|
89
|
+
surveys = self.class.select(:survey_version).where(:access_code => access_code).order("survey_version DESC")
|
90
|
+
next_version = surveys.any? ? surveys.first.survey_version.to_i + 1 : 0
|
91
|
+
|
92
|
+
self.survey_version = next_version
|
93
|
+
end
|
94
|
+
|
95
|
+
def translation(locale_symbol)
|
96
|
+
t = self.translations.where(:locale => locale_symbol.to_s).first
|
97
|
+
{:title => self.title, :description => self.description}.with_indifferent_access.merge(
|
98
|
+
t ? YAML.load(t.translation || "{}").with_indifferent_access : {}
|
99
|
+
)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module SurveySectionMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :has_many, :questions, :dependent => :destroy
|
7
|
+
base.send :belongs_to, :survey
|
8
|
+
|
9
|
+
# Scopes
|
10
|
+
base.send :scope, :with_includes, { :include => {:questions => [:answers, :question_group, {:dependency => :dependency_conditions}]}}
|
11
|
+
|
12
|
+
@@validations_already_included ||= nil
|
13
|
+
unless @@validations_already_included
|
14
|
+
# Validations
|
15
|
+
base.send :validates_presence_of, :title, :display_order
|
16
|
+
# this causes issues with building and saving
|
17
|
+
#, :survey
|
18
|
+
|
19
|
+
@@validations_already_included = true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Whitelisting attributes
|
23
|
+
base.send :attr_accessible, :survey, :survey_id, :title, :description, :reference_identifier, :data_export_identifier, :common_namespace, :common_identifier, :display_order, :custom_class
|
24
|
+
end
|
25
|
+
|
26
|
+
# Instance Methods
|
27
|
+
def initialize(*args)
|
28
|
+
super(*args)
|
29
|
+
default_args
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_args
|
33
|
+
self.data_export_identifier ||= Surveyor::Common.normalize(title)
|
34
|
+
end
|
35
|
+
|
36
|
+
def questions_and_groups
|
37
|
+
qs = []
|
38
|
+
questions.each_with_index.map do |q,i|
|
39
|
+
if q.part_of_group?
|
40
|
+
if (i+1 >= questions.size) or (q.question_group_id != questions[i+1].question_group_id)
|
41
|
+
q.question_group
|
42
|
+
end
|
43
|
+
else
|
44
|
+
q
|
45
|
+
end
|
46
|
+
end.compact
|
47
|
+
end
|
48
|
+
|
49
|
+
def translation(locale)
|
50
|
+
{:title => self.title, :description => self.description}.with_indifferent_access.merge(
|
51
|
+
(self.survey.translation(locale)[:survey_sections] || {})[self.reference_identifier] || {}
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module SurveyTranslationMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :survey
|
7
|
+
|
8
|
+
@@validations_already_included ||= nil
|
9
|
+
unless @@validations_already_included
|
10
|
+
# Validations
|
11
|
+
base.send :validates_presence_of, :locale, :translation
|
12
|
+
base.send :validates_uniqueness_of, :locale, :scope => :survey_id
|
13
|
+
# this causes issues with building and saving
|
14
|
+
#, :survey
|
15
|
+
|
16
|
+
@@validations_already_included = true
|
17
|
+
end
|
18
|
+
|
19
|
+
# Whitelisting attributes
|
20
|
+
base.send :attr_accessible, :survey, :survey_id, :locale, :translation
|
21
|
+
end
|
22
|
+
|
23
|
+
# Instance Methods
|
24
|
+
def initialize(*args)
|
25
|
+
super(*args)
|
26
|
+
default_args
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_args
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module ValidationConditionMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :validation
|
7
|
+
|
8
|
+
# Scopes
|
9
|
+
@@validations_already_included ||= nil
|
10
|
+
unless @@validations_already_included
|
11
|
+
# Validations
|
12
|
+
base.send :validates_presence_of, :operator, :rule_key
|
13
|
+
base.send :validates_inclusion_of, :operator, :in => Surveyor::Common::OPERATORS
|
14
|
+
base.send :validates_uniqueness_of, :rule_key, :scope => :validation_id
|
15
|
+
# this causes issues with building and saving
|
16
|
+
# base.send :validates_numericality_of, :validation_id #, :question_id, :answer_id
|
17
|
+
|
18
|
+
@@validations_already_included = true
|
19
|
+
end
|
20
|
+
|
21
|
+
base.send :include, Surveyor::ActsAsResponse # includes "as" instance method
|
22
|
+
|
23
|
+
# Whitelisting attributes
|
24
|
+
base.send :attr_accessible, :validation, :validation_id, :rule_key, :operator, :question_id, :answer_id, :datetime_value, :integer_value, :float_value, :unit, :text_value, :string_value, :response_other, :regexp
|
25
|
+
|
26
|
+
# Class methods
|
27
|
+
base.instance_eval do
|
28
|
+
def operators
|
29
|
+
Surveyor::Common::OPERATORS
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Instance Methods
|
35
|
+
def to_hash(response)
|
36
|
+
{rule_key.to_sym => (response.nil? ? false : self.is_valid?(response))}
|
37
|
+
end
|
38
|
+
|
39
|
+
def is_valid?(response)
|
40
|
+
klass = response.answer.response_class
|
41
|
+
compare_to = Response.find_by_question_id_and_answer_id(self.question_id, self.answer_id) || self
|
42
|
+
case self.operator
|
43
|
+
when "==", "<", ">", "<=", ">="
|
44
|
+
response.as(klass).send(self.operator, compare_to.as(klass))
|
45
|
+
when "!="
|
46
|
+
!(response.as(klass) == compare_to.as(klass))
|
47
|
+
when "=~"
|
48
|
+
return false if compare_to != self
|
49
|
+
!(response.as(klass).to_s =~ Regexp.new(self.regexp || "")).nil?
|
50
|
+
else
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module ValidationMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :answer
|
7
|
+
base.send :has_many, :validation_conditions, :dependent => :destroy
|
8
|
+
|
9
|
+
# Scopes
|
10
|
+
|
11
|
+
@@validations_already_included ||= nil
|
12
|
+
unless @@validations_already_included
|
13
|
+
# Validations
|
14
|
+
base.send :validates_presence_of, :rule
|
15
|
+
base.send :validates_format_of, :rule, :with => /^(?:and|or|\)|\(|[A-Z]|\s)+$/
|
16
|
+
# this causes issues with building and saving
|
17
|
+
# base.send :validates_numericality_of, :answer_id
|
18
|
+
|
19
|
+
@@validations_already_included = true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Whitelisting attributes
|
23
|
+
base.send :attr_accessible, :answer, :answer_id, :rule, :message
|
24
|
+
end
|
25
|
+
|
26
|
+
# Instance Methods
|
27
|
+
def is_valid?(response_set)
|
28
|
+
ch = conditions_hash(response_set)
|
29
|
+
rgx = Regexp.new(self.validation_conditions.map{|vc| ["a","o"].include?(vc.rule_key) ? "#{vc.rule_key}(?!nd|r)" : vc.rule_key}.join("|")) # exclude and, or
|
30
|
+
# logger.debug "v: #{self.inspect}"
|
31
|
+
# logger.debug "rule: #{self.rule.inspect}"
|
32
|
+
# logger.debug "rexp: #{rgx.inspect}"
|
33
|
+
# logger.debug "keyp: #{ch.inspect}"
|
34
|
+
# logger.debug "subd: #{self.rule.gsub(rgx){|m| ch[m.to_sym]}}"
|
35
|
+
eval(self.rule.gsub(rgx){|m| ch[m.to_sym]})
|
36
|
+
end
|
37
|
+
|
38
|
+
# A hash of the conditions (keyed by rule_key) and their evaluation (boolean) in the context of response_set
|
39
|
+
def conditions_hash(response_set)
|
40
|
+
hash = {}
|
41
|
+
response = response_set.responses.detect{|r| r.answer_id.to_i == self.answer_id.to_i}
|
42
|
+
# logger.debug "r: #{response.inspect}"
|
43
|
+
self.validation_conditions.each{|vc| hash.merge!(vc.to_hash(response))}
|
44
|
+
return hash
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|