study_engine 0.0.2

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 (138) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +42 -0
  3. data/app/assets/images/study_engine/spinner.gif +0 -0
  4. data/app/assets/images/study_engine/sprite/delete.png +0 -0
  5. data/app/assets/images/study_engine/sprite/download.png +0 -0
  6. data/app/assets/images/study_engine/sprite/edit.png +0 -0
  7. data/app/assets/images/study_engine/sprite/incomplete.png +0 -0
  8. data/app/assets/images/study_engine/sprite/metrc-sm.png +0 -0
  9. data/app/assets/images/study_engine/sprite/metrc.png +0 -0
  10. data/app/assets/images/study_engine/sprite/new.png +0 -0
  11. data/app/assets/javascripts/study_engine/admin_notes.js.coffee +12 -0
  12. data/app/assets/javascripts/study_engine/assessment_notifications.js.coffee +36 -0
  13. data/app/assets/javascripts/study_engine/assessment_timeout.js.coffee +24 -0
  14. data/app/assets/javascripts/study_engine/disable_buttons_after_click.js.coffee +6 -0
  15. data/app/assets/javascripts/study_engine/fire_click_event_immediately_on_mobile.js.coffee.erb +10 -0
  16. data/app/assets/javascripts/study_engine/response_validation.js.coffee +8 -0
  17. data/app/assets/javascripts/study_engine/restart_assessment_button.js.coffee +3 -0
  18. data/app/assets/javascripts/study_engine/study_engine.js.coffee +9 -0
  19. data/app/assets/javascripts/study_engine/study_id_picker.js.coffee +28 -0
  20. data/app/assets/stylesheets/study_engine.sass +1 -0
  21. data/app/assets/stylesheets/study_engine/_constant.sass +52 -0
  22. data/app/assets/stylesheets/study_engine/_mobile.sass +8 -0
  23. data/app/assets/stylesheets/study_engine/_tablet.sass +36 -0
  24. data/app/assets/stylesheets/study_engine/application.sass +101 -0
  25. data/app/assets/stylesheets/study_engine/base/_form.sass +131 -0
  26. data/app/assets/stylesheets/study_engine/base/_list.sass +38 -0
  27. data/app/assets/stylesheets/study_engine/base/_tables.sass +33 -0
  28. data/app/assets/stylesheets/study_engine/base/_text.sass +78 -0
  29. data/app/assets/stylesheets/study_engine/components/-fields/_login-fields.sass +14 -0
  30. data/app/assets/stylesheets/study_engine/components/-fields/_start-fields.sass +101 -0
  31. data/app/assets/stylesheets/study_engine/components/-nav/_global-nav.sass +19 -0
  32. data/app/assets/stylesheets/study_engine/components/-table/_results-table.sass +75 -0
  33. data/app/assets/stylesheets/study_engine/components/-table/_users-table.sass +11 -0
  34. data/app/assets/stylesheets/study_engine/components/_assessment-questions.sass +30 -0
  35. data/app/assets/stylesheets/study_engine/components/_form-buttons.sass +102 -0
  36. data/app/assets/stylesheets/study_engine/components/_helpt-text.sass +2 -0
  37. data/app/assets/stylesheets/study_engine/components/_method-icons.sass +15 -0
  38. data/app/assets/stylesheets/study_engine/components/_notes-modal.sass +65 -0
  39. data/app/assets/stylesheets/study_engine/components/_progress-bar.sass +25 -0
  40. data/app/assets/stylesheets/study_engine/components/_sortable-columns.sass +26 -0
  41. data/app/assets/stylesheets/study_engine/components/_table-actions.sass +8 -0
  42. data/app/assets/stylesheets/study_engine/components/_table-buttons.sass +17 -0
  43. data/app/assets/stylesheets/study_engine/components/_user-privileges.sass +3 -0
  44. data/app/assets/stylesheets/study_engine/mixins/_actions-table.sass +11 -0
  45. data/app/assets/stylesheets/study_engine/mixins/_buttons.sass +55 -0
  46. data/app/assets/stylesheets/study_engine/mixins/_checkbox-columns.sass +50 -0
  47. data/app/assets/stylesheets/study_engine/mixins/_custom-radios.sass +47 -0
  48. data/app/assets/stylesheets/study_engine/mixins/_icons.sass +38 -0
  49. data/app/assets/stylesheets/study_engine/mixins/_notification-text.sass +23 -0
  50. data/app/controllers/study_engine/admin/application_controller.rb +6 -0
  51. data/app/controllers/study_engine/admin/assessments_controller.rb +79 -0
  52. data/app/controllers/study_engine/admin/notes_controller.rb +17 -0
  53. data/app/controllers/study_engine/application_controller.rb +12 -0
  54. data/app/controllers/study_engine/assessments_controller.rb +94 -0
  55. data/app/helpers/study_engine/helpers.rb +15 -0
  56. data/app/helpers/study_engine/mailer_helpers.rb +11 -0
  57. data/app/mailers/study_engine/mailer.rb +25 -0
  58. data/app/models/study_engine/answer.rb +36 -0
  59. data/app/models/study_engine/assessment.rb +118 -0
  60. data/app/models/study_engine/begin_assessment_form.rb +110 -0
  61. data/app/models/study_engine/existing_assessments_form.rb +99 -0
  62. data/app/models/study_engine/form.rb +59 -0
  63. data/app/models/study_engine/report.rb +69 -0
  64. data/app/models/study_engine/result.rb +46 -0
  65. data/app/models/study_engine/screen_results.rb +54 -0
  66. data/app/models/study_engine/study_id.rb +47 -0
  67. data/app/models/study_engine/study_id/bank.rb +38 -0
  68. data/app/models/study_engine/study_id/sync.rb +52 -0
  69. data/app/models/study_engine/update_assessments_form.rb +53 -0
  70. data/app/views/study_engine/_progress_bar.html.slim +4 -0
  71. data/app/views/study_engine/admin/assessments/_assessment.html.slim +36 -0
  72. data/app/views/study_engine/admin/assessments/domain_results.csv.csvbuilder +17 -0
  73. data/app/views/study_engine/admin/assessments/edit.html.slim +68 -0
  74. data/app/views/study_engine/admin/assessments/index.html.slim +26 -0
  75. data/app/views/study_engine/admin/assessments/item_results.csv.csvbuilder +24 -0
  76. data/app/views/study_engine/admin/assessments/new.html.slim +46 -0
  77. data/app/views/study_engine/admin/assessments/screen_results.csv.csvbuilder +2 -0
  78. data/app/views/study_engine/assessments/complete.html.slim +25 -0
  79. data/app/views/study_engine/assessments/create.html.slim +46 -0
  80. data/app/views/study_engine/assessments/index.html.slim +43 -0
  81. data/app/views/study_engine/assessments/restart.html.slim +28 -0
  82. data/app/views/study_engine/assessments/show.html.slim +60 -0
  83. data/app/views/study_engine/mailer/assessment_recorded_notification.erb +8 -0
  84. data/app/views/study_engine/mailer/assessment_url_notification.erb +6 -0
  85. data/config/routes.rb +15 -0
  86. data/db/migrate/20160422185859_create_tables.rb +52 -0
  87. data/lib/study_engine.rb +5 -0
  88. data/lib/study_engine/config.rb +37 -0
  89. data/lib/study_engine/cucumber.rb +96 -0
  90. data/lib/study_engine/engine.rb +7 -0
  91. data/lib/study_engine/version.rb +3 -0
  92. data/lib/tasks/study_engine_tasks.rake +10 -0
  93. data/spec/ar_helper.rb +30 -0
  94. data/spec/models/study_engine/answer_spec.rb +28 -0
  95. data/spec/models/study_engine/assessment_spec.rb +250 -0
  96. data/spec/models/study_engine/begin_assessment_form_spec.rb +87 -0
  97. data/spec/models/study_engine/existing_assessments_form_spec.rb +171 -0
  98. data/spec/models/study_engine/report_spec.rb +48 -0
  99. data/spec/models/study_engine/result_spec.rb +102 -0
  100. data/spec/models/study_engine/screen_results_spec.rb +69 -0
  101. data/spec/models/study_engine/study_id/bank_spec.rb +72 -0
  102. data/spec/models/study_engine/study_id/sync_spec.rb +61 -0
  103. data/spec/models/study_engine/study_id_spec.rb +7 -0
  104. data/spec/models/study_engine/update_assessments_form_spec.rb +38 -0
  105. data/spec/rails_helper.rb +50 -0
  106. data/spec/schema.rb +65 -0
  107. data/spec/spec_helper.rb +72 -0
  108. data/spec/support/CATEngine3.exe +0 -0
  109. data/spec/support/forms/01. Physical Function Bank.xml +1213 -0
  110. data/spec/support/forms/02. Pain Interference Bank.xml +443 -0
  111. data/spec/support/forms/03. Anxiety Bank.xml +322 -0
  112. data/spec/support/forms/04. Depression Bank.xml +311 -0
  113. data/spec/support/forms/05. Ability to Participate Social bank Version 2.xml +353 -0
  114. data/spec/support/forms/06. Psychosocial Illness Impact Pos Bank.xml +900 -0
  115. data/spec/support/forms/07. Applied Cognition Gen Concerns Bank.xml +377 -0
  116. data/spec/support/forms/08. Satisfaction with Roles and Activities Bank Version 2.xml +443 -0
  117. data/spec/support/forms/09. Sleep Disturbance Bank.xml +300 -0
  118. data/spec/support/forms/10. Emotional Support Bank.xml +163 -0
  119. data/spec/support/forms/A - 07. Psychosocial Illness Impact Pos Bank.xml +471 -0
  120. data/spec/support/forms/A - 08. Pain interference Bank.xml +443 -0
  121. data/spec/support/forms/A - 09. Depression Bank.xml +311 -0
  122. data/spec/support/forms/B - 07. Physical Function Short Form.xml +123 -0
  123. data/spec/support/forms/B - 08. Physical Function Remainder A.xml +543 -0
  124. data/spec/support/forms/B - 09. Ability to Participate Social bank Version 2.xml +353 -0
  125. data/spec/support/forms/C - 07. Physical Function Short Form.xml +123 -0
  126. data/spec/support/forms/C - 08. Physical Function Remainder B.xml +553 -0
  127. data/spec/support/forms/C - 09. Anxiety Bank.xml +322 -0
  128. data/spec/support/parameters/01. Physical Function Bank.xml +682 -0
  129. data/spec/support/parameters/02. Pain Interference Bank.xml +256 -0
  130. data/spec/support/parameters/03. Anxiety Bank.xml +190 -0
  131. data/spec/support/parameters/04. Depression Bank.xml +184 -0
  132. data/spec/support/parameters/05. Ability to Participate Social bank Version 2.xml +226 -0
  133. data/spec/support/parameters/06. Psychosocial Illness Impact Pos Bank.xml +211 -0
  134. data/spec/support/parameters/07. Applied Cognition Gen Concerns Bank.xml +1 -0
  135. data/spec/support/parameters/08. Satisfaction with Roles and Activities Bank Version 2.xml +1 -0
  136. data/spec/support/parameters/09. Sleep Disturbance Bank.xml +1 -0
  137. data/spec/support/parameters/10. Emotional Support Bank.xml +1 -0
  138. metadata +394 -0
@@ -0,0 +1,99 @@
1
+ require "study_engine/assessment"
2
+ require "study_engine/config"
3
+
4
+ module StudyEngine
5
+ class ExistingAssessmentsForm
6
+ include ActiveModel::Validations
7
+ extend ActiveModel::Naming
8
+
9
+ def self.model_name
10
+ ActiveModel::Name.new(Assessment)
11
+ end
12
+
13
+ validate :format_of_study_id
14
+ validate :presence_of_some_event
15
+ validate :presence_of_previous_events
16
+
17
+ def initialize attributes = {}
18
+ self.study_id = StudyID.new
19
+ self.event_datetimes ||= []
20
+ attributes.each do |name, value|
21
+ send("#{name}=", value)
22
+ end
23
+ end
24
+
25
+ def to_model
26
+ self
27
+ end
28
+
29
+ def to_key
30
+ end
31
+
32
+ def persisted?
33
+ false
34
+ end
35
+
36
+ attr_accessor :study_id, :method_applied, :event_datetimes
37
+
38
+ delegate :study, :site, :id, to: :study_id, prefix: true
39
+ delegate :study=, :site=, :id=, to: :study_id, prefix: true
40
+
41
+ def method_applied
42
+ @method_applied || "In-person"
43
+ end
44
+
45
+ def study_options
46
+ StudyID::Bank.studies
47
+ end
48
+
49
+ def save
50
+ if valid?
51
+ base = Assessment.by_study_id(study_id)
52
+ event_datetimes.each.with_index do |datetime, index|
53
+ next if datetime.blank?
54
+ event = StudyEngine.events[index]
55
+ assessment = base.by_event(event).create!(created_at: datetime, assessment_updated_at: datetime, method_applied: method_applied)
56
+ assessment.update_column :group_index, nil
57
+ end
58
+ true
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def format_of_study_id
65
+ if study_id.present?
66
+ unless study_id.valid?
67
+ errors.add :base, "METRC study ID must be in the following format: XXX-XXX-####"
68
+ end
69
+ else
70
+ errors.add :base, "METRC study ID can not be blank. Enter ID in the following format: XXX-XXX-####"
71
+ end
72
+ end
73
+
74
+ def presence_of_some_event
75
+ if event_datetimes.all?(&:blank?)
76
+ events = StudyEngine.events.dup
77
+ events.map!(&:downcase)
78
+ events.last.prepend("or ")
79
+ events = events.join(", ")
80
+ errors.add :base, "A #{events} timestamp must be present"
81
+ end
82
+ end
83
+
84
+ def presence_of_previous_events
85
+ event_datetimes.each.with_index do |datetime, index|
86
+ next if datetime.blank? || index.nil? || index.zero?
87
+ event = StudyEngine.events[index]
88
+ previous_events = StudyEngine.events[0...index]
89
+
90
+ previous_events.each.with_index do |previous_event, index|
91
+ if event_datetimes[index].blank? && Assessment.by_study_id(study_id).by_event(previous_event).none?
92
+ errors.add :base, "Cannot add a #{event.downcase} assessment when #{previous_event.downcase} assessment does not exist"
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+
@@ -0,0 +1,59 @@
1
+ require "active_support/core_ext/module/delegation"
2
+
3
+ module StudyEngine
4
+ class Form
5
+ include ActiveModel::Validations
6
+ extend ActiveModel::Naming
7
+
8
+ def self.wraps(model)
9
+ @wraps = model
10
+ end
11
+
12
+ def self.model_name
13
+ ActiveModel::Name.new(wrapped_class)
14
+ end
15
+
16
+ def self.wrapped_class
17
+ @wraps.to_s.classify.constantize
18
+ end
19
+
20
+ def self.lookup_ancestors
21
+ [self]
22
+ end
23
+
24
+ cattr_accessor(:attributes_list) { [] }
25
+
26
+ def self.attributes *attributes
27
+ attributes.each do |attribute|
28
+ attributes_list << attribute
29
+ attr_accessor attribute
30
+ end
31
+ end
32
+
33
+ def self.new_from_model model
34
+ attributes = model.attributes.slice(*attributes_list.map(&:to_s))
35
+ new attributes
36
+ end
37
+
38
+ def initialize attributes = {}, model = self.class.wrapped_class.new
39
+ @model = model
40
+ attributes.each do |name, value|
41
+ send("#{name}=", value)
42
+ end
43
+ end
44
+
45
+ def attributes
46
+ attributes_list.reduce({}) do |hash, key|
47
+ hash[key] = send(key)
48
+ hash
49
+ end
50
+ end
51
+
52
+ def save
53
+ @model.update_attributes(attributes) if valid?
54
+ end
55
+
56
+ delegate :to_model, :to_key, :to_param, :to_partial_path, :persisted?, :new_record?, to: :@model
57
+ end
58
+ end
59
+
@@ -0,0 +1,69 @@
1
+ require "study_engine/config"
2
+
3
+ module StudyEngine
4
+ class Report < Struct.new(:assessments, :field, :direction)
5
+ def self.event_fields
6
+ StudyEngine.events.map.with_index { |_, index| :"event_#{index}" }
7
+ end
8
+
9
+ def self.group_class
10
+ Struct.new(:study_id, *event_fields) do
11
+ def each(&block)
12
+ Report.event_fields.map { |field| send(field) }.each(&block)
13
+ end
14
+
15
+ def max_count
16
+ Report.event_fields.map { |field| send(field).count }.max
17
+ end
18
+
19
+ def == other
20
+ values == other.values
21
+ end
22
+ end
23
+ end
24
+
25
+ def groups
26
+ sort build_groups
27
+ end
28
+
29
+ def build_groups
30
+ assessments.reduce({}) do |groups, assessment|
31
+ group = groups[assessment.study_id.to_s] || new_empty_group(assessment.study_id)
32
+
33
+ field = field_map[assessment.event]
34
+
35
+ group.send(field).push assessment
36
+ group.send(field).sort_by! { |a| a.completed_at ? -1 : 1 }
37
+
38
+ groups.merge(assessment.study_id.to_s => group)
39
+ end.values
40
+ end
41
+
42
+ def sort groups
43
+ groups.sort do |a, b|
44
+ a = a.send(field)
45
+ b = b.send(field)
46
+ ascending? ? a <=> b : b <=> a
47
+ end
48
+ end
49
+
50
+ def ascending?
51
+ direction == "ASC"
52
+ end
53
+
54
+ private
55
+
56
+ def field_map
57
+ StudyEngine.events.inject({}) do |hash, event|
58
+ index = StudyEngine.events.index(event)
59
+ hash.merge event => "event_#{index}"
60
+ end
61
+ end
62
+
63
+ def new_empty_group(study_id)
64
+ empty_groups = StudyEngine.events.map { |_| [] }
65
+ self.class.group_class.new(study_id, *empty_groups)
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,46 @@
1
+ require "cat_engine/bank"
2
+ require "cat_engine/engine"
3
+
4
+ module StudyEngine
5
+ class Result < ActiveRecord::Base
6
+ belongs_to :assessment, touch: true
7
+
8
+ has_many :answers
9
+
10
+ delegate :current_question, to: :bank
11
+
12
+ def bank
13
+ CatEngine::Bank.new(bank_id, history, assessment.full_history)
14
+ end
15
+
16
+ def name
17
+ bank_id.sub(".flat","")
18
+ end
19
+
20
+ def history
21
+ answers.pluck(:response_id).join(" ")
22
+ end
23
+
24
+ def save_answer answer
25
+ wrapper do # race condition could bypass validity check
26
+ if bank.valid_answer?(answer.response_id)
27
+ answers << answer
28
+ update_attributes bank.to_h
29
+ save!
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def wrapper
37
+ # UGH HACK HACK HACK why can't FlatEngine handle being wrapped in a lock?
38
+ if bank.send(:engine).is_a? CatEngine::Engine
39
+ with_lock { yield }
40
+ else
41
+ yield
42
+ end
43
+ end
44
+ end
45
+ end
46
+
@@ -0,0 +1,54 @@
1
+ require "study_engine/config"
2
+
3
+ module StudyEngine
4
+ class ScreenResults < Struct.new(:groups)
5
+ def each &block
6
+ block.call header
7
+ body block
8
+ end
9
+
10
+ private
11
+
12
+ def header
13
+ fields = ["Study ID"]
14
+ StudyEngine.events.each do |event|
15
+ fields += ["#{event} incomplete", "#{event} timestamp", "#{event} method", "#{event} coordinator", "#{event} note"]
16
+ fields += ["#{event} set"] if StudyEngine.groups[event].present?
17
+ end
18
+ fields.flatten
19
+ end
20
+
21
+ def body block
22
+ groups.each do |group|
23
+ group.max_count.times do |count|
24
+ col = [group.study_id.to_s]
25
+
26
+ col = group.each_with_index.inject(col) do |col, (assessments, index)|
27
+ col + assessment_segment(assessments[count], index)
28
+ end
29
+
30
+ block.call col
31
+ end
32
+ end
33
+ end
34
+
35
+ def assessment_segment assessment, index
36
+ assessment ? existing_assessment(assessment) : empty_assessment(index)
37
+ end
38
+
39
+ def existing_assessment assessment
40
+ summary = assessment.incomplete? && !assessment.by_mail? ? assessment.results_summary : ""
41
+ col = [summary, assessment.assessment_updated_at.to_s, assessment.method_applied, assessment.coordinator.try(:email), assessment.notes.to_s]
42
+ col << assessment.group_index_name if assessment.group_index
43
+ col
44
+ end
45
+
46
+ def empty_assessment index
47
+ col = ["", "", "", "", ""]
48
+ event = StudyEngine.events[index]
49
+ col << "" if StudyEngine.groups[event].present?
50
+ col
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,47 @@
1
+ require "equalizer"
2
+
3
+ module StudyEngine
4
+ class StudyID
5
+ def self.load slug
6
+ return unless slug
7
+ new slug
8
+ end
9
+
10
+ def self.dump study_id
11
+ study_id.to_s
12
+ end
13
+
14
+ include Equalizer.new(:study, :site, :id)
15
+
16
+ attr_accessor :study, :site, :id
17
+
18
+ def initialize slug=nil
19
+ @study, @site, @id = slug.split("-") if slug
20
+ end
21
+
22
+ def study= value
23
+ @study = value.try(:upcase)
24
+ end
25
+
26
+ def site= value
27
+ @site = value.try(:upcase)
28
+ end
29
+
30
+ def to_s
31
+ [study, site, id].join("-")
32
+ end
33
+
34
+ def <=> other
35
+ to_s <=> other.to_s
36
+ end
37
+
38
+ def present?
39
+ [@study, @site, @id].all?(&:present?)
40
+ end
41
+
42
+ def valid?
43
+ to_s =~ %r{\A[A-Z]{3}-[A-Z]{3}-[0-9]{4}\Z}
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,38 @@
1
+ module StudyEngine
2
+ class StudyID
3
+ class Bank < ActiveRecord::Base
4
+ self.table_name = "study_engine_study_ids"
5
+
6
+ def self.find_or_create_from_full_study_id study_id
7
+ study_id_study, study_id_site, study_id_id = study_id.split("-")
8
+ find_or_create_by study_id_study: study_id_study, study_id_site: study_id_site, study_id_id: study_id_id
9
+ end
10
+
11
+ def self.studies
12
+ pluck(:study_id_study).uniq.sort
13
+ end
14
+
15
+ def self.sites
16
+ pluck(:study_id_site).uniq.sort
17
+ end
18
+
19
+ def self.ids_grouped_by_study_and_site
20
+ all.reduce(Hash.new([])) do |hash, study_id|
21
+ hash[study_id.study_id_study + study_id.study_id_site] += [study_id.study_id_id]
22
+ hash
23
+ end
24
+ end
25
+
26
+ def self.studies_grouped_by_site
27
+ all.reduce(Hash.new(Set.new)) do |hash, study_id|
28
+ hash[study_id.study_id_site] += [study_id.study_id_study]
29
+ hash
30
+ end
31
+ end
32
+
33
+ validates :study_id_study, :study_id_site, format: /\A[A-Z]{3}\Z/
34
+ validates :study_id_id, format: /\A[0-9]{4}\Z/
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,52 @@
1
+ require "faraday"
2
+ require "csv"
3
+ require "study_engine/config"
4
+
5
+ module StudyEngine
6
+ class StudyID
7
+ class Sync
8
+ def self.call
9
+ new.call
10
+ end
11
+
12
+ def call
13
+ raise "Cannot sync with REDCap without setting StudyEngine.redcap_token" if StudyEngine.redcap_token.blank?
14
+ CSV.parse(response, headers: true) do |row|
15
+ process row unless row.empty?
16
+ end
17
+ StudyID::Bank.where(["updated_at < ?", 30.minutes.ago]).delete_all
18
+ end
19
+
20
+ private
21
+
22
+ def process row
23
+ study_id = StudyID::Bank.find_or_create_from_full_study_id(row["study_id"])
24
+ study_id.touch unless study_id.new_record?
25
+ end
26
+
27
+ def response
28
+ conn = Faraday.new(url: "https://metrcdata.org")
29
+ resp = conn.post "/redcap/api/",
30
+ token: StudyEngine.redcap_token,
31
+ content: "record",
32
+ format: "csv"
33
+ resp.body
34
+ end
35
+
36
+ prepend Module.new {
37
+ def response
38
+ retries = 0
39
+ super
40
+ rescue Faraday::TimeoutError
41
+ retries += 1
42
+ if retries < 5
43
+ retry
44
+ else
45
+ raise
46
+ end
47
+ end
48
+ }
49
+ end
50
+ end
51
+ end
52
+