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.
Files changed (212) hide show
  1. data/.gitignore +28 -0
  2. data/.rspec +1 -0
  3. data/CHANGELOG.md +198 -0
  4. data/Gemfile +5 -0
  5. data/Gemfile.rails_version +19 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +186 -0
  8. data/Rakefile +105 -0
  9. data/app/controllers/surveyor_controller.rb +6 -0
  10. data/app/helpers/results_helper.rb +20 -0
  11. data/app/helpers/survey_form_builder.rb +37 -0
  12. data/app/helpers/surveyor_helper.rb +3 -0
  13. data/app/inputs/quiet_input.rb +5 -0
  14. data/app/inputs/surveyor_check_boxes_input.rb +35 -0
  15. data/app/inputs/surveyor_radio_input.rb +18 -0
  16. data/app/models/answer.rb +4 -0
  17. data/app/models/dependency.rb +4 -0
  18. data/app/models/dependency_condition.rb +4 -0
  19. data/app/models/question.rb +4 -0
  20. data/app/models/question_group.rb +5 -0
  21. data/app/models/response.rb +5 -0
  22. data/app/models/response_set.rb +4 -0
  23. data/app/models/survey.rb +4 -0
  24. data/app/models/survey_section.rb +5 -0
  25. data/app/models/survey_section_sweeper.rb +15 -0
  26. data/app/models/survey_translation.rb +5 -0
  27. data/app/models/validation.rb +4 -0
  28. data/app/models/validation_condition.rb +4 -0
  29. data/app/views/layouts/results.html.erb +13 -0
  30. data/app/views/layouts/surveyor_default.html.erb +12 -0
  31. data/app/views/partials/_answer.html.haml +25 -0
  32. data/app/views/partials/_dependents.html.haml +5 -0
  33. data/app/views/partials/_question.html.haml +41 -0
  34. data/app/views/partials/_question_group.html.haml +44 -0
  35. data/app/views/partials/_section.html.haml +12 -0
  36. data/app/views/partials/_section_menu.html.haml +12 -0
  37. data/app/views/surveyor/edit.html.haml +24 -0
  38. data/app/views/surveyor/export.json.rabl +85 -0
  39. data/app/views/surveyor/new.html.haml +24 -0
  40. data/app/views/surveyor/show.html.haml +74 -0
  41. data/app/views/surveyor/show.json.rabl +15 -0
  42. data/ci-exec.sh +56 -0
  43. data/config/routes.rb +8 -0
  44. data/cucumber.yml +10 -0
  45. data/doc/REPRESENTATIONS.md +34 -0
  46. data/doc/api_id_schema.json +7 -0
  47. data/doc/question types.png +0 -0
  48. data/doc/response_set_schema.json +54 -0
  49. data/doc/surveyor question combinations.png +0 -0
  50. data/doc/surveyor reject or delete decision matrix.png +0 -0
  51. data/doc/surveyor_models.png +0 -0
  52. data/doc/surveyor_models2.png +0 -0
  53. data/doc/surveyor_timestamp_schema.json +9 -0
  54. data/features/ajax_submissions.feature +140 -0
  55. data/features/export_to_json.feature +344 -0
  56. data/features/internationalization.feature +121 -0
  57. data/features/no_duplicates.feature +110 -0
  58. data/features/show_survey.feature +71 -0
  59. data/features/step_definitions/parser_steps.rb +145 -0
  60. data/features/step_definitions/surveyor_steps.rb +325 -0
  61. data/features/step_definitions/ui_steps.rb +25 -0
  62. data/features/step_definitions/web_steps.rb +225 -0
  63. data/features/support/REDCapDemoDatabase_DataDictionary.csv +127 -0
  64. data/features/support/database_cleaner.rb +16 -0
  65. data/features/support/env.rb +56 -0
  66. data/features/support/hooks.rb +4 -0
  67. data/features/support/paths.rb +39 -0
  68. data/features/support/redcap_new_headers.csv +1 -0
  69. data/features/support/redcap_siblings.csv +1 -0
  70. data/features/support/redcap_whitespace.csv +1 -0
  71. data/features/support/selectors.rb +39 -0
  72. data/features/support/simultaneous_ajax.rb +101 -0
  73. data/features/support/single_quit_selenium_driver.rb +23 -0
  74. data/features/support/slow_updates.rb +18 -0
  75. data/features/surveyor.feature +895 -0
  76. data/features/surveyor_dependencies.feature +476 -0
  77. data/features/surveyor_parser.feature +504 -0
  78. data/features/z_redcap_parser.feature +62 -0
  79. data/lib/assets/images/surveyor/next.gif +0 -0
  80. data/lib/assets/images/surveyor/prev.gif +0 -0
  81. data/lib/assets/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  82. data/lib/assets/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  83. data/lib/assets/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  84. data/lib/assets/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  85. data/lib/assets/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  86. data/lib/assets/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  87. data/lib/assets/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  88. data/lib/assets/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  89. data/lib/assets/images/ui-icons_222222_256x240.png +0 -0
  90. data/lib/assets/images/ui-icons_2e83ff_256x240.png +0 -0
  91. data/lib/assets/images/ui-icons_454545_256x240.png +0 -0
  92. data/lib/assets/images/ui-icons_888888_256x240.png +0 -0
  93. data/lib/assets/images/ui-icons_cd0a0a_256x240.png +0 -0
  94. data/lib/assets/javascripts/surveyor/jquery-1.9.0.js +9555 -0
  95. data/lib/assets/javascripts/surveyor/jquery-ui-1.10.0.custom.js +14850 -0
  96. data/lib/assets/javascripts/surveyor/jquery-ui-timepicker-addon.js +1919 -0
  97. data/lib/assets/javascripts/surveyor/jquery.maskedinput.js +338 -0
  98. data/lib/assets/javascripts/surveyor/jquery.selectToUISlider.js +240 -0
  99. data/lib/assets/javascripts/surveyor/jquery.surveyor.js +156 -0
  100. data/lib/assets/stylesheets/surveyor/jquery-ui-1.10.0.custom.css +1174 -0
  101. data/lib/assets/stylesheets/surveyor/jquery-ui-timepicker-addon.css +11 -0
  102. data/lib/assets/stylesheets/surveyor/reset.css +48 -0
  103. data/lib/assets/stylesheets/surveyor/results.css +125 -0
  104. data/lib/assets/stylesheets/surveyor/ui.slider.extras.css +110 -0
  105. data/lib/assets/stylesheets/surveyor.sass +132 -0
  106. data/lib/generators/surveyor/custom_generator.rb +18 -0
  107. data/lib/generators/surveyor/install_generator.rb +102 -0
  108. data/lib/generators/surveyor/templates/app/assets/javascripts/surveyor_all.js +6 -0
  109. data/lib/generators/surveyor/templates/app/assets/stylesheets/surveyor_all.css +9 -0
  110. data/lib/generators/surveyor/templates/app/controllers/surveyor_controller.rb +40 -0
  111. data/lib/generators/surveyor/templates/app/views/layouts/surveyor_custom.html.erb +13 -0
  112. data/lib/generators/surveyor/templates/config/locales/surveyor_en.yml +19 -0
  113. data/lib/generators/surveyor/templates/config/locales/surveyor_es.yml +19 -0
  114. data/lib/generators/surveyor/templates/config/locales/surveyor_he.yml +19 -0
  115. data/lib/generators/surveyor/templates/config/locales/surveyor_ko.yml +19 -0
  116. data/lib/generators/surveyor/templates/db/migrate/add_api_id_to_question_groups.rb +10 -0
  117. data/lib/generators/surveyor/templates/db/migrate/add_api_ids.rb +14 -0
  118. data/lib/generators/surveyor/templates/db/migrate/add_api_ids_to_response_sets_and_responses.rb +12 -0
  119. data/lib/generators/surveyor/templates/db/migrate/add_attachment_to_response.rb +5 -0
  120. data/lib/generators/surveyor/templates/db/migrate/add_correct_answer_id_to_questions.rb +10 -0
  121. data/lib/generators/surveyor/templates/db/migrate/add_default_value_to_answers.rb +10 -0
  122. data/lib/generators/surveyor/templates/db/migrate/add_display_order_to_surveys.rb +10 -0
  123. data/lib/generators/surveyor/templates/db/migrate/add_display_type_to_answers.rb +14 -0
  124. data/lib/generators/surveyor/templates/db/migrate/add_index_to_response_sets.rb +10 -0
  125. data/lib/generators/surveyor/templates/db/migrate/add_index_to_surveys.rb +10 -0
  126. data/lib/generators/surveyor/templates/db/migrate/add_input_mask_attributes_to_answer.rb +12 -0
  127. data/lib/generators/surveyor/templates/db/migrate/add_section_id_to_responses.rb +12 -0
  128. data/lib/generators/surveyor/templates/db/migrate/add_unique_index_on_access_code_and_version_in_surveys.rb +10 -0
  129. data/lib/generators/surveyor/templates/db/migrate/add_unique_indicies.rb +18 -0
  130. data/lib/generators/surveyor/templates/db/migrate/add_version_to_surveys.rb +10 -0
  131. data/lib/generators/surveyor/templates/db/migrate/api_ids_must_be_unique.rb +23 -0
  132. data/lib/generators/surveyor/templates/db/migrate/create_answers.rb +38 -0
  133. data/lib/generators/surveyor/templates/db/migrate/create_dependencies.rb +23 -0
  134. data/lib/generators/surveyor/templates/db/migrate/create_dependency_conditions.rb +30 -0
  135. data/lib/generators/surveyor/templates/db/migrate/create_question_groups.rb +28 -0
  136. data/lib/generators/surveyor/templates/db/migrate/create_questions.rb +37 -0
  137. data/lib/generators/surveyor/templates/db/migrate/create_response_sets.rb +23 -0
  138. data/lib/generators/surveyor/templates/db/migrate/create_responses.rb +34 -0
  139. data/lib/generators/surveyor/templates/db/migrate/create_survey_sections.rb +30 -0
  140. data/lib/generators/surveyor/templates/db/migrate/create_survey_translations.rb +19 -0
  141. data/lib/generators/surveyor/templates/db/migrate/create_surveys.rb +32 -0
  142. data/lib/generators/surveyor/templates/db/migrate/create_validation_conditions.rb +33 -0
  143. data/lib/generators/surveyor/templates/db/migrate/create_validations.rb +21 -0
  144. data/lib/generators/surveyor/templates/db/migrate/drop_unique_index_on_access_code_in_surveys.rb +10 -0
  145. data/lib/generators/surveyor/templates/db/migrate/update_blank_api_ids_on_question_group.rb +22 -0
  146. data/lib/generators/surveyor/templates/db/migrate/update_blank_versions_on_surveys.rb +13 -0
  147. data/lib/generators/surveyor/templates/surveys/EXTENDING_SURVEYOR.md +52 -0
  148. data/lib/generators/surveyor/templates/surveys/MODIFYING_SURVEYOR.md +91 -0
  149. data/lib/generators/surveyor/templates/surveys/date_survey.rb +16 -0
  150. data/lib/generators/surveyor/templates/surveys/kitchen_sink_survey.rb +284 -0
  151. data/lib/generators/surveyor/templates/surveys/languages.rb +14 -0
  152. data/lib/generators/surveyor/templates/surveys/quiz.rb +11 -0
  153. data/lib/generators/surveyor/templates/surveys/translations/languages.es.yml +18 -0
  154. data/lib/generators/surveyor/templates/surveys/translations/languages.he.yml +18 -0
  155. data/lib/generators/surveyor/templates/surveys/translations/languages.ko.yml +18 -0
  156. data/lib/generators/surveyor/templates/vendor/assets/stylesheets/custom.sass +5 -0
  157. data/lib/surveyor/acts_as_response.rb +17 -0
  158. data/lib/surveyor/common.rb +59 -0
  159. data/lib/surveyor/engine.rb +10 -0
  160. data/lib/surveyor/helpers/asset_pipeline.rb +13 -0
  161. data/lib/surveyor/helpers/formtastic_custom_input.rb +13 -0
  162. data/lib/surveyor/helpers/surveyor_helper_methods.rb +103 -0
  163. data/lib/surveyor/models/answer_methods.rb +86 -0
  164. data/lib/surveyor/models/dependency_condition_methods.rb +72 -0
  165. data/lib/surveyor/models/dependency_methods.rb +60 -0
  166. data/lib/surveyor/models/question_group_methods.rb +61 -0
  167. data/lib/surveyor/models/question_methods.rb +115 -0
  168. data/lib/surveyor/models/response_methods.rb +132 -0
  169. data/lib/surveyor/models/response_set_methods.rb +196 -0
  170. data/lib/surveyor/models/survey_methods.rb +103 -0
  171. data/lib/surveyor/models/survey_section_methods.rb +56 -0
  172. data/lib/surveyor/models/survey_translation_methods.rb +33 -0
  173. data/lib/surveyor/models/validation_condition_methods.rb +56 -0
  174. data/lib/surveyor/models/validation_methods.rb +48 -0
  175. data/lib/surveyor/mustache_context.rb +11 -0
  176. data/lib/surveyor/parser.rb +428 -0
  177. data/lib/surveyor/redcap_parser.rb +289 -0
  178. data/lib/surveyor/surveyor_controller_methods.rb +237 -0
  179. data/lib/surveyor/unparser.rb +147 -0
  180. data/lib/surveyor/version.rb +3 -0
  181. data/lib/surveyor.rb +13 -0
  182. data/lib/tasks/surveyor_tasks.rake +88 -0
  183. data/rails/init.rb +1 -0
  184. data/spec/controllers/surveyor_controller_spec.rb +307 -0
  185. data/spec/factories.rb +161 -0
  186. data/spec/helpers/formtastic_custom_input_spec.rb +15 -0
  187. data/spec/helpers/surveyor_helper_spec.rb +118 -0
  188. data/spec/lib/benchmark_spec.rb +22 -0
  189. data/spec/lib/chinese_survey.rb +14 -0
  190. data/spec/lib/common_spec.rb +34 -0
  191. data/spec/lib/parser_spec.rb +204 -0
  192. data/spec/lib/rake_kitchen_sink.rb +40 -0
  193. data/spec/lib/redcap_parser_spec.rb +75 -0
  194. data/spec/lib/tasks_spec.rake +26 -0
  195. data/spec/lib/unparser_spec.rb +126 -0
  196. data/spec/models/answer_spec.rb +175 -0
  197. data/spec/models/dependency_condition_spec.rb +439 -0
  198. data/spec/models/dependency_spec.rb +101 -0
  199. data/spec/models/question_group_spec.rb +93 -0
  200. data/spec/models/question_spec.rb +207 -0
  201. data/spec/models/response_set_spec.rb +477 -0
  202. data/spec/models/response_spec.rb +218 -0
  203. data/spec/models/survey_section_spec.rb +85 -0
  204. data/spec/models/survey_spec.rb +191 -0
  205. data/spec/models/validation_condition_spec.rb +113 -0
  206. data/spec/models/validation_spec.rb +74 -0
  207. data/spec/rcov.opts +2 -0
  208. data/spec/spec.opts +4 -0
  209. data/spec/spec_helper.rb +56 -0
  210. data/stacktests.sh +65 -0
  211. data/surveyor.gemspec +45 -0
  212. 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
+ "&nbsp;&nbsp;You answered &quot;#{trigger_responses.join("&quot; and &quot;")}&quot; to the question &quot;#{dependent_questions.map(&:text).join("&quot;,&quot;")}&quot;"
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