surveyor 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +19 -0
- data/features/step_definitions/parser_steps.rb +4 -2
- data/features/surveyor_dependencies.feature +33 -0
- data/features/surveyor_parser.feature +24 -0
- data/lib/generators/surveyor/templates/surveys/kitchen_sink_survey.rb +15 -13
- data/lib/surveyor/models/dependency_condition_methods.rb +2 -2
- data/lib/surveyor/models/question_methods.rb +1 -1
- data/lib/surveyor/parser.rb +6 -2
- data/lib/surveyor/version.rb +1 -1
- data/spec/lib/tasks_spec.rake +26 -0
- data/spec/models/dependency_condition_spec.rb +18 -0
- data/spec/models/question_spec.rb +16 -16
- metadata +4 -2
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,22 @@
|
|
1
1
|
History for Surveyor
|
2
2
|
====================
|
3
3
|
|
4
|
+
1.1.0
|
5
|
+
-----
|
6
|
+
|
7
|
+
### Features
|
8
|
+
|
9
|
+
- Breaking change: Question#is_mandatory => false by default. For those who found it useful to have
|
10
|
+
all questions mandatory, the parser accepts `:default_mandatory => true` as an argument to the survey.
|
11
|
+
|
12
|
+
### Fixes
|
13
|
+
|
14
|
+
- fixing and documenting count operators on dependency conditions
|
15
|
+
|
16
|
+
### Infrastructure
|
17
|
+
|
18
|
+
- basic spec for the surveyor task
|
19
|
+
|
4
20
|
1.0.1
|
5
21
|
------
|
6
22
|
|
@@ -10,12 +26,15 @@ History for Surveyor
|
|
10
26
|
now exclude the question or question group from the DOM. These display types are
|
11
27
|
used to inject data (responses) into surveys. Note, custom_class => "hidden" doesn't
|
12
28
|
have any effect until a custom css rule is created by the end user. (#197)
|
29
|
+
|
13
30
|
- more readable parser and more strict parser method aliases (#278)
|
14
31
|
|
15
32
|
### Fixes
|
16
33
|
|
17
34
|
- Replaced deprecated ActiveRecord::Errors#each_full with ActiveRecord::Errors#full_messages. (#363)
|
35
|
+
|
18
36
|
- fixing dependency condition evaluation where #Response.*_value is nil. (#297)
|
37
|
+
|
19
38
|
- fixing grid answers leak, introduced in 5baa7ac3. thanks @jsurrett (#375, #377)
|
20
39
|
|
21
40
|
1.0.0
|
@@ -20,7 +20,7 @@ Given /^the questions?$/ do |q_string|
|
|
20
20
|
SURVEY
|
21
21
|
end
|
22
22
|
|
23
|
-
Given /^I parse redcap file "(
|
23
|
+
Given /^I parse redcap file "(.*)"$/ do |name|
|
24
24
|
Surveyor::RedcapParser.parse File.read(File.join(Rails.root, '..', 'features', 'support', name)), name
|
25
25
|
end
|
26
26
|
|
@@ -50,7 +50,9 @@ Then /^there should be (\d+) question(?:s?) with:$/ do |x, table|
|
|
50
50
|
table.hashes.each do |hash|
|
51
51
|
hash["reference_identifier"] = nil if hash["reference_identifier"] == "nil"
|
52
52
|
hash["custom_class"] = nil if hash["custom_class"] == "nil"
|
53
|
-
|
53
|
+
hash["is_mandatory"] = (hash["is_mandatory"] == "true" ? true : (hash["is_mandatory"] == "false" ? false : hash["is_mandatory"]))
|
54
|
+
result = Question.find(:first, :conditions => hash)
|
55
|
+
result.should_not be_nil
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
@@ -328,3 +328,36 @@ Feature: Survey dependencies
|
|
328
328
|
Then the question "How much does it cost to run your non-passive heating solutions?" should be hidden
|
329
329
|
And I choose "Oven"
|
330
330
|
Then the question "How much does it cost to run your non-passive heating solutions?" should be triggered
|
331
|
+
|
332
|
+
@javascript
|
333
|
+
Scenario: Count== dependencies
|
334
|
+
Given I parse
|
335
|
+
"""
|
336
|
+
survey "Counting" do
|
337
|
+
section "First" do
|
338
|
+
q_counts "How many times do you count a day", :pick => :any
|
339
|
+
a_1 "Once for me"
|
340
|
+
a_2 "Once for you"
|
341
|
+
a_3 "Once for everyone"
|
342
|
+
|
343
|
+
label "Good!"
|
344
|
+
dependency :rule => "A"
|
345
|
+
condition_A :q_counts, "count==1"
|
346
|
+
|
347
|
+
label "Twice as good!"
|
348
|
+
dependency :rule => "A"
|
349
|
+
condition_A :q_counts, "count==2"
|
350
|
+
end
|
351
|
+
end
|
352
|
+
"""
|
353
|
+
When I go to the surveys page
|
354
|
+
And I start the "Counting" survey
|
355
|
+
Then I should see "How many times do you count a day"
|
356
|
+
And the element "#q_2" should be hidden
|
357
|
+
And the element "#q_3" should be hidden
|
358
|
+
When I check "Once for me"
|
359
|
+
Then the element "#q_2" should not be hidden
|
360
|
+
And the element "#q_3" should be hidden
|
361
|
+
When I check "Once for you"
|
362
|
+
Then the element "#q_3" should not be hidden
|
363
|
+
And the element "#q_2" should be hidden
|
@@ -449,3 +449,27 @@ Feature: Survey parser
|
|
449
449
|
| sad | 0 |
|
450
450
|
| indifferent | 1 |
|
451
451
|
| happy | 2 |
|
452
|
+
|
453
|
+
Scenario: Parsing mandatory questions
|
454
|
+
Given I parse
|
455
|
+
"""
|
456
|
+
survey "Chores", :default_mandatory => true do
|
457
|
+
section "Morning" do
|
458
|
+
q "Did you take out the trash", :pick => :one
|
459
|
+
a "Yes"
|
460
|
+
a "No"
|
461
|
+
|
462
|
+
q "Did you do the laundry", :pick => :one
|
463
|
+
a "Yes"
|
464
|
+
a "No"
|
465
|
+
|
466
|
+
q "Optional comments", :is_mandatory => false
|
467
|
+
a :string
|
468
|
+
end
|
469
|
+
end
|
470
|
+
"""
|
471
|
+
And there should be 3 questions with:
|
472
|
+
| text | is_mandatory |
|
473
|
+
| Did you take out the trash | true |
|
474
|
+
| Did you do the laundry | true |
|
475
|
+
| Optional comments | false |
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
|
2
|
+
# Question#is_mandatory is now false by default. The default_mandatory option allows you to set
|
3
|
+
# is_mandatory for all questions in a survey.
|
4
|
+
survey "Kitchen Sink survey", :default_mandatory => false do
|
3
5
|
|
4
6
|
section "Basic questions" do
|
5
7
|
# A label is a question that accepts no answers
|
6
8
|
label "These questions are examples of the basic supported input types"
|
7
9
|
|
8
10
|
# A basic question with radio buttons
|
9
|
-
|
11
|
+
question "What is your favorite color?", :pick => :one
|
10
12
|
answer "red"
|
11
13
|
answer "blue"
|
12
14
|
answer "green"
|
@@ -14,7 +16,10 @@ survey "Kitchen Sink survey" do
|
|
14
16
|
answer :other
|
15
17
|
|
16
18
|
# A basic question with checkboxes
|
17
|
-
# "question" and "answer" may be abbreviated as "q" and "a"
|
19
|
+
# The "question" and "answer" methods may be abbreviated as "q" and "a".
|
20
|
+
# Append a reference identifier (a short string used for dependencies and validations) using the "_" underscore.
|
21
|
+
# Question reference identifiers must be unique within a survey.
|
22
|
+
# Answer reference identifiers must be unique within a question
|
18
23
|
q_2 "Choose the colors you don't like", :pick => :any
|
19
24
|
a_1 "red"
|
20
25
|
a_2 "blue"
|
@@ -23,8 +28,7 @@ survey "Kitchen Sink survey" do
|
|
23
28
|
a :omit
|
24
29
|
|
25
30
|
# A dependent question, with conditions and rule to logically join them
|
26
|
-
# the
|
27
|
-
# question reference identifiers used in conditions need to be unique on a survey for the lookups to work
|
31
|
+
# If the conditions, logically joined into the rule, are met, then the question will be shown.
|
28
32
|
q_2a "Please explain why you don't like this color?"
|
29
33
|
a_1 "explanation", :text, :help_text => "Please give an explanation for each color you don't like"
|
30
34
|
dependency :rule => "A or B or C or D"
|
@@ -33,10 +37,8 @@ survey "Kitchen Sink survey" do
|
|
33
37
|
condition_C :q_2, "==", :a_3
|
34
38
|
condition_D :q_2, "==", :a_4
|
35
39
|
|
36
|
-
#
|
37
|
-
#
|
38
|
-
# It understands conditions of the form count> count< count>= count<=
|
39
|
-
# count!=
|
40
|
+
# The count operator checks how many responses exist for the referenced question.
|
41
|
+
# It understands conditions of the form: count== count!= count> count< count>= count<=
|
40
42
|
q_2b "Please explain why you dislike so many colors?"
|
41
43
|
a_1 "explanation", :text
|
42
44
|
dependency :rule => "Z"
|
@@ -81,10 +83,10 @@ survey "Kitchen Sink survey" do
|
|
81
83
|
condition_A :q_cooling_1, "!=", :a_4
|
82
84
|
|
83
85
|
# Surveys, sections, questions, groups, and answers all take the following reference arguments
|
84
|
-
# :reference_identifier # usually from paper
|
85
|
-
# :data_export_identifier # data export
|
86
|
-
# :common_namespace # maping to a common
|
87
|
-
# :common_identifier # maping to a common
|
86
|
+
# :reference_identifier # used for parsing references, usually derived from the paper version
|
87
|
+
# :data_export_identifier # used for data export
|
88
|
+
# :common_namespace # maping to a common vocabulary - the namespace
|
89
|
+
# :common_identifier # maping to a common vocabulary - the identifier within the namespace
|
88
90
|
q "Get me started on an improv sketch", :reference_identifier => "improv_start", :data_export_identifier => "i_s", :common_namespace => "sketch comedy questions", :common_identifier => "get me started"
|
89
91
|
a "who", :string
|
90
92
|
a "what", :string
|
@@ -37,8 +37,8 @@ module Surveyor
|
|
37
37
|
def to_hash(response_set)
|
38
38
|
# all responses to associated question
|
39
39
|
responses = question.blank? ? [] : response_set.responses.where("responses.answer_id in (?)", question.answer_ids).all
|
40
|
-
if self.operator.match /^count(
|
41
|
-
op, i = self.operator.scan(/^count(
|
40
|
+
if self.operator.match /^count(>|>=|<|<=|==|!=)\d+$/
|
41
|
+
op, i = self.operator.scan(/^count(>|>=|<|<=|==|!=)(\d+)$/).flatten
|
42
42
|
# logger.warn({rule_key.to_sym => responses.count.send(op, i.to_i)})
|
43
43
|
return {rule_key.to_sym => (op == "!=" ? !responses.count.send("==", i.to_i) : responses.count.send(op, i.to_i))}
|
44
44
|
elsif operator == "!=" and (responses.blank? or responses.none?{|r| r.answer.id == self.answer.id})
|
data/lib/surveyor/parser.rb
CHANGED
@@ -152,9 +152,11 @@ module SurveyorParserSurveyMethods
|
|
152
152
|
|
153
153
|
# build and set context
|
154
154
|
title = args[0]
|
155
|
+
args[1] ||= {}
|
156
|
+
context[:default_mandatory] = args[1].delete(:default_mandatory) || false
|
155
157
|
self.attributes = ({
|
156
158
|
:title => title,
|
157
|
-
:reference_identifier => reference_identifier }.merge(args[1]
|
159
|
+
:reference_identifier => reference_identifier }.merge(args[1]))
|
158
160
|
context[:survey] = self
|
159
161
|
end
|
160
162
|
def clear(context)
|
@@ -165,7 +167,8 @@ module SurveyorParserSurveyMethods
|
|
165
167
|
:bad_references => [],
|
166
168
|
:duplicate_references => [],
|
167
169
|
:dependency_conditions => [],
|
168
|
-
:questions_with_correct_answers => {}
|
170
|
+
:questions_with_correct_answers => {},
|
171
|
+
:default_mandatory => false
|
169
172
|
})
|
170
173
|
end
|
171
174
|
end
|
@@ -239,6 +242,7 @@ module SurveyorParserQuestionMethods
|
|
239
242
|
self.attributes = ({
|
240
243
|
:question_group => context[:question_group],
|
241
244
|
:reference_identifier => reference_identifier,
|
245
|
+
:is_mandatory => context[:default_mandatory],
|
242
246
|
:text => text,
|
243
247
|
:display_type => (original_method =~ /label|image/ ? original_method : "default"),
|
244
248
|
:display_order => context[:survey_section].questions.size }.merge(hash_args))
|
data/lib/surveyor/version.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require "rake"
|
3
|
+
|
4
|
+
describe "app rake tasks" do
|
5
|
+
before(:all) do # do this only once before all tests
|
6
|
+
@rake = Rake::Application.new
|
7
|
+
Rake.application = @rake
|
8
|
+
Rake.application.rake_require "lib/tasks/surveyor_tasks"
|
9
|
+
Rake::Task.define_task(:environment)
|
10
|
+
end
|
11
|
+
after do # after every test
|
12
|
+
@rake[@task_name].reenable if @task_name
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should have 'environment' as a prereq" do
|
16
|
+
@task_name = "surveyor:parse"
|
17
|
+
@rake[@task_name].prerequisites.should include("environment")
|
18
|
+
end
|
19
|
+
it "should should trace" do
|
20
|
+
ENV["FILE"]="surveys/kitchen_sink_survey.rb"
|
21
|
+
@task_name = "surveyor:parse"
|
22
|
+
@rake.options.trace = true
|
23
|
+
Surveyor::Parser.should_receive(:parse).with(File.read(File.join(Rails.root, ENV["FILE"])), {:trace => true})
|
24
|
+
@rake[@task_name].invoke
|
25
|
+
end
|
26
|
+
end
|
@@ -58,6 +58,24 @@ describe DependencyCondition do
|
|
58
58
|
@dependency_condition.operator = "#"
|
59
59
|
@dependency_condition.should have(1).error_on(:operator)
|
60
60
|
end
|
61
|
+
it "should have a properly formed count operator" do
|
62
|
+
%w(count>1 count<1 count>=1 count<=1 count==1 count!=1).each do |o|
|
63
|
+
@dependency_condition.operator = o
|
64
|
+
@dependency_condition.should have(0).errors_on(:operator)
|
65
|
+
end
|
66
|
+
%w(count> count< count>= count<= count== count!=).each do |o|
|
67
|
+
@dependency_condition.operator = o
|
68
|
+
@dependency_condition.should have(1).errors_on(:operator)
|
69
|
+
end
|
70
|
+
%w(count=1 count><1 count<>1 count!1 count!!1 count=>1 count=<1).each do |o|
|
71
|
+
@dependency_condition.operator = o
|
72
|
+
@dependency_condition.should have(1).errors_on(:operator)
|
73
|
+
end
|
74
|
+
%w(count= count>< count<> count! count!! count=> count=< count> count< count>= count<= count== count!=).each do |o|
|
75
|
+
@dependency_condition.operator = o
|
76
|
+
@dependency_condition.should have(1).errors_on(:operator)
|
77
|
+
end
|
78
|
+
end
|
61
79
|
|
62
80
|
it "should protect timestamps" do
|
63
81
|
saved_attrs = @dependency_condition.attributes
|
@@ -3,24 +3,24 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
3
3
|
describe Question, "when creating a new question" do
|
4
4
|
before(:each) do
|
5
5
|
@ss = mock_model(SurveySection)
|
6
|
-
@question = Question.new(:text => "What is your favorite color?", :survey_section => @ss, :is_mandatory =>
|
6
|
+
@question = Question.new(:text => "What is your favorite color?", :survey_section => @ss, :is_mandatory => false, :display_order => 1)
|
7
7
|
end
|
8
8
|
|
9
9
|
it "should be invalid without text" do
|
10
10
|
@question.text = nil
|
11
11
|
@question.should have(1).error_on(:text)
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
it "should have a parent survey section" do
|
15
15
|
# this causes issues with building and saving
|
16
16
|
# @question.survey_section = nil
|
17
17
|
# @question.should have(1).error_on(:survey_section_id)
|
18
18
|
end
|
19
|
-
|
20
|
-
it "should be mandatory by default" do
|
21
|
-
@question.mandatory?.should
|
19
|
+
|
20
|
+
it "should not be mandatory by default" do
|
21
|
+
@question.mandatory?.should be_false
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
it "should convert pick attribute to string" do
|
25
25
|
@question.pick.should == "none"
|
26
26
|
@question.pick = :one
|
@@ -28,7 +28,7 @@ describe Question, "when creating a new question" do
|
|
28
28
|
@question.pick = nil
|
29
29
|
@question.pick.should == nil
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
it "should split the text" do
|
33
33
|
@question.split_text.should == "What is your favorite color?"
|
34
34
|
@question.split_text(:pre).should == "What is your favorite color?"
|
@@ -38,11 +38,11 @@ describe Question, "when creating a new question" do
|
|
38
38
|
@question.split_text(:pre).should == "before"
|
39
39
|
@question.split_text(:post).should == "after|extra"
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
it "should have an api_id" do
|
43
43
|
@question.api_id.length.should == 36
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
it "should protect api_id, timestamps" do
|
47
47
|
saved_attrs = @question.attributes
|
48
48
|
if defined? ActiveModel::MassAssignmentSecurity::Error
|
@@ -63,11 +63,11 @@ describe Question, "that has answers" do
|
|
63
63
|
Factory(:answer, :question => @question, :display_order => 1, :text => "red")
|
64
64
|
Factory(:answer, :question => @question, :display_order => 2, :text => "green")
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
it "should have answers" do
|
68
68
|
@question.answers.should have(3).answers
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
it "should retrieve those answers in display_order" do
|
72
72
|
@question.answers.map(&:display_order).should == [1,2,3]
|
73
73
|
end
|
@@ -79,7 +79,7 @@ describe Question, "that has answers" do
|
|
79
79
|
end
|
80
80
|
|
81
81
|
describe Question, "when interacting with an instance" do
|
82
|
-
|
82
|
+
|
83
83
|
before(:each) do
|
84
84
|
@question = Factory(:question)
|
85
85
|
end
|
@@ -88,7 +88,7 @@ describe Question, "when interacting with an instance" do
|
|
88
88
|
@question.display_type = nil
|
89
89
|
@question.renderer.should == :default
|
90
90
|
end
|
91
|
-
|
91
|
+
|
92
92
|
it "should let you know if it is part of a group" do
|
93
93
|
@question.question_group = Factory(:question_group)
|
94
94
|
@question.solo?.should be_false
|
@@ -97,7 +97,7 @@ describe Question, "when interacting with an instance" do
|
|
97
97
|
@question.solo?.should be_true
|
98
98
|
@question.part_of_group?.should be_false
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
require 'mustache'
|
102
102
|
class FakeMustacheContext < ::Mustache
|
103
103
|
def site
|
@@ -111,7 +111,7 @@ describe Question, "when interacting with an instance" do
|
|
111
111
|
@question.text = "You are in {{site}}"
|
112
112
|
@question.render_question_text(FakeMustacheContext).should == "You are in Northwestern"
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
end
|
116
116
|
|
117
117
|
describe Question, "with dependencies" do
|
@@ -131,5 +131,5 @@ describe Question, "with dependencies" do
|
|
131
131
|
@question.destroy
|
132
132
|
Dependency.find_by_id(dep_id).should be_nil
|
133
133
|
end
|
134
|
-
|
134
|
+
|
135
135
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: surveyor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-11-02 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rails
|
@@ -521,6 +521,7 @@ files:
|
|
521
521
|
- spec/lib/parser_spec.rb
|
522
522
|
- spec/lib/rake_kitchen_sink.rb
|
523
523
|
- spec/lib/redcap_parser_spec.rb
|
524
|
+
- spec/lib/tasks_spec.rake
|
524
525
|
- spec/lib/unparser_spec.rb
|
525
526
|
- spec/models/answer_spec.rb
|
526
527
|
- spec/models/dependency_condition_spec.rb
|
@@ -596,6 +597,7 @@ test_files:
|
|
596
597
|
- spec/lib/parser_spec.rb
|
597
598
|
- spec/lib/rake_kitchen_sink.rb
|
598
599
|
- spec/lib/redcap_parser_spec.rb
|
600
|
+
- spec/lib/tasks_spec.rake
|
599
601
|
- spec/lib/unparser_spec.rb
|
600
602
|
- spec/models/answer_spec.rb
|
601
603
|
- spec/models/dependency_condition_spec.rb
|