surveyor 0.17.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,210 @@
1
+ 0.18.0
2
+
3
+ * finalizing redcap feature. parse dependencies. closes #93
4
+ * finished decomposing redcap branching logic rules
5
+ * working on redcap parser spec, question/answer reference lookup table
6
+ * skipping calcuated answers for now
7
+ * validations
8
+ * more progress on redcap parser
9
+ * working on redcap parser
10
+
11
+ 0.17.0
12
+
13
+ * cucumber tests now work
14
+ * correction to post install notes in rakefile
15
+ * fix to rakefile instructions
16
+ * un-dryed the gemfile to try to fix a gem location problem
17
+ * added test boot file for test app usage, modified the rake task to copy in this file as well
18
+ * removed unparser from specs, it was breakin a bunch of them. deferred investigation
19
+ * got specs running and passing by adding internal rails app
20
+ * added rvmrc to create default gemset and added bundler
21
+ * Plugin installation is no longer supported
22
+ * wrong information in readme
23
+ * Merge branch 'master' into issue87
24
+ * Add count operator example to kitchen sink survey
25
+ * Generalize count operator to handle < <= >= !=
26
+ * Parse/unparse surveys w/ count> DC operator
27
+ * Add custom validation to handle count operator
28
+ * Remove count> "operator."
29
+
30
+ 0.16.1
31
+
32
+ * fixed surveyor.sections translation line
33
+ * changed map resources order to access results survey success
34
+ * add translations for Sections title
35
+ * Add I18n to Sections title
36
+ * updated date on license
37
+ * updating results views and controller for new paths
38
+ * new results routes
39
+
40
+ 0.16.0
41
+
42
+ * minor fixes to unparsing
43
+ * refining unparser. added rake task to unparse survey. closes #79
44
+ * unparsing for groups, dependencies, validations
45
+ * starting work on unparser for basic survey, section and question.
46
+
47
+ 0.15.0
48
+
49
+ * prevent duplicate survey titles by appending incrementing numbers
50
+ * rake task to remove a survey. closes #64
51
+ * cleanup of old parsing strategy
52
+ * features and specs and new parser. closes #62
53
+ * first test driven work on parser
54
+ * moving parser and common specs so they run automatically. fixing some spec errors
55
+ * first shot a surveyor parser. some parts untested, but coded to determine style. references #62
56
+ * refactoring counters
57
+ * fixing failing specs. fixes acts\_as\_response issues
58
+
59
+ 0.14.5
60
+
61
+ * use modules to include model methods. re-closes #77
62
+ * rails init. destroy dependent models
63
+
64
+ 0.14.4
65
+
66
+ * explicitly require surveyor models and helper. update sweeper syntax. closes #77
67
+ * cleanup and requires
68
+ * fixing instructions for extending surveyor. closes #76
69
+
70
+ 0.14.3
71
+
72
+ * remove manual numbering until it works. refactoring to use common methods.
73
+
74
+ 0.14.2
75
+
76
+ * lowercase localization. feature instead of story in cucumber feature
77
+ * add results section
78
+ * add simple admin section for displaying survey result set
79
+ * Added manual numbering to labels as well
80
+
81
+ 0.14.1
82
+
83
+ * typo in repeaters - use survey\_section\_id instead of section\_id
84
+
85
+ 0.14.0
86
+
87
+ * view my survey specs fixed, fragment caching for surveyor#edit, localization. thanks bnadav
88
+
89
+ 0.13.0
90
+
91
+ * simpler customization of surveyor.
92
+ * spec plugins task
93
+ * Feature instead of Story for cucumber. http://wiki.github.com/aslakhellesoy/cucumber/upgrading
94
+
95
+ 0.12.0
96
+
97
+ * fix parser error in ruby 1.9, where instance_variables are symbols. closes #61
98
+ * added fastercsv as dependency. closes #59
99
+ * typo fix and test
100
+ * fixed broken spec for survey urls, made pending surveyor_controller specs pass
101
+ * Added explicit dependencycondition and validationcondition to DSL
102
+ * have authentication work with authlogic
103
+ * added "correct_answer" to parser, so you can specify one correct answer per question
104
+
105
+ 0.11.0
106
+
107
+ * basic csv export. closes #21
108
+ * add unique indicies. closes #45
109
+ * add one_integer renderer. closes #51
110
+ * constrain surveys to have unique access_codes. closes #45. closes #42
111
+ * covering the extremely unlikely case that response_sets may have a non-unique access_code. closes #46. thanks jakewendt.
112
+ * current user id not needed in the view, set in SurveyorController. closes #48. thanks jakewendt
113
+
114
+ 0.10.0
115
+
116
+ * surveyor config['extend'] is now an array. custom modules (e.g. SurveyExtensions are now included from within surveyor models, allowing
117
+ the customizations to work on every request in development. closes #39. thanks to mgurley and jakewendt for the suggestions.
118
+ * remove comment from surveyor_includes
119
+ * css tweak
120
+ * automatically add backslashes and eliminate multiple backslashes in relative root for routes
121
+ * readme spelling and line breaks
122
+ * fixing a failing spec with factory instead of mock parent model
123
+ * upgrading cucumber to 0.6
124
+
125
+ 0.9.11
126
+
127
+ * adding rails init.rb to make gem loading work. thanks mike gurley. closes #52.
128
+ * Repeater changed to only have +1, not +3 as previous
129
+ * added locking and transaction to surveyor update action. Prevents bug that caused duplicated answers
130
+ * some light re-factoring and code readability changes
131
+ * some code formatting changes
132
+ * added require statement to specs so the factory_girl test dependency was more clear
133
+ * spiced up the readme... may have some typos
134
+ * readme update
135
+
136
+ 0.9.10
137
+
138
+ * styles, adding labels for dates, correcting labels for radio buttons
139
+
140
+ 0.9.9
141
+
142
+ * count label and image questions complete when mandatory. closes #38
143
+ * validate by other responses. closes #35
144
+
145
+ 0.9.8
146
+
147
+ * @current\_user.id if @current\_user isn't nil. Closes #37
148
+
149
+ 0.9.7
150
+
151
+ * fixing typos
152
+ * remove surveyor controller from load\_once\_paths. fixes issue with dependencies and unloading in development. closes #36
153
+
154
+ 0.9.6
155
+
156
+ * response set reports progress and mandatory questions completeness. closes #33
157
+ * adding correctness to response sets
158
+ * adding correctness to responses
159
+
160
+ 0.9.5
161
+
162
+ * allow append for survey parser. closes #32
163
+
164
+ 0.9.4
165
+
166
+ * making tinycode compatible with ruby 1.8.6
167
+
168
+ 0.9.3
169
+
170
+ * fix for survey parser require
171
+
172
+ 0.9.2
173
+
174
+ * fixing specs for namespacing and move of tinycode
175
+ * namespacing SurveyParser models to avoid conflict with model extensions
176
+
177
+ 0.9.1
178
+
179
+ * fix for tinycode, more descriptive missing method
180
+
181
+ 0.9.0
182
+
183
+ * validations in dsl and surveyor models
184
+ * preserve underscores in reference identifiers
185
+ * dsl specs, refactoring into base class
186
+ * adding display order to surveys
187
+ * moving columnizer and tiny column functionality to surveyor module
188
+ * columnizer (and tiny code) refactoring, columnizer spec extracted from answer spec
189
+ * cleanup of scopes with joins
190
+ * refactoring dependency
191
+
192
+ 0.8.0
193
+
194
+ * question group dependencies
195
+ * expanded examples in kitchen sink survey
196
+ * specs
197
+
198
+ 0.7.1
199
+
200
+ * custom index page
201
+ * custom classes and renderers
202
+ * fixing typo in kitchen sink survey
203
+
204
+ 0.7.0
205
+
206
+ * new kitchen sink survey with better documentation of DSL
207
+ * migration misspelling
208
+ * fixing ordering, dependency conditions evaluation, and changing named scopes for now
209
+ * DRYing up surveyor DSL models
210
+ * working on adding dependencies for question groups
data/README.md CHANGED
@@ -1,18 +1,18 @@
1
1
  # Surveys On Rails
2
2
 
3
- Surveyor is a rails (gem) plugin, that brings surveys to your rails app. Before Rails 2.3, it was implemented as a Rails Engine. Surveys are written in a DSL (Domain Specific Language), with examples available in the "kitchen sink" survey.
3
+ Surveyor is a ruby gem and developer tool that brings surveys into Rails applications. Surveys are written in the Surveyor DSL (Domain Specific Language). Before Rails 2.3, it was implemented as a Rails Engine. It also existed previously as a plugin. Today it is a gem only.
4
4
 
5
5
  ## Why you might want to use Surveyor
6
6
 
7
- If you have to have a part of your Rails app that asks users questions as part of a survey, quiz, or questionnaire then you should consider using Surveyor. This plugin was designed out of the need to deliver clinical research surveys to large populations of people but it can be used for any type of survey. It has an easy to use DSL to define the questions, response dependencies (if user answers 'A' to question 1 then show question 1a, etc...), and structure (different sections of longer questionnaires).
7
+ If your Rails app needs to asks users questions as part of a survey, quiz, or questionnaire then you should consider using Surveyor. This gem was designed to deliver clinical research surveys to large populations, but it can be used for any type of survey.
8
8
 
9
- To build your questionnaire you define it using a custom DSL. Having a DSL instead of a GUI makes it significantly easier to import long surveys (no more endless clicking and typing into tiny text boxes). It also means that you can let your customer write out the survey, edit, re-edit, tweak, throw out and start over, any number of surveys without having to change a single line of code in your app.
9
+ The Surveyor DSL defines questions, answers, question groups, survey sections, dependencies (e.g. if response to question 4 is A, then show question 5), and validations. Answers are the options available for each question - user input is called "responses" and are grouped into "response sets". A DSL makes it significantly easier to import long surveys (no more click/copy/paste). It also enables non-programmers to write out, edit, re-edit... any number of surveys.
10
10
 
11
11
  ## DSL example
12
12
 
13
- Our DSL supports a wide range of question types (too many to list here) and varying dependency logic. Here are the first few questions of the "kitchen_sink" survey which should give you and idea of how the DSL works. The full example with all the types of questions is in the plugin and available if you run the installation instructions below.
13
+ The Surveyor DSL supports a wide range of question types (too many to list here) and complex dependency logic. Here are the first few questions of the "kitchen sink" survey which should give you and idea of how it works. The full example with all the types of questions available if you follow the installation instructions below.
14
14
 
15
- survey "&#8220;Kitchen Sink&#8221; survey" do
15
+ survey "Kitchen Sink survey" do
16
16
 
17
17
  section "Basic questions" do
18
18
  # A label is a question that accepts no answers
@@ -46,268 +46,60 @@ Our DSL supports a wide range of question types (too many to list here) and vary
46
46
  condition_C :q_2, "==", :a_3
47
47
  condition_D :q_2, "==", :a_4
48
48
 
49
- # ... other question, sections and such. See kitchen_sink_survey.rb for more.
49
+ # ... other question, sections and such. See surveys/kitchen_sink_survey.rb for more.
50
50
  end
51
51
 
52
52
  end
53
53
 
54
- The survey above shows a couple simple question types. The first one is a "pick one" type with the "other" custom entry. The second question is a "pick any" type with the option to "omit". It also has a dependency where you can ask a follow up question based on how the user answered the previous question. Notice the way the dependency is defined as a string. This implementation supports any number of complex dependency rules so not just "A or B or C or D" but "A and (B or C) and D" or "!A or ((B and !C) or D)". The conditions are the letters used they are evaluated separately using the operators defined for "==","<>", ">=","<", (the usual stuff) the plugged in to the dependency rule and evaluated. See the example survey for more details.
54
+ The first question is "pick one" (radio buttons) with "other". The second question is "pick any" (checkboxes) with the option to "omit". It also features a dependency with a follow up question. Notice the dependency rule is defined as a string. We support complex dependency such as "A and (B or C) and D" or "A or ((B and C) or D)". The conditions are evaluated separately using the operators "==","!=","<>", ">=","<" the substituted by letter into to the dependency rule and evaluated.
55
55
 
56
56
  # Installation
57
57
 
58
- As a gem:
59
-
60
- # in environment.rb
61
- config.gem "surveyor", :source => 'http://gemcutter.org'
62
-
63
- rake gems:install
58
+ 1. Add it to your bundler Gemfile:
64
59
 
65
- Or as a gem (with bundler):
60
+ gem "surveyor"
66
61
 
67
- # in Gemfile
68
- gem "surveyor"
62
+ `bundle install`
69
63
 
70
- bundle install
71
-
72
- Generate assets, run migrations:
64
+ 2. Generate assets, run migrations:
73
65
 
74
- script/generate surveyor
75
- rake db:migrate
66
+ `script/generate surveyor`
67
+ `rake db:migrate`
76
68
 
77
- Try out the "kitchen sink" survey:
69
+ 3. Try out the "kitchen sink" survey. The rake task above generates surveys from our custom survey DSL (a good format for end users and stakeholders).
78
70
 
79
- rake surveyor FILE=surveys/kitchen_sink_survey.rb
71
+ `rake surveyor FILE=surveys/kitchen_sink_survey.rb`
80
72
 
81
- The rake tasks above generate surveys in our custom survey DSL (which is a great format for end users and stakeholders to use).
82
- After you have run them start up your app and go to:
73
+ 4. Start up your app and visit:
83
74
 
84
- http://localhost:3000/surveys
75
+ http://localhost:3000/surveys
85
76
 
86
- Try taking the survey and compare it to the contents of the DSL file kitchen\_sink\_survey.rb. See how each type of DSL question maps to the resulting rendered view of the question.
77
+ Try taking the survey and compare it to the contents of the DSL file kitchen\_sink\_survey.rb. See how the DSL maps to what you see.
87
78
 
88
79
  There are two other useful rake tasks for removing (only surveys without responses) and un-parsing (from db to DSL file) surveys:
89
80
 
90
- rake surveyor:remove
91
- rake surveyor:unparse
81
+ `rake surveyor:remove`
82
+ `rake surveyor:unparse`
92
83
 
93
84
  # Customizing surveyor
94
85
 
95
86
  Surveyor's controller, models, and views may be customized via classes in your app/models, app/helpers and app/controllers directories. To generate a sample custom controller and layout, run:
96
87
 
97
- script/generate extend_surveyor
98
-
99
- and check out surveys/EXTENDING\_SURVEYOR
100
-
101
- # Dependencices
102
-
103
- Surveyor depends on Ruby (1.8.7 - 1.9.1), Rails 2.3 and the SASS style sheet language, part of HAML (http://haml.hamptoncatlin.com/download). It also depends on fastercsv for csv exports. For running the test suite you will need rspec and have the rspec plugin installed in your application.
104
-
105
- # Test Suite and Development
106
-
107
- To work on the plugin code (for enhancements, and bug fixes, etc...) fork this github project. Then clone the project under the vendor/plugins directory in a Rails app used only for development:
108
-
109
- # Changes
110
-
111
- 0.17.0
112
-
113
- * cucumber tests now work
114
- * correction to post install notes in rakefile
115
- * fix to rakefile instructions
116
- * un-dryed the gemfile to try to fix a gem location problem
117
- * added test boot file for test app usage, modified the rake task to copy in this file as well
118
- * removed unparser from specs, it was breakin a bunch of them. deferred investigation
119
- * got specs running and passing by adding internal rails app
120
- * added rvmrc to create default gemset and added bundler
121
- * Plugin installation is no longer supported
122
- * wrong information in readme
123
- * Merge branch 'master' into issue87
124
- * Add count operator example to kitchen sink survey
125
- * Generalize count operator to handle < <= >= !=
126
- * Parse/unparse surveys w/ count> DC operator
127
- * Add custom validation to handle count operator
128
- * Remove count> "operator."
129
-
130
- 0.16.1
131
-
132
- * fixed surveyor.sections translation line
133
- * changed map resources order to access results survey success
134
- * add translations for Sections title
135
- * Add I18n to Sections title
136
- * updated date on license
137
- * updating results views and controller for new paths
138
- * new results routes
139
-
140
- 0.16.0
141
-
142
- * minor fixes to unparsing
143
- * refining unparser. added rake task to unparse survey. closes #79
144
- * unparsing for groups, dependencies, validations
145
- * starting work on unparser for basic survey, section and question.
146
-
147
- 0.15.0
148
-
149
- * prevent duplicate survey titles by appending incrementing numbers
150
- * rake task to remove a survey. closes #64
151
- * cleanup of old parsing strategy
152
- * features and specs and new parser. closes #62
153
- * first test driven work on parser
154
- * moving parser and common specs so they run automatically. fixing some spec errors
155
- * first shot a surveyor parser. some parts untested, but coded to determine style. references #62
156
- * refactoring counters
157
- * fixing failing specs. fixes acts\_as\_response issues
158
-
159
- 0.14.5
160
-
161
- * use modules to include model methods. re-closes #77
162
- * rails init. destroy dependent models
163
-
164
- 0.14.4
165
-
166
- * explicitly require surveyor models and helper. update sweeper syntax. closes #77
167
- * cleanup and requires
168
- * fixing instructions for extending surveyor. closes #76
169
-
170
- 0.14.3
171
-
172
- * remove manual numbering until it works. refactoring to use common methods.
173
-
174
- 0.14.2
175
-
176
- * lowercase localization. feature instead of story in cucumber feature
177
- * add results section
178
- * add simple admin section for displaying survey result set
179
- * Added manual numbering to labels as well
180
-
181
- 0.14.1
182
-
183
- * typo in repeaters - use survey\_section\_id instead of section\_id
184
-
185
- 0.14.0
186
-
187
- * view my survey specs fixed, fragment caching for surveyor#edit, localization. thanks bnadav
188
-
189
- 0.13.0
190
-
191
- * simpler customization of surveyor.
192
- * spec plugins task
193
- * Feature instead of Story for cucumber. http://wiki.github.com/aslakhellesoy/cucumber/upgrading
194
-
195
- 0.12.0
196
-
197
- * fix parser error in ruby 1.9, where instance_variables are symbols. closes #61
198
- * added fastercsv as dependency. closes #59
199
- * typo fix and test
200
- * fixed broken spec for survey urls, made pending surveyor_controller specs pass
201
- * Added explicit dependencycondition and validationcondition to DSL
202
- * have authentication work with authlogic
203
- * added "correct_answer" to parser, so you can specify one correct answer per question
204
-
205
- 0.11.0
206
-
207
- * basic csv export. closes #21
208
- * add unique indicies. closes #45
209
- * add one_integer renderer. closes #51
210
- * constrain surveys to have unique access_codes. closes #45. closes #42
211
- * covering the extremely unlikely case that response_sets may have a non-unique access_code. closes #46. thanks jakewendt.
212
- * current user id not needed in the view, set in SurveyorController. closes #48. thanks jakewendt
213
-
214
- 0.10.0
215
-
216
- * surveyor config['extend'] is now an array. custom modules (e.g. SurveyExtensions are now included from within surveyor models, allowing
217
- the customizations to work on every request in development. closes #39. thanks to mgurley and jakewendt for the suggestions.
218
- * remove comment from surveyor_includes
219
- * css tweak
220
- * automatically add backslashes and eliminate multiple backslashes in relative root for routes
221
- * readme spelling and line breaks
222
- * fixing a failing spec with factory instead of mock parent model
223
- * upgrading cucumber to 0.6
224
-
225
- 0.9.11
226
-
227
- * adding rails init.rb to make gem loading work. thanks mike gurley. closes #52.
228
- * Repeater changed to only have +1, not +3 as previous
229
- * added locking and transaction to surveyor update action. Prevents bug that caused duplicated answers
230
- * some light re-factoring and code readability changes
231
- * some code formatting changes
232
- * added require statement to specs so the factory_girl test dependency was more clear
233
- * spiced up the readme... may have some typos
234
- * readme update
235
-
236
- 0.9.10
237
-
238
- * styles, adding labels for dates, correcting labels for radio buttons
239
-
240
- 0.9.9
241
-
242
- * count label and image questions complete when mandatory. closes #38
243
- * validate by other responses. closes #35
244
-
245
- 0.9.8
246
-
247
- * @current\_user.id if @current\_user isn't nil. Closes #37
248
-
249
- 0.9.7
250
-
251
- * fixing typos
252
- * remove surveyor controller from load\_once\_paths. fixes issue with dependencies and unloading in development. closes #36
253
-
254
- 0.9.6
255
-
256
- * response set reports progress and mandatory questions completeness. closes #33
257
- * adding correctness to response sets
258
- * adding correctness to responses
259
-
260
- 0.9.5
261
-
262
- * allow append for survey parser. closes #32
263
-
264
- 0.9.4
265
-
266
- * making tinycode compatible with ruby 1.8.6
267
-
268
- 0.9.3
269
-
270
- * fix for survey parser require
271
-
272
- 0.9.2
273
-
274
- * fixing specs for namespacing and move of tinycode
275
- * namespacing SurveyParser models to avoid conflict with model extensions
276
-
277
- 0.9.1
278
-
279
- * fix for tinycode, more descriptive missing method
280
-
281
- 0.9.0
282
-
283
- * validations in dsl and surveyor models
284
- * preserve underscores in reference identifiers
285
- * dsl specs, refactoring into base class
286
- * adding display order to surveys
287
- * moving columnizer and tiny column functionality to surveyor module
288
- * columnizer (and tiny code) refactoring, columnizer spec extracted from answer spec
289
- * cleanup of scopes with joins
290
- * refactoring dependency
88
+ `script/generate extend_surveyor`
291
89
 
292
- 0.8.0
90
+ and read surveys/EXTENDING\_SURVEYOR
293
91
 
294
- * question group dependencies
295
- * expanded examples in kitchen sink survey
296
- * specs
92
+ # Requirements
297
93
 
298
- 0.7.1
94
+ Surveyor depends on Ruby (1.8.7 - 1.9.1), Rails 2.3 and HAML/SASS http://haml.hamptoncatlin.com/. It also depends on fastercsv for csv exports.
299
95
 
300
- * custom index page
301
- * custom classes and renderers
302
- * fixing typo in kitchen sink survey
96
+ # Contributing, testing
303
97
 
304
- 0.7.0
98
+ To work on the code fork this github project. Run:
305
99
 
306
- * new kitchen sink survey with better documentation of DSL
307
- * migration misspelling
308
- * fixing ordering, dependency conditions evaluation, and changing named scopes for now
309
- * DRYing up surveyor DSL models
310
- * working on adding dependencies for question groups
100
+ `bundler install`
101
+ `rake testbed`
311
102
 
103
+ and start writing tests!
312
104
 
313
- Copyright (c) 2008-2009 Brian Chamberlain and Mark Yoon, released under the MIT license
105
+ Copyright (c) 2008-2010 Brian Chamberlain and Mark Yoon, released under the MIT license
data/Rakefile CHANGED
@@ -30,12 +30,13 @@ rescue LoadError
30
30
  end
31
31
  end
32
32
 
33
+ desc "Set up a rails app for testing in the spec dir"
34
+ task :testbed => [:"testbed:build_app", :"testbed:copy_files", :"testbed:install_surveyor"]
33
35
 
34
36
  namespace "testbed" do
35
-
36
37
  RAPPNAME = "test_app" #This is also hardcoded in the spec/spec_helper.rb and gitignore file. Change it there too...
37
-
38
- desc "Install rails base app in spec dir"
38
+
39
+ "Generate rails app in spec dir"
39
40
  task :build_app do
40
41
  directory "spec"
41
42
  chdir("spec") do
@@ -60,7 +61,7 @@ namespace "testbed" do
60
61
  end
61
62
  end
62
63
 
63
- desc "Install surveyor in rails base app, runs migrations, preps for testing"
64
+ desc "Install surveyor in test app, run migrations, prep test db"
64
65
  task :install_surveyor do
65
66
  sh "gem install surveyor"
66
67
  chdir("spec/#{RAPPNAME}") do
@@ -73,17 +74,11 @@ namespace "testbed" do
73
74
  puts "NOTE: We installed the surveyor gem using 'gem install surveyor' to fix a problem where RVM (or bundler or both) don't let Rails see generators in a gem. ('script/generate surveyor' for example). To remove the gem run `gem uninstall surveyor` to remove the gem version of surveyor leaving the dev version" # Getting around a bug/problem in bundler. see: http://bit.ly/9NZOEz
74
75
  end
75
76
 
76
- desc "Remove rails base app in spec dir"
77
- task :remove_app do
77
+ desc "Remove rails test app from spec dir"
78
+ task :remove do
78
79
  puts "Removing the test_app in the spec folder"
79
80
  sh "rm -rf spec/#{RAPPNAME}"
80
81
  end
81
-
82
- desc "Setup for the test app (create)"
83
- task :setup => [:build_app, :copy_files, :install_surveyor]
84
- desc "Teardown for the test app (remove)"
85
- task :teardown => [:remove_app]
86
-
87
82
  end # namespace
88
83
 
89
84
  require 'spec/rake/spectask'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.0
1
+ 0.18.0
@@ -0,0 +1,19 @@
1
+ Feature: Survey creation
2
+ As a
3
+ I want to write out the survey in the DSL
4
+ So that I can give it to survey participants
5
+
6
+ Scenario: Basic questions
7
+ Given I parse redcap file "REDCapDemoDatabase_DataDictionary.csv"
8
+ Then there should be 1 survey with:
9
+ ||
10
+ And there should be 143 questions with:
11
+ ||
12
+ And there should be 161 answers with:
13
+ ||
14
+ And there should be 3 resolved dependency_conditions with:
15
+ ||
16
+ And there should be 2 dependencies with:
17
+ | rule |
18
+ | A |
19
+ | A and B |
@@ -2,6 +2,10 @@ Given /^I parse$/ do |string|
2
2
  Surveyor::Parser.parse(string)
3
3
  end
4
4
 
5
+ Given /^I parse redcap file "([^"]*)"$/ do |name|
6
+ Surveyor::RedcapParser.parse File.read(File.join(RAILS_ROOT, '..', '..', 'features', 'support', name)), name
7
+ end
8
+
5
9
  Then /^there should be (\d+) survey(?:s?) with:$/ do |x, table|
6
10
  Survey.count.should == x.to_i
7
11
  table.hashes.each do |hash|
@@ -33,7 +37,7 @@ Then /^there should be (\d+) answer(?:s?) with:$/ do |x, table|
33
37
  end
34
38
  end
35
39
 
36
- Then /^there should be (\d+) dependency with:$/ do |x, table|
40
+ Then /^there should be (\d+) dependenc(?:y|ies) with:$/ do |x, table|
37
41
  Dependency.count.should == x.to_i
38
42
  table.hashes.each do |hash|
39
43
  Dependency.find(:first, :conditions => hash).should_not be_nil
@@ -0,0 +1,127 @@
1
+ "Variable / Field Name","Form Name","Field Units","Section Header","Field Type","Field Label","Choices OR Calculations","Field Note","Text Validation Type","Text Validation Min","Text Validation Max",Identifier?,"Branching Logic (Show field only if...)","Required Field?"
2
+ study_id,demographics,,,text,"Study ID",,,,,,,,
3
+ date_enrolled,demographics,,"Demographic Characteristics",text,"Date subject signed consent",,YYYY-MM-DD,date,,,,,
4
+ first_name,demographics,,,text,"First Name",,,,,,y,,
5
+ last_name,demographics,,,text,"Last Name",,,,,,y,,
6
+ address,demographics,,"Contact Information",notes,"Street, City, State, ZIP",,,,,,y,,
7
+ telephone_1,demographics,,,text,"Phone number",,"Include Area Code",phone,,,y,,
8
+ telephone_2,demographics,,,text,"Second phone number",,"Include Area Code",phone,,,y,,
9
+ email,demographics,,,text,E-mail,,,email,,,y,,
10
+ sex,demographics,,,dropdown,Gender,"0, Female | 1, Male",,,,,,,y
11
+ given_birth,demographics,,,dropdown,"Has the subject given birth before?","0, No | 1, Yes",,,,,,"[sex] = ""0""",
12
+ num_children,demographics,,,text,"How many times has the subject given birth?",,,integer,0,,,"[sex] = ""0"" and [given_birth] = ""1""",
13
+ ethnicity,demographics,,,radio,Ethnicity,"0, Hispanic or Latino | 1, NOT Hispanic or Latino | 2, Unknown / Not Reported",,,,,,,
14
+ race,demographics,,,radio,Race,"0, American Indian/Alaska Native | 1, Asian | 2, Native Hawaiian or Other Pacific Islander | 3, Black or African American | 4, White | 5, More Than One Race | 6, Unknown / Not Reported",,,,,,,
15
+ dob,demographics,,,text,"Date of birth",,,date,,,y,,
16
+ age,demographics,,,calc,"Age (years)","round(datediff([dob],'today','y'),1)",,,,,,,
17
+ height,demographics,cm,,text,"Height (cm)",,,number,130,215,,,
18
+ weight,demographics,kilograms,,text,"Weight (kilograms)",,,integer,35,200,,,
19
+ bmi,demographics,kilograms,,calc,BMI,"round(([weight]*10000)/(([height])^(2)),1)",,,,,,,
20
+ patient_document,demographics,,,file,"Patient document",,,,,,,,
21
+ meds,demographics,,,checkbox,"Is patient taking any of the following medications? (check all that apply)","1, Lexapro | 2, Celexa | 3, Prozac | 4, Paxil | 5, Zoloft",,,,,,,
22
+ diabetes,demographics,,,dropdown,"Patient has a diagnosis of diabetes mellitus?","0, No | 1, Yes",,,,,,,
23
+ diabetes_type,demographics,,,dropdown,"Type of Diabetes Mellitus","0, Type 1 insulin-dependent | 1, Type 2 insulin-dependent | 2, Type 2 non insulin-dependent",,,,,,,
24
+ dialysis_initiation,demographics,,"Dialysis Information",text,"Date of first outpatient dialysis treatment",,,date,,,,,
25
+ access_type,demographics,,,dropdown,"Type of vascular access","0, Graft | 1, Fistula | 2, Catheter with maturing graft | 3, Catheter with maturing fistula",,,,,,,
26
+ access_location,demographics,,,dropdown,"Location of currently used vascular access","0, Forearm | 1, Upper arm | 2, Internal jugular vein | 3, Subclavian vein | 4, Other",,,,,,,
27
+ dialysis_unit_name,demographics,,,text,"Name of dialysis unit",,,,,,,,
28
+ dialysis_unit_phone,demographics,,,text,"Phone number",,"Include Area Code",phone,,,,,
29
+ dialysis_schedule_days,demographics,,,radio,"Days of the week patient is dialyzed","0, Monday-Wednesday-Friday | 1, Tuesday-Thursday-Saturday | 2, Other",,,,,,,
30
+ dialysis_schedule_time,demographics,,,dropdown,"Shift patient is dialyzed","0, First shift | 1, Second shift | 2, Third shift | 3, Fourth shift",,,,,,,
31
+ etiology_esrd,demographics,,,dropdown,"Etiology of ESRD","0, Diabetes | 1, Hypertension | 2, Glomerulonephritis | 3, Polycystic Kidney Disease | 4, Interstitial Nephritis | 5, Hereditary Nephritis | 6, Other",,,,,,,
32
+ subject_comments,demographics,,"General Comments",notes,Comments,,,,,,,,
33
+ date_visit_b,baseline_data,,"Baseline Measurements",text,"Date of baseline visit",,,date,,,,,
34
+ date_blood_b,baseline_data,,,text,"Date blood was drawn",,,date,,,,,
35
+ alb_b,baseline_data,g/dL,,text,"Serum Albumin (g/dL)",,,integer,3,5,,,
36
+ prealb_b,baseline_data,mg/dL,,text,"Serum Prealbumin (mg/dL)",,,number,10,40,,,
37
+ creat_b,baseline_data,mg/dL,,text,"Creatinine (mg/dL)",,,number,0.5,20,,,
38
+ npcr_b,baseline_data,g/kg/d,,text,"Normalized Protein Catabolic Rate (g/kg/d)",,,number,0.5,2,,,
39
+ chol_b,baseline_data,mg/dL,,text,"Cholesterol (mg/dL)",,,number,100,300,,,
40
+ transferrin_b,baseline_data,mg/dL,,text,"Transferrin (mg/dL)",,,number,100,300,,,
41
+ kt_v_b,baseline_data,,,text,Kt/V,,,number,0.9,3,,,
42
+ drywt_b,baseline_data,kilograms,,text,"Dry weight (kilograms)",,,number,35,200,,,
43
+ plasma1_b,baseline_data,,,dropdown,"Collected Plasma 1?","0, No | 1, Yes",,,,,,,
44
+ plasma2_b,baseline_data,,,dropdown,"Collected Plasma 2?","0, No | 1, Yes",,,,,,,
45
+ plasma3_b,baseline_data,,,dropdown,"Collected Plasma 3?","0, No | 1, Yes",,,,,,,
46
+ serum1_b,baseline_data,,,dropdown,"Collected Serum 1?","0, No | 1, Yes",,,,,,,
47
+ serum2_b,baseline_data,,,dropdown,"Collected Serum 2?","0, No | 1, Yes",,,,,,,
48
+ serum3_b,baseline_data,,,dropdown,"Collected Serum 3?","0, No | 1, Yes",,,,,,,
49
+ sga_b,baseline_data,,,text,"Subject Global Assessment (score = 1-7)",,,number,0.9,7.1,,,
50
+ date_supplement_dispensed,baseline_data,,,text,"Date patient begins supplement",,,date,,,,,
51
+ date_visit_1,month_1_data,,"Month 1",text,"Date of Month 1 visit",,,date,,,,,
52
+ alb_1,month_1_data,g/dL,,text,"Serum Albumin (g/dL)",,,number,3,5,,,
53
+ prealb_1,month_1_data,mg/dL,,text,"Serum Prealbumin (mg/dL)",,,number,10,40,,,
54
+ creat_1,month_1_data,mg/dL,,text,"Creatinine (mg/dL)",,,number,0.5,20,,,
55
+ npcr_1,month_1_data,g/kg/d,,text,"Normalized Protein Catabolic Rate (g/kg/d)",,,number,0.5,2,,,
56
+ chol_1,month_1_data,mg/dL,,text,"Cholesterol (mg/dL)",,,number,100,300,,,
57
+ transferrin_1,month_1_data,mg/dL,,text,"Transferrin (mg/dL)",,,number,100,300,,,
58
+ kt_v_1,month_1_data,,,text,Kt/V,,,number,0.9,3,,,
59
+ drywt_1,month_1_data,kilograms,,text,"Dry weight (kilograms)",,,number,35,200,,,
60
+ no_show_1,month_1_data,,,text,"Number of treatments missed",,,number,0,7,,,
61
+ compliance_1,month_1_data,,,dropdown,"How compliant was the patient in drinking the supplement?","0, 100 percent | 1, 99-75 percent | 2, 74-50 percent | 3, 49-25 percent | 4, 0-24 percent",,,,,,,
62
+ hospit_1,month_1_data,,"Hospitalization Data",dropdown,"Was patient hospitalized since last visit?","0, No | 1, Yes",,,,,,,
63
+ cause_hosp_1,month_1_data,,,dropdown,"What was the cause of hospitalization?","1, Vascular access related events | 2, CVD events | 3, Other",,,,,,,
64
+ admission_date_1,month_1_data,,,text,"Date of hospital admission",,,date,,,,,
65
+ discharge_date_1,month_1_data,,,text,"Date of hospital discharge",,,date,,,,,
66
+ discharge_summary_1,month_1_data,,,dropdown,"Discharge summary in patients binder?","0, No | 1, Yes",,,,,,,
67
+ death_1,month_1_data,,"Mortality Data",dropdown,"Has patient died since last visit?","0, No | 1, Yes",,,,,,,
68
+ date_death_1,month_1_data,,,text,"Date of death",,,date,,,,,
69
+ cause_death_1,month_1_data,,,dropdown,"What was the cause of death?","1, All-cause | 2, Cardiovascular",,,,,,,
70
+ date_visit_2,month_2_data,,"Month 2",text,"Date of Month 2 visit",,,date,,,,,
71
+ alb_2,month_2_data,g/dL,,text,"Serum Albumin (g/dL)",,,number,3,5,,,
72
+ prealb_2,month_2_data,mg/dL,,text,"Serum Prealbumin (mg/dL)",,,number,10,40,,,
73
+ creat_2,month_2_data,mg/dL,,text,"Creatinine (mg/dL)",,,number,0.5,20,,,
74
+ npcr_2,month_2_data,g/kg/d,,text,"Normalized Protein Catabolic Rate (g/kg/d)",,,number,0.5,2,,,
75
+ chol_2,month_2_data,mg/dL,,text,"Cholesterol (mg/dL)",,,number,100,300,,,
76
+ transferrin_2,month_2_data,mg/dL,,text,"Transferrin (mg/dL)",,,number,100,300,,,
77
+ kt_v_2,month_2_data,,,text,Kt/V,,,number,0.9,3,,,
78
+ drywt_2,month_2_data,kilograms,,text,"Dry weight (kilograms)",,,number,35,200,,,
79
+ no_show_2,month_2_data,,,text,"Number of treatments missed",,,number,0,7,,,
80
+ compliance_2,month_2_data,,,dropdown,"How compliant was the patient in drinking the supplement?","0, 100 percent | 1, 99-75 percent | 2, 74-50 percent | 3, 49-25 percent | 4, 0-24 percent",,,,,,,
81
+ hospit_2,month_2_data,,"Hospitalization Data",dropdown,"Was patient hospitalized since last visit?","0, No | 1, Yes",,,,,,,
82
+ cause_hosp_2,month_2_data,,,dropdown,"What was the cause of hospitalization?","1, Vascular access related events | 2, CVD events | 3, Other",,,,,,,
83
+ admission_date_2,month_2_data,,,text,"Date of hospital admission",,,date,,,,,
84
+ discharge_date_2,month_2_data,,,text,"Date of hospital discharge",,,date,,,,,
85
+ discharge_summary_2,month_2_data,,,dropdown,"Discharge summary in patients binder?","0, No | 1, Yes",,,,,,,
86
+ death_2,month_2_data,,"Mortality Data",dropdown,"Has patient died since last visit?","0, No | 1, Yes",,,,,,,
87
+ date_death_2,month_2_data,,,text,"Date of death",,,date,,,,,
88
+ cause_death_2,month_2_data,,,dropdown,"What was the cause of death?","1, All-cause | 2, Cardiovascular",,,,,,,
89
+ date_visit_3,month_3_data,,"Month 3",text,"Date of Month 3 visit",,,date,,,,,
90
+ date_blood_3,month_3_data,,,text,"Date blood was drawn",,,date,,,,,
91
+ alb_3,month_3_data,g/dL,,text,"Serum Albumin (g/dL)",,,number,3,5,,,
92
+ prealb_3,month_3_data,mg/dL,,text,"Serum Prealbumin (mg/dL)",,,number,10,40,,,
93
+ creat_3,month_3_data,mg/dL,,text,"Creatinine (mg/dL)",,,number,0.5,20,,,
94
+ npcr_3,month_3_data,g/kg/d,,text,"Normalized Protein Catabolic Rate (g/kg/d)",,,number,0.5,2,,,
95
+ chol_3,month_3_data,mg/dL,,text,"Cholesterol (mg/dL)",,,number,100,300,,,
96
+ transferrin_3,month_3_data,mg/dL,,text,"Transferrin (mg/dL)",,,number,100,300,,,
97
+ kt_v_3,month_3_data,,,text,Kt/V,,,number,0.9,3,,,
98
+ drywt_3,month_3_data,kilograms,,text,"Dry weight (kilograms)",,,number,35,200,,,
99
+ plasma1_3,month_3_data,,,dropdown,"Collected Plasma 1?","0, No | 1, Yes",,,,,,,
100
+ plasma2_3,month_3_data,,,dropdown,"Collected Plasma 2?","0, No | 1, Yes",,,,,,,
101
+ plasma3_3,month_3_data,,,dropdown,"Collected Plasma 3?","0, No | 1, Yes",,,,,,,
102
+ serum1_3,month_3_data,,,dropdown,"Collected Serum 1?","0, No | 1, Yes",,,,,,,
103
+ serum2_3,month_3_data,,,dropdown,"Collected Serum 2?","0, No | 1, Yes",,,,,,,
104
+ serum3_3,month_3_data,,,dropdown,"Collected Serum 3?","0, No | 1, Yes",,,,,,,
105
+ sga_3,month_3_data,,,text,"Subject Global Assessment (score = 1-7)",,,number,0.9,7.1,,,
106
+ no_show_3,month_3_data,,,text,"Number of treatments missed",,,number,0,7,,,
107
+ compliance_3,month_3_data,,,dropdown,"How compliant was the patient in drinking the supplement?","0, 100 percent | 1, 99-75 percent | 2, 74-50 percent | 3, 49-25 percent | 4, 0-24 percent",,,,,,,
108
+ hospit_3,month_3_data,,"Hospitalization Data",dropdown,"Was patient hospitalized since last visit?","0, No | 1, Yes",,,,,,,
109
+ cause_hosp_3,month_3_data,,,dropdown,"What was the cause of hospitalization?","1, Vascular access related events | 2, CVD events | 3, Other",,,,,,,
110
+ admission_date_3,month_3_data,,,text,"Date of hospital admission",,,date,,,,,
111
+ discharge_date_3,month_3_data,,,text,"Date of hospital discharge",,,date,,,,,
112
+ discharge_summary_3,month_3_data,,,dropdown,"Discharge summary in patients binder?","0, No | 1, Yes",,,,,,,
113
+ death_3,month_3_data,,"Mortality Data",dropdown,"Has patient died since last visit?","0, No | 1, Yes",,,,,,,
114
+ date_death_3,month_3_data,,,text,"Date of death",,,date,,,,,
115
+ cause_death_3,month_3_data,,,dropdown,"What was the cause of death?","1, All-cause | 2, Cardiovascular",,,,,,,
116
+ complete_study,completion_data,,"Study Completion Information",dropdown,"Has patient completed study?","0, No | 1, Yes",,,,,,,
117
+ withdraw_date,completion_data,,,text,"Put a date if patient withdrew study",,,date,,,,,
118
+ withdraw_reason,completion_data,,,dropdown,"Reason patient withdrew from study","0, Non-compliance | 1, Did not wish to continue in study | 2, Could not tolerate the supplement | 3, Hospitalization | 4, Other",,,,,,,
119
+ complete_study_date,completion_data,,,text,"Date of study completion",,,date,,,,,
120
+ study_comments,completion_data,,"General Comments",notes,Comments,,,,,,,,
121
+ fatexp20_0d31d8,promis_fatigue__short_form_7a,,"Please respond to each item by marking one answer per question.<br><br>In the past 7 days...",radio,"How often did you feel tired?...","1, 1 Never | 2, 2 Rarely | 3, 3 Sometimes | 4, 4 Often | 5, 5 Always",,,,,,,
122
+ fatexp5_d80a4c,promis_fatigue__short_form_7a,,,radio,"How often did you experience extreme exhaustion?...","1, 1 Never | 2, 2 Rarely | 3, 3 Sometimes | 4, 4 Often | 5, 5 Always",,,,,,,
123
+ fatexp18_396fe5,promis_fatigue__short_form_7a,,,radio,"How often did you run out of energy?...","1, 1 Never | 2, 2 Rarely | 3, 3 Sometimes | 4, 4 Often | 5, 5 Always",,,,,,,
124
+ fatimp33_a9e236,promis_fatigue__short_form_7a,,,radio,"How often did your fatigue limit you at work (include work at home)?...","1, 1 Never | 2, 2 Rarely | 3, 3 Sometimes | 4, 4 Often | 5, 5 Always",,,,,,,
125
+ fatimp30_5f4d82,promis_fatigue__short_form_7a,,,radio,"How often were you too tired to think clearly?...","1, 1 Never | 2, 2 Rarely | 3, 3 Sometimes | 4, 4 Often | 5, 5 Always",,,,,,,
126
+ fatimp21_de19f3,promis_fatigue__short_form_7a,,,radio,"How often were you too tired to take a bath or shower?...","1, 1 Never | 2, 2 Rarely | 3, 3 Sometimes | 4, 4 Often | 5, 5 Always",,,,,,,
127
+ fatimp40_f7e5db,promis_fatigue__short_form_7a,,,radio,"How often did you have enough energy to exercise strenuously?...","5, 5 Never | 4, 4 Rarely | 3, 3 Sometimes | 2, 2 Often | 1, 1 Always",,,,,,,
@@ -1,4 +1,4 @@
1
- survey "&#8220;Kitchen Sink&#8221; survey" do
1
+ survey "Kitchen Sink survey" do
2
2
 
3
3
  section "Basic questions" do
4
4
  # A label is a question that accepts no answers
@@ -0,0 +1,233 @@
1
+ require 'fastercsv'
2
+ require 'active_support' # for humanize
3
+ module Surveyor
4
+ class RedcapParser
5
+ # Attributes
6
+ attr_accessor :context
7
+
8
+ # Class methods
9
+ def self.parse(str, filename)
10
+ puts
11
+ Surveyor::RedcapParser.new.parse(str, filename)
12
+ puts
13
+ puts
14
+ end
15
+
16
+ # Instance methods
17
+ def initialize
18
+ self.context = {}
19
+ end
20
+ def parse(str, filename)
21
+ begin
22
+ FasterCSV.parse(str, :headers => :first_row, :return_headers => true, :header_converters => :symbol) do |r|
23
+ if r.header_row? # header row
24
+ return puts "Missing headers: #{missing_columns(r).inspect}\n\n" unless missing_columns(r).blank?
25
+ context[:survey] = Survey.new(:title => filename)
26
+ print "survey_#{context[:survey].access_code} "
27
+ else # non-header rows
28
+ SurveySection.build_or_set(context, r)
29
+ Question.build_and_set(context, r)
30
+ Answer.build_and_set(context, r)
31
+ Validation.build_and_set(context, r)
32
+ Dependency.build_and_set(context, r)
33
+ end
34
+ end
35
+ print context[:survey].save ? "saved. " : " not saved! #{context[:survey].errors.each_full{|x| x }.join(", ")} "
36
+ # print context[:survey].sections.map(&:questions).flatten.map(&:answers).flatten.map{|x| x.errors.each_full{|y| y}.join}.join
37
+ rescue FasterCSV::MalformedCSVError
38
+ puts = "Oops. Not a valid CSV file."
39
+ # ensure
40
+ end
41
+ end
42
+ def missing_columns(r)
43
+ required_columns - r.headers.map(&:to_s)
44
+ end
45
+ def required_columns
46
+ %w(variable__field_name form_name field_units section_header field_type field_label choices_or_calculations field_note text_validation_type text_validation_min text_validation_max identifier branching_logic_show_field_only_if required_field)
47
+ end
48
+ end
49
+ end
50
+
51
+ # Surveyor models with extra parsing methods
52
+ class Survey < ActiveRecord::Base
53
+ include Surveyor::Models::SurveyMethods
54
+ end
55
+ class SurveySection < ActiveRecord::Base
56
+ include Surveyor::Models::SurveySectionMethods
57
+ def self.build_or_set(context, r)
58
+ unless context[:survey_section] && context[:survey_section].reference_identifier == r[:form_name]
59
+ if match = context[:survey].sections.detect{|ss| ss.reference_identifier == r[:form_name]}
60
+ context[:current_survey_section] = match
61
+ else
62
+ context[:survey_section] = context[:survey].sections.build({:title => r[:form_name].to_s.humanize, :reference_identifier => r[:form_name]})
63
+ print "survey_section_#{context[:survey_section].reference_identifier} "
64
+ end
65
+ end
66
+ end
67
+ end
68
+ class QuestionGroup < ActiveRecord::Base
69
+ include Surveyor::Models::QuestionGroupMethods
70
+ end
71
+ class Question < ActiveRecord::Base
72
+ include Surveyor::Models::QuestionMethods
73
+ def self.build_and_set(context, r)
74
+ if !r[:section_header].blank?
75
+ context[:survey_section].questions.build({:display_type => "label", :text => r[:section_header]})
76
+ print "label_ "
77
+ end
78
+ context[:question] = context[:survey_section].questions.build({
79
+ :reference_identifier => r[:variable__field_name],
80
+ :text => r[:field_label],
81
+ :help_text => r[:field_note],
82
+ :is_mandatory => (/^y/i.match r[:required_field]) ? true : false,
83
+ :pick => pick_from_field_type(r[:field_type]),
84
+ :display_type => display_type_from_field_type(r[:field_type])
85
+ })
86
+ print "question_#{context[:question].reference_identifier} "
87
+ end
88
+ def self.pick_from_field_type(ft)
89
+ {"checkbox" => :any, "radio" => :one}[ft] || :none
90
+ end
91
+ def self.display_type_from_field_type(ft)
92
+ {"text" => :string, "dropdown" => :dropdown, "notes" => :text}[ft]
93
+ end
94
+ end
95
+ class Dependency < ActiveRecord::Base
96
+ include Surveyor::Models::DependencyMethods
97
+ def self.build_and_set(context, r)
98
+ unless (bl = r[:branching_logic_show_field_only_if]).blank?
99
+ # TODO: forgot to tie rule key to component, counting on the sequence of components
100
+ letters = ('A'..'Z').to_a
101
+ hash = decompose_rule(bl)
102
+ context[:dependency] = context[:question].build_dependency(:rule => hash[:rule])
103
+ hash[:components].each do |component|
104
+ context[:dependency].dependency_conditions.build(decompose_component(component).merge(:lookup_reference => context[:lookup], :rule_key => letters.shift))
105
+ end
106
+ print "dependency(#{hash[:rule]}) "
107
+ end
108
+ end
109
+ def self.decompose_component(str)
110
+ # [initial_52] = "1"
111
+ if match = str.match(/^\[(\w+)\] ?([!=><]+) ?"(\w+)"$/)
112
+ {:question_reference => match[1], :operator => match[2].gsub(/^=$/, "=="), :answer_reference => match[3]}
113
+ # [initial_119(2)] = "1"
114
+ elsif match = str.match(/^\[(\w+)\((\w+)\)\] ?([!=><]+) ?"1"$/)
115
+ {:question_reference => match[1], :operator => match[3].gsub(/^=$/, "=="), :answer_reference => match[2]}
116
+ # [f1_q15] >= 21
117
+ elsif match = str.match(/^\[(\w+)\] ?([!=><]+) ?(\d+)$/)
118
+ {:question_reference => match[1], :operator => match[2].gsub(/^=$/, "=="), :integer_value => match[3]}
119
+ # uhoh
120
+ else
121
+ puts "\n!!! skipping dependency_condition #{str}"
122
+ end
123
+ end
124
+ def self.decompose_rule(str)
125
+ # see spec/lib/redcap_parser_spec.rb for examples
126
+ letters = ('A'..'Z').to_a
127
+ rule = str
128
+ components = str.split(/\band\b|\bor\b|\((?!\d)|\)(?!\(|\])/).reject(&:blank?).map(&:strip)
129
+ components.each_with_index do |part, i|
130
+ # internal commas on the right side of the operator e.g. '[initial_189] = "1, 2, 3"'
131
+ if match = part.match(/^(\[[^\]]+\][^\"]+)"([0-9 ]+,[0-9 ,]+)"$/)
132
+ nums = match[2].split(",").map(&:strip)
133
+ components[i] = nums.map{|x| "#{match[1]}\"#{x}\""}
134
+ # sub in rule key
135
+ rule = rule.gsub(part, "(#{nums.map{letters.shift}.join(' and ')})")
136
+ # multiple internal parenthesis on the left e.g. '[initial_119(1)(2)(3)(4)(6)] = "1"'
137
+ elsif match = part.match(/^\[(\w+)(\(\d+\)\([\d\(\)]+)\]([^\"]+"\d+")$/)
138
+ nums = match[2].split(/\(|\)/).reject(&:blank?).map(&:strip)
139
+ components[i] = nums.map{|x| "[#{match[1]}(#{x})]#{match[3]}"}
140
+ # sub in rule key
141
+ rule = rule.gsub(part, "(#{nums.map{letters.shift}.join(' and ')})")
142
+ else
143
+ # 'or' on the right of the operator
144
+ components[i] = components[i-1].gsub(/"(\d+)"/, part) if part.match(/^"(\d+)"$/) && i != 0
145
+ # sub in rule key
146
+ rule = rule.gsub(part){letters.shift}
147
+ end
148
+ end
149
+ {:rule => rule, :components => components.flatten}
150
+ end
151
+ end
152
+ class DependencyCondition < ActiveRecord::Base
153
+ include Surveyor::Models::DependencyConditionMethods
154
+ attr_accessor :question_reference, :answer_reference, :lookup_reference
155
+ before_save :resolve_references
156
+ def resolve_references
157
+ print "resolve(#{question_reference},#{answer_reference})"
158
+ if row = lookup_reference.find{|r| r[0] == question_reference and r[1] == answer_reference}
159
+ print "...found "
160
+ self.answer = row[2]
161
+ self.question = self.answer.question
162
+ else
163
+ puts "\n!!! failed lookup for dependency_condition q: #{question_reference} a: #{question_reference}"
164
+ end
165
+ end
166
+ end
167
+ class Answer < ActiveRecord::Base
168
+ include Surveyor::Models::AnswerMethods
169
+ def self.build_and_set(context, r)
170
+ r[:choices_or_calculations].to_s.split("|").each do |pair|
171
+ aref, atext = pair.strip.split(", ")
172
+ if aref.blank? or atext.blank?
173
+ puts "\n!!! skipping answer #{pair}"
174
+ else
175
+ context[:answer] = context[:question].answers.build(:reference_identifier => aref, :text => atext)
176
+ unless context[:question].reference_identifier.blank? or aref.blank? or !context[:answer].valid?
177
+ context[:lookup] ||= []
178
+ context[:lookup] << [context[:question].reference_identifier, aref, context[:answer]]
179
+ end
180
+ puts "#{context[:answer].errors.full_messages}, #{context[:answer].inspect}" unless context[:answer].valid?
181
+ print "answer_#{context[:answer].reference_identifier} "
182
+ end
183
+ end
184
+ end
185
+ end
186
+ class Validation < ActiveRecord::Base
187
+ include Surveyor::Models::ValidationMethods
188
+ def self.build_and_set(context, r)
189
+ # text_validation_type text_validation_min text_validation_max
190
+ min = r[:text_validation_min].to_s.blank? ? nil : r[:text_validation_min].to_s
191
+ max = r[:text_validation_max].to_s.blank? ? nil : r[:text_validation_max].to_s
192
+ type = r[:text_validation_type].to_s.blank? ? nil : r[:text_validation_type].to_s
193
+ if min or max
194
+ context[:question].answers.each do |a|
195
+ context[:validation] = a.validations.build(:rule => min ? max ? "A and B" : "A" : "B")
196
+ context[:validation].validation_conditions.build(:rule_key => "A", :operator => ">=", :integer_value => min) if min
197
+ context[:validation].validation_conditions.build(:rule_key => "B", :operator => "<=", :integer_value => max) if max
198
+ end
199
+ elsif type
200
+ # date email integer number phone
201
+ case r[:text_validation_type]
202
+ when "date"
203
+ context[:question].display_type = :date if context[:question].display_type == :string
204
+ when "email"
205
+ context[:question].answers.each do |a|
206
+ context[:validation] = a.validations.build(:rule => "A")
207
+ context[:validation].validation_conditions.build(:rule_key => "A", :operator => "=~", :regexp_value => "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$")
208
+ end
209
+ when "integer"
210
+ context[:question].display_type = :integer if context[:question].display_type == :string
211
+ context[:question].answers.each do |a|
212
+ context[:validation] = a.validations.build(:rule => "A")
213
+ context[:validation].validation_conditions.build(:rule_key => "A", :operator => "=~", :regexp_value => "\d+")
214
+ end
215
+ when "number"
216
+ context[:question].display_type = :float if context[:question].display_type == :string
217
+ context[:question].answers.each do |a|
218
+ context[:validation] = a.validations.build(:rule => "A")
219
+ context[:validation].validation_conditions.build(:rule_key => "A", :operator => "=~", :regexp_value => "^\d*(,\d{3})*(\.\d*)?$")
220
+ end
221
+ when "phone"
222
+ context[:question].answers.each do |a|
223
+ context[:validation] = a.validations.build(:rule => "A")
224
+ context[:validation].validation_conditions.build(:rule_key => "A", :operator => "=~", :regexp_value => "\d{3}.*\d{4}")
225
+ end
226
+ end
227
+ end
228
+ end
229
+
230
+ end
231
+ class ValidationCondition < ActiveRecord::Base
232
+ include Surveyor::Models::ValidationConditionMethods
233
+ end
@@ -10,6 +10,15 @@ namespace :surveyor do
10
10
  Surveyor::Parser.parse File.read(file)
11
11
  puts "--- Done #{file} ---"
12
12
  end
13
+ desc "generate and load survey from REDCap Data Dictionary (specify FILE=surveys/redcap.csv)"
14
+ task :redcap => :environment do
15
+ raise "USAGE: file name required e.g. 'FILE=surveys/redcap_demo_survey.csv'" if ENV["FILE"].blank?
16
+ file = File.join(RAILS_ROOT, ENV["FILE"])
17
+ raise "File does not exist: #{file}" unless FileTest.exists?(file)
18
+ puts "--- Parsing #{file} ---"
19
+ Surveyor::RedcapParser.parse File.read(file), File.basename(file, ".csv")
20
+ puts "--- Done #{file} ---"
21
+ end
13
22
  desc "generate a surveyor DSL file from a survey"
14
23
  task :unparse => :environment do
15
24
  surveys = Survey.all
@@ -0,0 +1,48 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Surveyor::RedcapParser do
4
+ before(:each) do
5
+ # @parser = Surveyor::Parser.new
6
+ end
7
+ it "should decompose dependency rules" do
8
+ # basic
9
+ Dependency.decompose_rule('[f1_q12]="1"').should == {:rule => "A", :components => ['[f1_q12]="1"']}
10
+ # spacing
11
+ Dependency.decompose_rule('[f1_q9] = "1"').should == {:rule => "A", :components => ['[f1_q9] = "1"']}
12
+ # and
13
+ Dependency.decompose_rule('[pre_q88]="1" and [pre_q90]="1"').should == {:rule => "A and B", :components => ['[pre_q88]="1"', '[pre_q90]="1"']}
14
+ # or
15
+ Dependency.decompose_rule('[second_q111]="1" or [second_q111]="3"').should == {:rule => "A or B", :components => ['[second_q111]="1"', '[second_q111]="3"']}
16
+ # or and
17
+ Dependency.decompose_rule('[second_q100]="1" or [second_q100]="3" and [second_q101]="1"').should == {:rule => "A or B and C", :components => ['[second_q100]="1"', '[second_q100]="3"', '[second_q101]="1"']}
18
+ # and or
19
+ Dependency.decompose_rule('[second_q4]="1" and [second_q11]="1" or [second_q11]="98"').should == {:rule => "A and B or C", :components => ['[second_q4]="1"', '[second_q11]="1"', '[second_q11]="98"']}
20
+ # or or or
21
+ Dependency.decompose_rule('[pre_q74]="1" or [pre_q74]="2" or [pre_q74]="4" or [pre_q74]="5"').should == {:rule => "A or B or C or D", :components => ['[pre_q74]="1"', '[pre_q74]="2"', '[pre_q74]="4"', '[pre_q74]="5"']}
22
+ # and with different operator
23
+ Dependency.decompose_rule('[f1_q15] >= 21 and [f1_q28] ="1"').should == {:rule => "A and B", :components => ['[f1_q15] >= 21', '[f1_q28] ="1"']}
24
+ end
25
+ it "should decompose nested dependency rules" do
26
+ # external parenthesis
27
+ Dependency.decompose_rule('([pre_q74]="1" or [pre_q74]="2" or [pre_q74]="4" or [pre_q74]="5") and [pre_q76]="2"').should == {:rule => "(A or B or C or D) and E", :components => ['[pre_q74]="1"', '[pre_q74]="2"', '[pre_q74]="4"', '[pre_q74]="5"', '[pre_q76]="2"']}
28
+ # internal parenthesis
29
+ Dependency.decompose_rule('[f1_q10(4)]="1"').should == {:rule => "A", :components => ['[f1_q10(4)]="1"']}
30
+ # internal and external parenthesis
31
+ Dependency.decompose_rule('([f1_q7(11)] = "1" or [initial_52] = "1") and [pre_q76]="2"').should == {:rule => "(A or B) and C", :components => ['[f1_q7(11)] = "1"', '[initial_52] = "1"', '[pre_q76]="2"']}
32
+ end
33
+ it "should decompose shortcut dependency rules" do
34
+ # 'or' on the right of the operator
35
+ Dependency.decompose_rule('[initial_108] = "1" or "2"').should == {:rule => "A or B", :components => ['[initial_108] = "1"', '[initial_108] = "2"']}
36
+ # multiple 'or' on the right
37
+ Dependency.decompose_rule('[initial_52] = "1" or "2" or "3"').should == {:rule => "A or B or C", :components => ['[initial_52] = "1"', '[initial_52] = "2"', '[initial_52] = "3"']}
38
+ # commas on the right
39
+ Dependency.decompose_rule('[initial_189] = "1, 2, 3"').should == {:rule => "(A and B and C)", :components => ['[initial_189] = "1"', '[initial_189] = "2"', '[initial_189] = "3"']}
40
+ # multiple internal parenthesis on the left
41
+ Dependency.decompose_rule('[initial_119(1)(2)(3)(4)(5)] = "1"').should == {:rule => "(A and B and C and D and E)", :components => ['[initial_119(1)] = "1"', '[initial_119(2)] = "1"', '[initial_119(3)] = "1"', '[initial_119(4)] = "1"', '[initial_119(5)] = "1"']}
42
+ end
43
+ it "should decompose components" do
44
+ Dependency.decompose_component('[initial_52] = "1"').should == {:question_reference => 'initial_52', :operator => '==', :answer_reference => '1'}
45
+ Dependency.decompose_component('[initial_119(2)] = "1"').should == {:question_reference => 'initial_119', :operator => '==', :answer_reference => '2'}
46
+ Dependency.decompose_component('[f1_q15] >= 21').should == {:question_reference => 'f1_q15', :operator => '>=', :integer_value => '21'}
47
+ end
48
+ end
data/surveyor.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{surveyor}
8
- s.version = "0.17.0"
8
+ s.version = "0.18.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Brian Chamberlain", "Mark Yoon"]
12
- s.date = %q{2010-11-30}
12
+ s.date = %q{2010-12-01}
13
13
  s.email = %q{yoon@northwestern.edu}
14
14
  s.extra_rdoc_files = [
15
15
  "README.md"
@@ -17,6 +17,7 @@ Gem::Specification.new do |s|
17
17
  s.files = [
18
18
  ".gitignore",
19
19
  ".rvmrc",
20
+ "CHANGELOG",
20
21
  "Gemfile",
21
22
  "Gemfile.lock",
22
23
  "MIT-LICENSE",
@@ -52,8 +53,10 @@ Gem::Specification.new do |s|
52
53
  "app/views/surveyor/new.html.haml",
53
54
  "app/views/surveyor/show.html.haml",
54
55
  "config/routes.rb",
56
+ "features/redcap.feature",
55
57
  "features/step_definitions/surveyor_steps.rb",
56
58
  "features/step_definitions/web_steps.rb",
59
+ "features/support/REDCapDemoDatabase_DataDictionary.csv",
57
60
  "features/support/env.rb",
58
61
  "features/support/paths.rb",
59
62
  "features/surveyor.feature",
@@ -137,9 +140,6 @@ Gem::Specification.new do |s|
137
140
  "generators/surveyor/templates/migrate/create_validations.rb",
138
141
  "generators/surveyor/templates/surveys/kitchen_sink_survey.rb",
139
142
  "generators/surveyor/templates/tasks/surveyor.rb",
140
- "generators/test_surveyor/templates/TESTING_SURVEYOR",
141
- "generators/test_surveyor/templates/environments/cucumber.rb",
142
- "generators/test_surveyor/test_surveyor_generator.rb",
143
143
  "init.rb",
144
144
  "install.rb",
145
145
  "lib/surveyor.rb",
@@ -157,6 +157,7 @@ Gem::Specification.new do |s|
157
157
  "lib/surveyor/models/validation_condition_methods.rb",
158
158
  "lib/surveyor/models/validation_methods.rb",
159
159
  "lib/surveyor/parser.rb",
160
+ "lib/surveyor/redcap_parser.rb",
160
161
  "lib/surveyor/surveyor_controller_methods.rb",
161
162
  "lib/surveyor/unparser.rb",
162
163
  "lib/tasks/surveyor_tasks.rake",
@@ -165,6 +166,7 @@ Gem::Specification.new do |s|
165
166
  "spec/factories.rb",
166
167
  "spec/lib/common_spec.rb",
167
168
  "spec/lib/parser_spec.rb",
169
+ "spec/lib/redcap_parser_spec.rb",
168
170
  "spec/lib/unparser_spec.rb",
169
171
  "spec/models/answer_spec.rb",
170
172
  "spec/models/dependency_condition_spec.rb",
@@ -197,6 +199,7 @@ Gem::Specification.new do |s|
197
199
  "spec/factories.rb",
198
200
  "spec/lib/common_spec.rb",
199
201
  "spec/lib/parser_spec.rb",
202
+ "spec/lib/redcap_parser_spec.rb",
200
203
  "spec/lib/unparser_spec.rb",
201
204
  "spec/models/answer_spec.rb",
202
205
  "spec/models/dependency_condition_spec.rb",
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: surveyor
3
3
  version: !ruby/object:Gem::Version
4
- hash: 91
4
+ hash: 87
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 17
8
+ - 18
9
9
  - 0
10
- version: 0.17.0
10
+ version: 0.18.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Brian Chamberlain
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-11-30 00:00:00 -06:00
19
+ date: 2010-12-01 00:00:00 -06:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -58,6 +58,7 @@ extra_rdoc_files:
58
58
  files:
59
59
  - .gitignore
60
60
  - .rvmrc
61
+ - CHANGELOG
61
62
  - Gemfile
62
63
  - Gemfile.lock
63
64
  - MIT-LICENSE
@@ -93,8 +94,10 @@ files:
93
94
  - app/views/surveyor/new.html.haml
94
95
  - app/views/surveyor/show.html.haml
95
96
  - config/routes.rb
97
+ - features/redcap.feature
96
98
  - features/step_definitions/surveyor_steps.rb
97
99
  - features/step_definitions/web_steps.rb
100
+ - features/support/REDCapDemoDatabase_DataDictionary.csv
98
101
  - features/support/env.rb
99
102
  - features/support/paths.rb
100
103
  - features/surveyor.feature
@@ -178,9 +181,6 @@ files:
178
181
  - generators/surveyor/templates/migrate/create_validations.rb
179
182
  - generators/surveyor/templates/surveys/kitchen_sink_survey.rb
180
183
  - generators/surveyor/templates/tasks/surveyor.rb
181
- - generators/test_surveyor/templates/TESTING_SURVEYOR
182
- - generators/test_surveyor/templates/environments/cucumber.rb
183
- - generators/test_surveyor/test_surveyor_generator.rb
184
184
  - init.rb
185
185
  - install.rb
186
186
  - lib/surveyor.rb
@@ -198,6 +198,7 @@ files:
198
198
  - lib/surveyor/models/validation_condition_methods.rb
199
199
  - lib/surveyor/models/validation_methods.rb
200
200
  - lib/surveyor/parser.rb
201
+ - lib/surveyor/redcap_parser.rb
201
202
  - lib/surveyor/surveyor_controller_methods.rb
202
203
  - lib/surveyor/unparser.rb
203
204
  - lib/tasks/surveyor_tasks.rake
@@ -206,6 +207,7 @@ files:
206
207
  - spec/factories.rb
207
208
  - spec/lib/common_spec.rb
208
209
  - spec/lib/parser_spec.rb
210
+ - spec/lib/redcap_parser_spec.rb
209
211
  - spec/lib/unparser_spec.rb
210
212
  - spec/models/answer_spec.rb
211
213
  - spec/models/dependency_condition_spec.rb
@@ -309,6 +311,7 @@ test_files:
309
311
  - spec/factories.rb
310
312
  - spec/lib/common_spec.rb
311
313
  - spec/lib/parser_spec.rb
314
+ - spec/lib/redcap_parser_spec.rb
312
315
  - spec/lib/unparser_spec.rb
313
316
  - spec/models/answer_spec.rb
314
317
  - spec/models/dependency_condition_spec.rb
File without changes
@@ -1,21 +0,0 @@
1
- config.cache_classes = true # This must be true for Cucumber to operate correctly!
2
-
3
- # Log error messages when you accidentally call methods on nil.
4
- config.whiny_nils = true
5
-
6
- # Show full error reports and disable caching
7
- config.action_controller.consider_all_requests_local = true
8
- config.action_controller.perform_caching = false
9
-
10
- # Disable request forgery protection in test environment
11
- config.action_controller.allow_forgery_protection = false
12
-
13
- # Tell Action Mailer not to deliver emails to the real world.
14
- # The :test delivery method accumulates sent emails in the
15
- # ActionMailer::Base.deliveries array.
16
- config.action_mailer.delivery_method = :test
17
-
18
- config.gem "cucumber", :lib => false, :version => ">=0.3.11" unless File.directory?(File.join(Rails.root, 'vendor/plugins/cucumber'))
19
- config.gem "webrat", :lib => false, :version => ">=0.4.4" unless File.directory?(File.join(Rails.root, 'vendor/plugins/webrat'))
20
- config.gem "rspec", :lib => false, :version => ">=1.2.6" unless File.directory?(File.join(Rails.root, 'vendor/plugins/rspec'))
21
- config.gem "rspec-rails", :lib => 'spec/rails', :version => ">=1.2.6" unless File.directory?(File.join(Rails.root, 'vendor/plugins/rspec-rails'))
@@ -1,15 +0,0 @@
1
- class TestSurveyorGenerator < Rails::Generator::Base
2
- def manifest
3
- record do |m|
4
-
5
- m.directory "surveys"
6
-
7
- # Copy README to your app
8
- m.file "TESTING_SURVEYOR", "surveys/TESTING_SURVEYOR"
9
-
10
- m.file "environments/cucumber.rb", "config/environments/cucumber.rb"
11
- m.readme "TESTING_SURVEYOR"
12
-
13
- end
14
- end
15
- end