upgrade_surveyor 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (203) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +30 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +18 -0
  5. data/CHANGELOG.md +253 -0
  6. data/Gemfile +5 -0
  7. data/Gemfile.rails_version +25 -0
  8. data/MIT-LICENSE +20 -0
  9. data/README.md +164 -0
  10. data/Rakefile +65 -0
  11. data/app/controllers/surveyor_controller.rb +5 -0
  12. data/app/helpers/results_helper.rb +20 -0
  13. data/app/helpers/survey_form_builder.rb +37 -0
  14. data/app/helpers/surveyor_helper.rb +3 -0
  15. data/app/inputs/quiet_input.rb +5 -0
  16. data/app/inputs/surveyor_check_boxes_input.rb +35 -0
  17. data/app/inputs/surveyor_radio_input.rb +18 -0
  18. data/app/models/answer.rb +3 -0
  19. data/app/models/dependency.rb +3 -0
  20. data/app/models/dependency_condition.rb +3 -0
  21. data/app/models/permitted_params.rb +102 -0
  22. data/app/models/question.rb +3 -0
  23. data/app/models/question_group.rb +3 -0
  24. data/app/models/response.rb +4 -0
  25. data/app/models/response_set.rb +3 -0
  26. data/app/models/survey.rb +3 -0
  27. data/app/models/survey_section.rb +4 -0
  28. data/app/models/survey_translation.rb +4 -0
  29. data/app/models/validation.rb +3 -0
  30. data/app/models/validation_condition.rb +3 -0
  31. data/app/views/layouts/results.html.erb +13 -0
  32. data/app/views/layouts/surveyor_default.html.erb +12 -0
  33. data/app/views/partials/_answer.html.haml +25 -0
  34. data/app/views/partials/_dependents.html.haml +5 -0
  35. data/app/views/partials/_question.html.haml +28 -0
  36. data/app/views/partials/_question_group.html.haml +44 -0
  37. data/app/views/partials/_section.html.haml +12 -0
  38. data/app/views/partials/_section_menu.html.haml +12 -0
  39. data/app/views/surveyor/edit.html.haml +21 -0
  40. data/app/views/surveyor/export.json.rabl +85 -0
  41. data/app/views/surveyor/new.html.haml +24 -0
  42. data/app/views/surveyor/show.html.haml +74 -0
  43. data/app/views/surveyor/show.json.rabl +15 -0
  44. data/config/routes.rb +8 -0
  45. data/doc/REPRESENTATIONS.md +34 -0
  46. data/doc/api_id_schema.json +7 -0
  47. data/doc/question types.png +0 -0
  48. data/doc/response_set_schema.json +54 -0
  49. data/doc/surveyor question combinations.png +0 -0
  50. data/doc/surveyor reject or delete decision matrix.png +0 -0
  51. data/doc/surveyor_models.png +0 -0
  52. data/doc/surveyor_models2.png +0 -0
  53. data/doc/surveyor_timestamp_schema.json +9 -0
  54. data/lib/assets/images/surveyor/next.gif +0 -0
  55. data/lib/assets/images/surveyor/prev.gif +0 -0
  56. data/lib/assets/images/surveyor/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  57. data/lib/assets/images/surveyor/ui-bg_flat_75_ffffff_40x100.png +0 -0
  58. data/lib/assets/images/surveyor/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  59. data/lib/assets/images/surveyor/ui-bg_glass_65_ffffff_1x400.png +0 -0
  60. data/lib/assets/images/surveyor/ui-bg_glass_75_dadada_1x400.png +0 -0
  61. data/lib/assets/images/surveyor/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  62. data/lib/assets/images/surveyor/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  63. data/lib/assets/images/surveyor/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  64. data/lib/assets/images/surveyor/ui-icons_222222_256x240.png +0 -0
  65. data/lib/assets/images/surveyor/ui-icons_2e83ff_256x240.png +0 -0
  66. data/lib/assets/images/surveyor/ui-icons_454545_256x240.png +0 -0
  67. data/lib/assets/images/surveyor/ui-icons_888888_256x240.png +0 -0
  68. data/lib/assets/images/surveyor/ui-icons_cd0a0a_256x240.png +0 -0
  69. data/lib/assets/javascripts/surveyor/jquery-1.9.0.js +9555 -0
  70. data/lib/assets/javascripts/surveyor/jquery-ui-1.10.0.custom.js +14850 -0
  71. data/lib/assets/javascripts/surveyor/jquery-ui-timepicker-addon.js +1919 -0
  72. data/lib/assets/javascripts/surveyor/jquery.maskedinput.js +338 -0
  73. data/lib/assets/javascripts/surveyor/jquery.selectToUISlider.js +240 -0
  74. data/lib/assets/javascripts/surveyor/jquery.surveyor.js +154 -0
  75. data/lib/assets/stylesheets/surveyor/jquery-ui-1.10.0.custom.sass +1380 -0
  76. data/lib/assets/stylesheets/surveyor/jquery-ui-timepicker-addon.css +11 -0
  77. data/lib/assets/stylesheets/surveyor/reset.css +48 -0
  78. data/lib/assets/stylesheets/surveyor/results.css +125 -0
  79. data/lib/assets/stylesheets/surveyor/ui.slider.extras.css +110 -0
  80. data/lib/assets/stylesheets/surveyor.sass +132 -0
  81. data/lib/generators/surveyor/custom_generator.rb +18 -0
  82. data/lib/generators/surveyor/install_generator.rb +98 -0
  83. data/lib/generators/surveyor/templates/app/assets/javascripts/surveyor_all.js +6 -0
  84. data/lib/generators/surveyor/templates/app/assets/stylesheets/surveyor_all.css +9 -0
  85. data/lib/generators/surveyor/templates/app/controllers/surveyor_controller.rb +40 -0
  86. data/lib/generators/surveyor/templates/app/views/layouts/surveyor_custom.html.erb +13 -0
  87. data/lib/generators/surveyor/templates/config/locales/surveyor_en.yml +19 -0
  88. data/lib/generators/surveyor/templates/config/locales/surveyor_es.yml +19 -0
  89. data/lib/generators/surveyor/templates/config/locales/surveyor_he.yml +19 -0
  90. data/lib/generators/surveyor/templates/config/locales/surveyor_ko.yml +19 -0
  91. data/lib/generators/surveyor/templates/db/migrate/add_api_id_to_question_groups.rb +10 -0
  92. data/lib/generators/surveyor/templates/db/migrate/add_api_ids.rb +14 -0
  93. data/lib/generators/surveyor/templates/db/migrate/add_api_ids_to_response_sets_and_responses.rb +12 -0
  94. data/lib/generators/surveyor/templates/db/migrate/add_correct_answer_id_to_questions.rb +10 -0
  95. data/lib/generators/surveyor/templates/db/migrate/add_default_value_to_answers.rb +10 -0
  96. data/lib/generators/surveyor/templates/db/migrate/add_display_order_to_surveys.rb +10 -0
  97. data/lib/generators/surveyor/templates/db/migrate/add_display_type_to_answers.rb +14 -0
  98. data/lib/generators/surveyor/templates/db/migrate/add_index_to_response_sets.rb +10 -0
  99. data/lib/generators/surveyor/templates/db/migrate/add_index_to_surveys.rb +10 -0
  100. data/lib/generators/surveyor/templates/db/migrate/add_input_mask_attributes_to_answer.rb +12 -0
  101. data/lib/generators/surveyor/templates/db/migrate/add_section_id_to_responses.rb +12 -0
  102. data/lib/generators/surveyor/templates/db/migrate/add_unique_index_on_access_code_and_version_in_surveys.rb +10 -0
  103. data/lib/generators/surveyor/templates/db/migrate/add_unique_indicies.rb +18 -0
  104. data/lib/generators/surveyor/templates/db/migrate/add_version_to_surveys.rb +10 -0
  105. data/lib/generators/surveyor/templates/db/migrate/api_ids_must_be_unique.rb +23 -0
  106. data/lib/generators/surveyor/templates/db/migrate/create_answers.rb +38 -0
  107. data/lib/generators/surveyor/templates/db/migrate/create_dependencies.rb +23 -0
  108. data/lib/generators/surveyor/templates/db/migrate/create_dependency_conditions.rb +30 -0
  109. data/lib/generators/surveyor/templates/db/migrate/create_question_groups.rb +28 -0
  110. data/lib/generators/surveyor/templates/db/migrate/create_questions.rb +37 -0
  111. data/lib/generators/surveyor/templates/db/migrate/create_response_sets.rb +23 -0
  112. data/lib/generators/surveyor/templates/db/migrate/create_responses.rb +34 -0
  113. data/lib/generators/surveyor/templates/db/migrate/create_survey_sections.rb +30 -0
  114. data/lib/generators/surveyor/templates/db/migrate/create_survey_translations.rb +19 -0
  115. data/lib/generators/surveyor/templates/db/migrate/create_surveys.rb +32 -0
  116. data/lib/generators/surveyor/templates/db/migrate/create_validation_conditions.rb +33 -0
  117. data/lib/generators/surveyor/templates/db/migrate/create_validations.rb +21 -0
  118. data/lib/generators/surveyor/templates/db/migrate/drop_unique_index_on_access_code_in_surveys.rb +10 -0
  119. data/lib/generators/surveyor/templates/db/migrate/update_blank_api_ids_on_question_group.rb +22 -0
  120. data/lib/generators/surveyor/templates/db/migrate/update_blank_versions_on_surveys.rb +13 -0
  121. data/lib/generators/surveyor/templates/surveys/EXTENDING_SURVEYOR.md +52 -0
  122. data/lib/generators/surveyor/templates/surveys/MODIFYING_SURVEYOR.md +91 -0
  123. data/lib/generators/surveyor/templates/surveys/date_survey.rb +16 -0
  124. data/lib/generators/surveyor/templates/surveys/kitchen_sink_survey.rb +280 -0
  125. data/lib/generators/surveyor/templates/surveys/languages.rb +14 -0
  126. data/lib/generators/surveyor/templates/surveys/quiz.rb +11 -0
  127. data/lib/generators/surveyor/templates/surveys/translations/languages.es.yml +18 -0
  128. data/lib/generators/surveyor/templates/surveys/translations/languages.he.yml +18 -0
  129. data/lib/generators/surveyor/templates/surveys/translations/languages.ko.yml +18 -0
  130. data/lib/generators/surveyor/templates/vendor/assets/stylesheets/custom.sass +5 -0
  131. data/lib/surveyor/acts_as_response.rb +19 -0
  132. data/lib/surveyor/common.rb +59 -0
  133. data/lib/surveyor/engine.rb +15 -0
  134. data/lib/surveyor/helpers/formtastic_custom_input.rb +13 -0
  135. data/lib/surveyor/helpers/surveyor_helper_methods.rb +86 -0
  136. data/lib/surveyor/models/answer_methods.rb +76 -0
  137. data/lib/surveyor/models/dependency_condition_methods.rb +65 -0
  138. data/lib/surveyor/models/dependency_methods.rb +57 -0
  139. data/lib/surveyor/models/question_group_methods.rb +62 -0
  140. data/lib/surveyor/models/question_methods.rb +103 -0
  141. data/lib/surveyor/models/response_methods.rb +125 -0
  142. data/lib/surveyor/models/response_set_methods.rb +187 -0
  143. data/lib/surveyor/models/survey_methods.rb +95 -0
  144. data/lib/surveyor/models/survey_section_methods.rb +48 -0
  145. data/lib/surveyor/models/survey_translation_methods.rb +28 -0
  146. data/lib/surveyor/models/validation_condition_methods.rb +42 -0
  147. data/lib/surveyor/models/validation_methods.rb +41 -0
  148. data/lib/surveyor/mustache_context.rb +13 -0
  149. data/lib/surveyor/parser.rb +427 -0
  150. data/lib/surveyor/redcap_parser.rb +288 -0
  151. data/lib/surveyor/surveyor_controller_methods.rb +237 -0
  152. data/lib/surveyor/unparser.rb +161 -0
  153. data/lib/surveyor/version.rb +3 -0
  154. data/lib/surveyor.rb +13 -0
  155. data/lib/tasks/surveyor_tasks.rake +88 -0
  156. data/rails/init.rb +1 -0
  157. data/spec/controllers/surveyor_controller_spec.rb +304 -0
  158. data/spec/factories.rb +166 -0
  159. data/spec/features/ajax_spec.rb +205 -0
  160. data/spec/features/api_spec.rb +157 -0
  161. data/spec/features/duplicate_prevention_spec.rb +50 -0
  162. data/spec/features/translations_spec.rb +116 -0
  163. data/spec/features/ui_spec.rb +466 -0
  164. data/spec/fixtures/REDCapDemoDatabase_DataDictionary.csv +127 -0
  165. data/spec/fixtures/chinese_survey.rb +14 -0
  166. data/spec/fixtures/everything.rb +215 -0
  167. data/spec/fixtures/favorites-ish.rb +22 -0
  168. data/spec/fixtures/favorites.rb +22 -0
  169. data/spec/fixtures/feelings.rb +38 -0
  170. data/spec/fixtures/lifestyle.rb +55 -0
  171. data/spec/fixtures/numbers.rb +21 -0
  172. data/spec/fixtures/redcap_new_headers.csv +1 -0
  173. data/spec/fixtures/redcap_siblings.csv +1 -0
  174. data/spec/fixtures/redcap_whitespace.csv +1 -0
  175. data/spec/helpers/formtastic_custom_input_spec.rb +15 -0
  176. data/spec/helpers/surveyor_helper_spec.rb +114 -0
  177. data/spec/lib/common_spec.rb +37 -0
  178. data/spec/lib/parser_spec.rb +393 -0
  179. data/spec/lib/rake_kitchen_sink.rb +40 -0
  180. data/spec/lib/redcap_parser_spec.rb +129 -0
  181. data/spec/lib/tasks_spec.rake +26 -0
  182. data/spec/lib/unparser_spec.rb +126 -0
  183. data/spec/models/answer_spec.rb +144 -0
  184. data/spec/models/dependency_condition_spec.rb +428 -0
  185. data/spec/models/dependency_spec.rb +90 -0
  186. data/spec/models/question_group_spec.rb +66 -0
  187. data/spec/models/question_spec.rb +176 -0
  188. data/spec/models/response_set_spec.rb +447 -0
  189. data/spec/models/response_spec.rb +205 -0
  190. data/spec/models/survey_section_spec.rb +58 -0
  191. data/spec/models/survey_spec.rb +155 -0
  192. data/spec/models/validation_condition_spec.rb +98 -0
  193. data/spec/models/validation_spec.rb +64 -0
  194. data/spec/rcov.opts +2 -0
  195. data/spec/spec.opts +4 -0
  196. data/spec/spec_helper.rb +99 -0
  197. data/spec/support/shared.rb +5 -0
  198. data/spec/support/surveyor_api_helpers.rb +15 -0
  199. data/spec/support/surveyor_ui_helpers.rb +108 -0
  200. data/spec/support/wait_for_ajax.rb +11 -0
  201. data/surveyor.gemspec +40 -0
  202. data/switch.sh +53 -0
  203. metadata +510 -0
@@ -0,0 +1,11 @@
1
+ # encoding: UTF-8
2
+ survey "Favorites" do
3
+ section "Foods" do
4
+ # In a quiz, both the questions and the answers need to have reference identifiers
5
+ # Here, the question has reference_identifier: "1", and the answers: "oint", "tweet", and "moo"
6
+ question_1 "What is the best meat?", :pick => :one, :correct => "oink"
7
+ a_oink "bacon"
8
+ a_tweet "chicken"
9
+ a_moo "beef"
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+ title: "Un idioma nunca es suficiente"
3
+ survey_sections:
4
+ one:
5
+ title: "Uno"
6
+ two:
7
+ title: "Dos"
8
+ question_groups:
9
+ hello:
10
+ text: "¡Hola!"
11
+ questions:
12
+ name:
13
+ text: "¿Cómo se llama Usted?"
14
+ answers:
15
+ name:
16
+ help_text: "Mi nombre es..."
17
+ color:
18
+ text: "¿Cuál es tu color favorito?"
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+ title: "ידיעת שפה אחת אינה מספיקה"
3
+ survey_sections:
4
+ one:
5
+ title: "אחת"
6
+ two:
7
+ title: "שנים"
8
+ question_groups:
9
+ hello:
10
+ text: "שלום"
11
+ questions:
12
+ name:
13
+ text: "מה שמך?"
14
+ answers:
15
+ name:
16
+ help_text: "שמי..."
17
+ color:
18
+ text: "מהו הצבע האהוב עליך?"
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+ title: "한가지 언어로는 충분치 않습니다."
3
+ survey_sections:
4
+ one:
5
+ title: "하나"
6
+ two:
7
+ title: "두"
8
+ question_groups:
9
+ hello:
10
+ text: "안녕하십니까"
11
+ questions:
12
+ name:
13
+ text: "성함이 어떻게 되십니까?"
14
+ answers:
15
+ name:
16
+ help_text: "제 이름은 ... 입니다"
17
+ color:
18
+ text: "좋아하는 색은 무엇입니까?"
@@ -0,0 +1,5 @@
1
+ /* Add custom styles here. They will be
2
+ * loaded after the surveyor stylesheets */
3
+
4
+ #surveyor
5
+ :font-family "Century Gothic"
@@ -0,0 +1,19 @@
1
+ module Surveyor
2
+ module ActsAsResponse
3
+ extend ActiveSupport::Concern
4
+
5
+ # Returns the response as a particular response_class type
6
+ def as(type_symbol)
7
+ return case type_symbol.to_sym
8
+ when :string, :text, :integer, :float, :datetime
9
+ self.send("#{type_symbol}_value".to_sym)
10
+ when :date
11
+ self.datetime_value.nil? ? nil : self.datetime_value.to_date
12
+ when :time
13
+ self.datetime_value.nil? ? nil : self.datetime_value.to_time
14
+ else # :answer_id
15
+ self.answer_id
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,59 @@
1
+ require 'securerandom'
2
+ require 'uuidtools'
3
+
4
+ module Surveyor
5
+ class Common
6
+ OPERATORS = %w(== != < > <= >= =~)
7
+
8
+ class << self
9
+ if SecureRandom.respond_to?(:urlsafe_base64)
10
+ def make_tiny_code
11
+ # 7 random bytes is increased to ~10 characters (sans padding) by
12
+ # base64 encoding
13
+ SecureRandom.urlsafe_base64(7)
14
+ end
15
+ else
16
+ def make_tiny_code
17
+ s = [SecureRandom.random_bytes(7)].pack("m*")
18
+ s.delete!("\n")
19
+ s.tr!("+/", "-_")
20
+ s.delete!("=")
21
+ end
22
+ end
23
+
24
+ def to_normalized_string(text)
25
+ words_to_omit = %w(a be but has have in is it of on or the to when)
26
+ col_text = text.to_s.gsub(/(<[^>]*>)|\n|\t/su, ' ') # Remove html tags
27
+ col_text.downcase! # Remove capitalization
28
+ col_text.gsub!(/\"|\'/u, '') # Remove potential problem characters
29
+ col_text.gsub!(/\(.*?\)/u,'') # Remove text inside parens
30
+ col_text.gsub!(/\W/u, ' ') # Remove all other non-word characters
31
+ cols = (col_text.split(' ') - words_to_omit)
32
+ (cols.size > 5 ? cols[-5..-1] : cols).join("_")
33
+ end
34
+
35
+ alias :normalize :to_normalized_string
36
+
37
+ def generate_api_id
38
+ UUIDTools::UUID.random_create.to_s
39
+ end
40
+
41
+ ##
42
+ # @private Intended for internal use only.
43
+ #
44
+ # Loads and uses either `FasterCSV` (for Ruby 1.8) or the stdlib `CSV`
45
+ # (for Ruby 1.9+).
46
+ #
47
+ # @return [Class] either `CSV` for `FasterCSV`.
48
+ def csv_impl
49
+ @csv_impl ||= if RUBY_VERSION < '1.9'
50
+ require 'fastercsv'
51
+ FasterCSV
52
+ else
53
+ require 'csv'
54
+ CSV
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,15 @@
1
+ require 'rails'
2
+ require 'surveyor'
3
+ require 'haml' # required for view resolution
4
+
5
+ module Surveyor
6
+ class Engine < Rails::Engine
7
+ root = File.expand_path('../../', __FILE__)
8
+ config.autoload_paths << root
9
+ config.to_prepare do
10
+ Dir.glob(File.expand_path('../../../app/inputs/*_input*.rb', __FILE__)).each do |c|
11
+ require_dependency(c)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ module Surveyor
2
+ module Helpers
3
+ module FormtasticCustomInput
4
+ def input_html_options_with(choice, response_class)
5
+ input_html_options.merge(choice_html_options(choice)).merge({:id => (input_html_options[:id] || "answer_id").gsub("answer_id", response_class_to_method(response_class).to_s), :class => [input_html_options[:class], response_class].join(" ")})
6
+ end
7
+ def response_class_to_method(type_sym)
8
+ # doesn't handle response_class == answer, and doesn't have to
9
+ "#{type_sym.to_s}_value".to_sym
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,86 @@
1
+ module Surveyor
2
+ module Helpers
3
+ module SurveyorHelperMethods
4
+
5
+ # Layout: stylsheets and javascripts
6
+ def surveyor_includes
7
+ stylesheet_link_tag('surveyor_all') + javascript_include_tag('surveyor_all')
8
+ end
9
+ # Helper for displaying warning/notice/error flash messages
10
+ def flash_messages(types)
11
+ types.map{|type| content_tag(:div, "#{flash[type]}".html_safe, :class => type.to_s)}.join.html_safe
12
+ end
13
+ # Section: dependencies, menu, previous and next
14
+ def dependency_explanation_helper(question,response_set)
15
+ # Attempts to explain why this dependent question needs to be answered by referenced the dependent question and users response
16
+ trigger_responses = []
17
+ dependent_questions = Question.find_all_by_id(question.dependency.dependency_conditions.map(&:question_id)).uniq
18
+ response_set.responses.find_all_by_question_id(dependent_questions.map(&:id)).uniq.each do |resp|
19
+ trigger_responses << resp.to_s
20
+ end
21
+ "&nbsp;&nbsp;You answered &quot;#{trigger_responses.join("&quot; and &quot;")}&quot; to the question &quot;#{dependent_questions.map(&:text).join("&quot;,&quot;")}&quot;"
22
+ end
23
+ def menu_button_for(section)
24
+ submit_tag(section.translation(I18n.locale)[:title], :name => "section[#{section.id}]")
25
+ end
26
+ def previous_section
27
+ # use copy in memory instead of making extra db calls
28
+ prev_index = [(@sections.index(@section) || 0) - 1, 0].max
29
+ submit_tag(t('surveyor.previous_section').html_safe, :name => "section[#{@sections[prev_index].id}]") unless @sections[0] == @section
30
+ end
31
+ def next_section
32
+ # use copy in memory instead of making extra db calls
33
+ next_index = [(@sections.index(@section) || @sections.count) + 1, @sections.count].min
34
+ @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[next_index].id}]")
35
+ end
36
+
37
+ # Questions
38
+ def q_text(q, context=nil, locale=nil)
39
+ "#{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)}"
40
+ end
41
+
42
+ def next_question_number(question)
43
+ @n ||= 0
44
+ "<span class='qnum'>#{@n += 1}) </span>"
45
+ end
46
+
47
+ # Responses
48
+ def rc_to_attr(type_sym)
49
+ case type_sym.to_s
50
+ when /^answer$/ then :answer_id
51
+ else "#{type_sym.to_s}_value".to_sym
52
+ end
53
+ end
54
+
55
+ def rc_to_as(type_sym)
56
+ case type_sym.to_s
57
+ when /(integer|float|date|time|datetime)/ then :string
58
+ else type_sym
59
+ end
60
+ end
61
+
62
+ def generate_pick_none_input_html(value, default_value, css_class, response_class, disabled, input_mask, input_mask_placeholder)
63
+ html = {}
64
+ html[:class] = [response_class,css_class].reject{ |c| c.blank? }
65
+ html[:value] = value.blank? ? default_value : value
66
+ html[:disabled] = disabled unless disabled.blank?
67
+ if input_mask
68
+ html[:'data-input-mask'] = input_mask
69
+ html[:'data-input-mask-placeholder'] = input_mask_placeholder unless input_mask_placeholder.blank?
70
+ end
71
+ html
72
+ end
73
+
74
+ # Responses
75
+ def response_for(response_set, question, answer = nil, response_group = nil)
76
+ return nil unless response_set && question && question.id
77
+ 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)}
78
+ result.blank? ? response_set.responses.build(:question_id => question.id, :response_group => response_group) : result
79
+ end
80
+ def response_idx(increment = true)
81
+ @rc ||= 0
82
+ (increment ? @rc += 1 : @rc).to_s
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,76 @@
1
+ require 'surveyor/common'
2
+ module Surveyor
3
+ module Models
4
+ module AnswerMethods
5
+ extend ActiveSupport::Concern
6
+ include ActiveModel::Validations
7
+ include MustacheContext
8
+ include ActiveModel::ForbiddenAttributesProtection
9
+
10
+ included do
11
+ # Associations
12
+ belongs_to :question
13
+ has_many :responses
14
+ has_many :validations, :dependent => :destroy
15
+ attr_accessible *PermittedParams.new.answer_attributes if defined? ActiveModel::MassAssignmentSecurity
16
+
17
+ # Validations
18
+ validates_presence_of :text
19
+ end
20
+
21
+ # Instance Methods
22
+ def initialize(*args)
23
+ super(*args)
24
+ default_args
25
+ end
26
+
27
+ def default_args
28
+ self.is_exclusive ||= false
29
+ self.display_type ||= "default"
30
+ self.response_class ||= "answer"
31
+ self.short_text ||= text
32
+ self.data_export_identifier ||= Surveyor::Common.normalize(text)
33
+ self.api_id ||= Surveyor::Common.generate_api_id
34
+ end
35
+ def display_type=(val)
36
+ write_attribute(:display_type, val.nil? ? nil : val.to_s)
37
+ end
38
+
39
+ def css_class
40
+ [(is_exclusive ? "exclusive" : nil), custom_class].compact.join(" ")
41
+ end
42
+
43
+ def text_for(position = nil, context = nil, locale = nil)
44
+ return "" if display_type == "hidden_label"
45
+ imaged(split(in_context(translation(locale)[:text], context), position))
46
+ end
47
+ def help_text_for(context = nil, locale = nil)
48
+ in_context(translation(locale)[:help_text], context)
49
+ end
50
+ def default_value_for(context = nil, locale = nil)
51
+ in_context(translation(locale)[:default_value], context)
52
+ end
53
+ def split(text, position=nil)
54
+ case position
55
+ when :pre
56
+ text.split("|",2)[0]
57
+ when :post
58
+ text.split("|",2)[1]
59
+ else
60
+ text
61
+ end.to_s
62
+ end
63
+ def translation(locale)
64
+ {:text => self.text, :help_text => self.help_text, :default_value => self.default_value}.with_indifferent_access.merge(
65
+ (self.question.translation(locale)[:answers] || {})[self.reference_identifier] || {}
66
+ )
67
+ end
68
+
69
+ private
70
+
71
+ def imaged(text)
72
+ self.display_type == "image" && !text.blank? ? ActionController::Base.helpers.image_tag(text) : text
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,65 @@
1
+ module Surveyor
2
+ module Models
3
+ module DependencyConditionMethods
4
+ extend ActiveSupport::Concern
5
+ include ActiveModel::Validations
6
+ include Surveyor::ActsAsResponse # includes "as" instance method
7
+ include ActiveModel::ForbiddenAttributesProtection
8
+
9
+ included do
10
+ # Associations
11
+ belongs_to :answer
12
+ belongs_to :dependency
13
+ belongs_to :dependent_question, :foreign_key => :question_id, :class_name => :question
14
+ belongs_to :question
15
+ attr_accessible *PermittedParams.new.dependency_condition_attributes if defined? ActiveModel::MassAssignmentSecurity
16
+
17
+ # Validations
18
+ validates_presence_of :operator, :rule_key
19
+ validate :validates_operator
20
+ validates_uniqueness_of :rule_key, :scope => :dependency_id
21
+ end
22
+
23
+ module ClassMethods
24
+ def operators
25
+ Surveyor::Common::OPERATORS
26
+ end
27
+ end
28
+
29
+ # Instance methods
30
+ def to_hash(response_set)
31
+ # all responses to associated question
32
+ responses = question.blank? ? [] : response_set.responses.where("responses.answer_id in (?)", question.answer_ids)
33
+ if self.operator.match /^count(>|>=|<|<=|==|!=)\d+$/
34
+ op, i = self.operator.scan(/^count(>|>=|<|<=|==|!=)(\d+)$/).flatten
35
+ # logger.warn({rule_key.to_sym => responses.count.send(op, i.to_i)})
36
+ return {rule_key.to_sym => (op == "!=" ? !responses.count.send("==", i.to_i) : responses.count.send(op, i.to_i))}
37
+ elsif operator == "!=" and (responses.blank? or responses.none?{|r| r.answer.id == self.answer.id})
38
+ # logger.warn( {rule_key.to_sym => true})
39
+ return {rule_key.to_sym => true}
40
+ elsif response = responses.to_a.detect{|r| r.answer.id == self.answer.id}
41
+ klass = response.answer.response_class
42
+ klass = "answer" if self.as(klass).nil? # it should compare answer ids when the dependency condition *_value is nil
43
+ case self.operator
44
+ when "==", "<", ">", "<=", ">="
45
+ # logger.warn( {rule_key.to_sym => response.as(klass).send(self.operator, self.as(klass))})
46
+ return {rule_key.to_sym => !response.as(klass).nil? && response.as(klass).send(self.operator, self.as(klass))}
47
+ when "!="
48
+ # logger.warn( {rule_key.to_sym => !response.as(klass).send("==", self.as(klass))})
49
+ return {rule_key.to_sym => !response.as(klass).send("==", self.as(klass))}
50
+ end
51
+ end
52
+ # logger.warn({rule_key.to_sym => false})
53
+ {rule_key.to_sym => false}
54
+ end
55
+
56
+ protected
57
+
58
+ def validates_operator
59
+ errors.add(:operator, "Invalid operator") unless
60
+ Surveyor::Common::OPERATORS.include?(self.operator) ||
61
+ self.operator && self.operator.match(/^count(<|>|==|>=|<=|!=)(\d+)/)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,57 @@
1
+ module Surveyor
2
+ module Models
3
+ module DependencyMethods
4
+ extend ActiveSupport::Concern
5
+ include ActiveModel::Validations
6
+ include ActiveModel::ForbiddenAttributesProtection
7
+
8
+ included do
9
+ # Associations
10
+ belongs_to :question
11
+ belongs_to :question_group
12
+ has_many :dependency_conditions, :dependent => :destroy
13
+ attr_accessible *PermittedParams.new.dependency_attributes if defined? ActiveModel::MassAssignmentSecurity
14
+
15
+ # Validations
16
+ validates_presence_of :rule
17
+ validates_format_of :rule, :with => /\A(?:and|or|\)|\(|[A-Z]|\s)+\Z/ #TODO properly formed parenthesis etc.
18
+ validates_numericality_of :question_id, :if => Proc.new { |d| d.question_group_id.nil? }
19
+ validates_numericality_of :question_group_id, :if => Proc.new { |d| d.question_id.nil? }
20
+
21
+ # Attribute aliases
22
+ alias_attribute :dependent_question_id, :question_id
23
+ end
24
+
25
+ # Instance Methods
26
+ def question_group_id=(i)
27
+ write_attribute(:question_id, nil) unless i.nil?
28
+ write_attribute(:question_group_id, i)
29
+ end
30
+
31
+ def question_id=(i)
32
+ write_attribute(:question_group_id, nil) unless i.nil?
33
+ write_attribute(:question_id, i)
34
+ end
35
+
36
+ # Has this dependency has been met in the context of response_set?
37
+ # Substitutes the conditions hash into the rule and evaluates it
38
+ def is_met?(response_set)
39
+ ch = conditions_hash(response_set)
40
+ return false if ch.blank?
41
+ # logger.debug "rule: #{self.rule.inspect}"
42
+ # logger.debug "rexp: #{rgx.inspect}"
43
+ # logger.debug "keyp: #{ch.inspect}"
44
+ # logger.debug "subd: #{self.rule.gsub(rgx){|m| ch[m.to_sym]}}"
45
+ 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
46
+ eval(self.rule.gsub(rgx){|m| ch[m.to_sym]})
47
+ end
48
+
49
+ # A hash of the conditions (keyed by rule_key) and their evaluation (boolean) in the context of response_set
50
+ def conditions_hash(response_set)
51
+ hash = {}
52
+ self.dependency_conditions.each{|dc| hash.merge!(dc.to_hash(response_set))}
53
+ return hash
54
+ end
55
+ end
56
+ end
57
+ end