surveyor 0.14.5 → 0.15.0

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 (49) hide show
  1. data/README.md +12 -4
  2. data/VERSION +1 -1
  3. data/features/step_definitions/surveyor_steps.rb +64 -4
  4. data/features/surveyor.feature +144 -11
  5. data/lib/surveyor.rb +0 -4
  6. data/lib/surveyor/acts_as_response.rb +13 -29
  7. data/lib/surveyor/common.rb +2 -2
  8. data/lib/surveyor/models/answer_methods.rb +14 -1
  9. data/lib/surveyor/models/dependency_condition_methods.rb +4 -3
  10. data/lib/surveyor/models/question_methods.rb +11 -1
  11. data/lib/surveyor/models/response_methods.rb +2 -1
  12. data/lib/surveyor/models/survey_methods.rb +10 -2
  13. data/lib/surveyor/models/survey_section_methods.rb +11 -1
  14. data/lib/surveyor/models/validation_condition_methods.rb +3 -2
  15. data/lib/surveyor/models/validation_methods.rb +2 -2
  16. data/lib/surveyor/parser.rb +274 -0
  17. data/lib/tasks/surveyor_tasks.rake +25 -25
  18. data/spec/factories.rb +1 -1
  19. data/spec/lib/common_spec.rb +22 -0
  20. data/spec/lib/parser_spec.rb +30 -0
  21. data/spec/models/answer_spec.rb +6 -5
  22. data/spec/models/dependency_condition_spec.rb +7 -6
  23. data/spec/models/question_spec.rb +12 -10
  24. data/spec/models/survey_section_spec.rb +3 -2
  25. data/spec/models/survey_spec.rb +11 -0
  26. data/spec/models/validation_condition_spec.rb +5 -5
  27. data/spec/models/validation_spec.rb +5 -4
  28. data/surveyor.gemspec +7 -23
  29. metadata +10 -26
  30. data/lib/fixtures_extensions.rb +0 -6
  31. data/script/surveyor/answer.rb +0 -54
  32. data/script/surveyor/base.rb +0 -75
  33. data/script/surveyor/dependency.rb +0 -13
  34. data/script/surveyor/dependency_condition.rb +0 -40
  35. data/script/surveyor/parser.rb +0 -223
  36. data/script/surveyor/question.rb +0 -59
  37. data/script/surveyor/question_group.rb +0 -26
  38. data/script/surveyor/specs/answer_spec.rb +0 -29
  39. data/script/surveyor/specs/question_spec.rb +0 -63
  40. data/script/surveyor/specs/spec_helper.rb +0 -7
  41. data/script/surveyor/specs/survey_section_spec.rb +0 -23
  42. data/script/surveyor/specs/validation_condition_spec.rb +0 -20
  43. data/script/surveyor/specs/validation_spec.rb +0 -20
  44. data/script/surveyor/survey.rb +0 -34
  45. data/script/surveyor/survey_section.rb +0 -21
  46. data/script/surveyor/validation.rb +0 -21
  47. data/script/surveyor/validation_condition.rb +0 -21
  48. data/script/surveyor/whr_dsl.tmproj +0 -244
  49. data/spec/lib/surveyor_spec.rb +0 -44
@@ -8,12 +8,13 @@ module Surveyor
8
8
  # Scopes
9
9
 
10
10
  # Validations
11
- base.send :validates_numericality_of, :validation_id #, :question_id, :answer_id
12
11
  base.send :validates_presence_of, :operator, :rule_key
13
12
  base.send :validates_inclusion_of, :operator, :in => Surveyor::Common::OPERATORS
14
13
  base.send :validates_uniqueness_of, :rule_key, :scope => :validation_id
14
+ # this causes issues with building and saving
15
+ # base.send :validates_numericality_of, :validation_id #, :question_id, :answer_id
15
16
 
16
- base.send :acts_as_response # includes "as" instance method
17
+ base.send :include, Surveyor::ActsAsResponse # includes "as" instance method
17
18
 
18
19
  # Class methods
19
20
  base.instance_eval do
@@ -11,8 +11,8 @@ module Surveyor
11
11
  # Validations
12
12
  base.send :validates_presence_of, :rule
13
13
  base.send :validates_format_of, :rule, :with => /^(?:and|or|\)|\(|[A-Z]|\s)+$/
14
- base.send :validates_numericality_of, :answer_id
15
-
14
+ # this causes issues with building and saving
15
+ # base.send :validates_numericality_of, :answer_id
16
16
  end
17
17
 
18
18
  # Instance Methods
@@ -0,0 +1,274 @@
1
+ module Surveyor
2
+ class Parser
3
+ # Attributes
4
+ attr_accessor :context
5
+
6
+ # Class methods
7
+ def self.parse(str)
8
+ puts
9
+ Surveyor::Parser.new.instance_eval(str)
10
+ puts
11
+ end
12
+
13
+ # Instance methods
14
+ def initialize
15
+ self.context = {}
16
+ end
17
+
18
+ # This method_missing does all the heavy lifting for the DSL
19
+ def method_missing(missing_method, *args, &block)
20
+ method_name, reference_identifier = missing_method.to_s.split("_", 2)
21
+ type = full(method_name)
22
+
23
+ print reference_identifier.blank? ? "#{type} " : "#{type}_#{reference_identifier} "
24
+
25
+ # check for blocks
26
+ raise "Error: A #{type.humanize} cannot be empty" if block_models.include?(type) && !block_given?
27
+ raise "Error: Dropping the #{type.humanize} block like it's hot!" if !block_models.include?(type) && block_given?
28
+
29
+ # parse and build
30
+ type.classify.constantize.parse_and_build(context, args, method_name, reference_identifier)
31
+
32
+ # evaluate and clear context for block models
33
+ if block_models.include?(type)
34
+ self.instance_eval(&block)
35
+ if type == 'survey'
36
+ puts
37
+ print context[type.to_sym].save ? "saved. " : " not saved! #{context[type.to_sym].errors.each_full{|x| x }.join(", ")} "
38
+ end
39
+ context[type.to_sym].clear(context)
40
+ end
41
+ end
42
+
43
+ # Private methods
44
+ private
45
+
46
+ def full(method_name)
47
+ case method_name.to_s
48
+ when /^section$/; "survey_section"
49
+ when /^g|grid|group|repeater$/; "question_group"
50
+ when /^q|label|image$/; "question"
51
+ when /^a$/; "answer"
52
+ when /^d$/; "dependency"
53
+ when /^c(ondition)?$/; context[:validation] ? "validation_condition" : "dependency_condition"
54
+ when /^v$/; "validation"
55
+ when /^dc(ondition)?$/; "dependency_condition"
56
+ when /^vc(ondition)?$/; "validation_condition"
57
+ else method_name
58
+ end
59
+ end
60
+ def block_models
61
+ %w(survey survey_section question_group)
62
+ end
63
+ end
64
+ end
65
+
66
+ # Surveyor models with extra parsing methods
67
+ class Survey < ActiveRecord::Base
68
+ # block
69
+ include Surveyor::Models::SurveyMethods
70
+
71
+ def self.parse_and_build(context, args, original_method, reference_identifier)
72
+ # clear context
73
+ context.delete_if{|k,v| true}
74
+ context[:question_references] = {}
75
+ context[:answer_references] = {}
76
+
77
+ # build and set context
78
+ title = args[0]
79
+ context[:survey] = new({ :title => title,
80
+ :reference_identifier => reference_identifier}.merge(args[1] || {}))
81
+ end
82
+ def clear(context)
83
+ context.delete_if{|k,v| true}
84
+ context[:question_references] = {}
85
+ context[:answer_references] = {}
86
+ end
87
+ end
88
+ class SurveySection < ActiveRecord::Base
89
+ # block
90
+ include Surveyor::Models::SurveySectionMethods
91
+
92
+ def self.parse_and_build(context, args, original_method, reference_identifier)
93
+ # clear context
94
+ context.delete_if{|k,v| k != :survey && k != :question_references && k != :answer_references}
95
+
96
+ # build and set context
97
+ title = args[0]
98
+ context[:survey_section] = context[:survey].sections.build({ :title => title,
99
+ :data_export_identifier => Surveyor::Common.normalize(title)}.merge(args[1] || {}))
100
+ end
101
+ def clear(context)
102
+ context.delete_if{|k,v| k != :survey && k != :question_references && k != :answer_references}
103
+ end
104
+ end
105
+ class QuestionGroup < ActiveRecord::Base
106
+ # block
107
+ include Surveyor::Models::QuestionGroupMethods
108
+
109
+ def self.parse_and_build(context, args, original_method, reference_identifier)
110
+ # clear context
111
+ context.delete_if{|k,v| k != :survey && k != :survey_section && k != :question_references && k != :answer_references}
112
+
113
+ # build and set context
114
+ context[:question_group] = context[:question_group] = new({ :text => args[0] || "Question Group",
115
+ :display_type => (original_method =~ /grid|repeater/ ? original_method : "default")}.merge(args[1] || {}))
116
+
117
+ end
118
+ def clear(context)
119
+ context.delete_if{|k,v| k != :survey && k != :survey_section && k != :question_references && k != :answer_references}
120
+ end
121
+ end
122
+ class Question < ActiveRecord::Base
123
+ # nonblock
124
+ include Surveyor::Models::QuestionMethods
125
+
126
+ def self.parse_and_build(context, args, original_method, reference_identifier)
127
+ # clear context
128
+ context.delete_if{|k,v| %w(question dependency dependency_condition answer validation validation_condition).map(&:to_sym).include? k}
129
+
130
+ # build and set context
131
+ text = args[0] || "Question"
132
+ context[:question] = context[:survey_section].questions.build({
133
+ :question_group => context[:question_group],
134
+ :reference_identifier => reference_identifier,
135
+ :text => text,
136
+ :short_text => text,
137
+ :display_type => (original_method =~ /label|image/ ? original_method : "default"),
138
+ :data_export_identifier => Surveyor::Common.normalize(text)}.merge(args[1] || {}))
139
+
140
+ # keep reference for dependencies
141
+ context[:question_references][reference_identifier] = context[:question] unless reference_identifier.blank?
142
+
143
+ # add grid answers
144
+ if context[:question_group] && context[:question_group].display_type == "grid"
145
+ (context[:grid_answers] || []).each do |grid_answer|
146
+ a = context[:question].answers.build(grid_answer.attributes)
147
+ context[:answer_references][reference_identifier][grid_answer.reference_identifier] = a unless reference_identifier.blank? or grid_answer.reference_identifier.blank?
148
+ end
149
+ end
150
+ end
151
+ end
152
+ class Dependency < ActiveRecord::Base
153
+ # nonblock
154
+ include Surveyor::Models::DependencyMethods
155
+
156
+ def self.parse_and_build(context, args, original_method, reference_identifier)
157
+ # clear context
158
+ context.delete_if{|k,v| %w(dependency dependency_condition).map(&:to_sym).include? k}
159
+
160
+ # build and set context
161
+ context[:dependency] = context[:question].build_dependency({ :question_group => context[:question_group]}.merge(args[0] || {}))
162
+ end
163
+ end
164
+ class DependencyCondition < ActiveRecord::Base
165
+ # nonblock
166
+ include Surveyor::Models::DependencyConditionMethods
167
+
168
+ attr_accessor :question_reference, :answer_reference, :context_reference
169
+ before_save :resolve_references
170
+
171
+ def self.parse_and_build(context, args, original_method, reference_identifier)
172
+ # clear context
173
+ context.delete_if{|k,v| k == :dependency_condition}
174
+
175
+ # build and set context
176
+ a0, a1, a2 = args
177
+ context[:dependency_condition] = context[:dependency].dependency_conditions.build({
178
+ :context_reference => context,
179
+ :operator => a1 || "==",
180
+ :question_reference => a0.to_s.gsub("q_", ""),
181
+ :rule_key => reference_identifier}.merge(a2.is_a?(Hash) ? a2 : {:answer_reference => a2.to_s.gsub("a_", "")}))
182
+ end
183
+ def resolve_references
184
+ if context_reference
185
+ # Looking up references to questions and answers for linking the dependency objects
186
+ print (self.question = context_reference[:question_references][question_reference]) ? "found question:#{question_reference} " : "lost! question:#{question_reference} "
187
+ context_reference[:answer_references][question_reference] ||= {}
188
+ print (self.answer = context_reference[:answer_references][question_reference][answer_reference]) ? "found answer:#{answer_reference} " : "lost! answer:#{answer_reference} "
189
+ end
190
+ end
191
+
192
+ end
193
+ class Answer < ActiveRecord::Base
194
+ # nonblock
195
+ include Surveyor::Models::AnswerMethods
196
+
197
+ def self.parse_and_build(context, args, original_method, reference_identifier)
198
+ # clear context
199
+ context.delete_if{|k,v| %w(answer validation validation_condition).map(&:to_sym).include? k}
200
+
201
+ attrs = { :reference_identifier => reference_identifier,
202
+ :is_exclusive => false,
203
+ :hide_label => false,
204
+ :response_class => "answer"}.merge(self.parse_args(args))
205
+
206
+ # add answers to grid
207
+ if context[:question_group] && context[:question_group].display_type == "grid"
208
+ context[:answer] = new(attrs)
209
+ context[:grid_answers] ||= []
210
+ context[:grid_answers] << context[:answer]
211
+ else
212
+ context[:answer] = context[:question].answers.build(attrs)
213
+ context[:answer_references][context[:question].reference_identifier] ||= {} unless context[:question].reference_identifier.blank?
214
+ context[:answer_references][context[:question].reference_identifier][reference_identifier] = context[:answer] unless reference_identifier.blank? or context[:question].reference_identifier.blank?
215
+ end
216
+ end
217
+ def self.parse_args(args)
218
+ case args[0]
219
+ when Hash # Hash
220
+ self.text_args(args[0][:text]).merge(args[0])
221
+ when String # (String, Hash) or (String, Symbol, Hash)
222
+ self.text_args(args[0]).merge(self.hash_from args[1]).merge(args[2] || {})
223
+ when Symbol # (Symbol, Hash) or (Symbol, Symbol, Hash)
224
+ self.symbol_args(args[0]).merge(self.hash_from args[1]).merge(args[2] || {})
225
+ else
226
+ self.text_args(nil)
227
+ end
228
+ end
229
+ def self.text_args(text = "Answer")
230
+ {:text => text.to_s, :short_text => text, :data_export_identifier => Surveyor::Common.normalize(text)}
231
+ end
232
+ def self.hash_from(arg)
233
+ arg.is_a?(Symbol) ? {:response_class => arg.to_s} : arg.is_a?(Hash) ? arg : {}
234
+ end
235
+ def self.symbol_args(arg)
236
+ case arg
237
+ when :other
238
+ self.text_args("Other")
239
+ when :other_and_string
240
+ self.text_args("Other").merge({:response_class => "string"})
241
+ when :none, :omit # is_exclusive erases and disables other checkboxes and input elements
242
+ self.text_args(arg.to_s.humanize).merge({:is_exclusive => true})
243
+ when :integer, :date, :time, :datetime, :text, :datetime, :string
244
+ self.text_args(arg.to_s.humanize).merge({:response_class => arg.to_s, :hide_label => true})
245
+ end
246
+ end
247
+ end
248
+ class Validation < ActiveRecord::Base
249
+ # nonblock
250
+ include Surveyor::Models::ValidationMethods
251
+
252
+ def self.parse_and_build(context, args, original_method, reference_identifier)
253
+ # clear context
254
+ context.delete_if{|k,v| %w(validation validation_condition).map(&:to_sym).include? k}
255
+
256
+ # build and set context
257
+ context[:validation] = context[:answer].validations.build({:rule => "A"}.merge(args[0] || {}))
258
+ end
259
+ end
260
+ class ValidationCondition < ActiveRecord::Base
261
+ # nonblock
262
+ include Surveyor::Models::ValidationConditionMethods
263
+
264
+ def self.parse_and_build(context, args, original_method, reference_identifier)
265
+ # clear context
266
+ context.delete_if{|k,v| k == :validation_condition}
267
+
268
+ # build and set context
269
+ a0, a1 = args
270
+ context[:validation_condition] = context[:validation].validation_conditions.build({
271
+ :operator => a0 || "==",
272
+ :rule_key => reference_identifier}.merge(a1 || {}))
273
+ end
274
+ end
@@ -1,35 +1,35 @@
1
1
  desc "generate and load survey (specify FILE=surveys/your_survey.rb)"
2
- task :surveyor => :"surveyor:default"
2
+ task :surveyor => :"surveyor:parse"
3
3
 
4
4
  namespace :surveyor do
5
-
6
- task :default => [:generate_fixtures, :load_fixtures]
7
-
8
- desc "generate survey fixtures from survey file"
9
- task :generate_fixtures => :environment do
10
- require File.join(File.dirname(__FILE__), "../../script/surveyor/parser")
5
+ task :parse => :environment do
11
6
  raise "USAGE: file name required e.g. 'FILE=surveys/kitchen_sink_survey.rb'" if ENV["FILE"].blank?
12
- fixture_dir = File.join(RAILS_ROOT, "surveys", "fixtures")
13
- mkdir fixture_dir unless File.exists?(fixture_dir)
14
- SurveyParser::Parser.parse(File.join(RAILS_ROOT, ENV["FILE"]))
7
+ file = File.join(RAILS_ROOT, ENV["FILE"])
8
+ raise "File does not exist: #{file}" unless FileTest.exists?(file)
9
+ puts "--- Parsing #{file} ---"
10
+ Surveyor::Parser.parse File.read(file)
11
+ puts "--- Done #{file} ---"
15
12
  end
16
-
17
- desc "load survey fixtures"
18
- task :load_fixtures => :environment do
19
- require 'active_record/fixtures'
20
- require 'fixtures_extensions' unless ENV["APPEND"].blank?
21
- ActiveRecord::Base.establish_connection(Rails.env)
22
-
23
- fixture_dir = File.join(RAILS_ROOT, "surveys", "fixtures")
24
- fixtures = Dir.glob("#{fixture_dir}/*.yml")
25
- raise "No fixtures found." if fixtures.empty?
26
-
27
- fixtures.each do |file_name|
28
- puts "Loading #{file_name}..."
29
- Fixtures.create_fixtures(fixture_dir, File.basename(file_name, '.*'))
13
+ desc "remove surveys (that don't have response sets)"
14
+ task :remove => :environment do
15
+ surveys = Survey.all.delete_if{|s| !s.response_sets.blank?}
16
+ if surveys
17
+ puts "The following surveys do not have any response sets"
18
+ surveys.each do |survey|
19
+ puts "#{survey.id} #{survey.title}"
20
+ end
21
+ puts "Which survey would you like to remove?"
22
+ id = $stdin.gets.to_i
23
+ if survey_to_delete = surveys.detect{|s| s.id == id}
24
+ puts "removing #{survey_to_delete.title}"
25
+ survey_to_delete.destroy
26
+ else
27
+ put "not found"
28
+ end
29
+ else
30
+ puts "There are no surveys surveys without response sets"
30
31
  end
31
32
  end
32
-
33
33
  end
34
34
 
35
35
  namespace :spec do
data/spec/factories.rb CHANGED
@@ -103,7 +103,7 @@ end
103
103
  Factory.define :response_set do |r|
104
104
  r.user_id {}
105
105
  r.association :survey # r.survey_id {}
106
- r.access_code {Surveyor.make_tiny_code}
106
+ r.access_code {Surveyor::Common.make_tiny_code}
107
107
  r.started_at {Time.now}
108
108
  r.completed_at {}
109
109
  end
@@ -0,0 +1,22 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Surveyor::Common, "" do
4
+ it "should convert text to a code that is more appropirate for a database entry" do
5
+ # A few answers from the survey
6
+ { "This? is a in - t3rrible-@nswer of! (question) on" => "this_t3rrible_nswer",
7
+ "Private insurance/ HMO/ PPO" => "private_insurance_hmo_ppo",
8
+ "<bold>VA</bold>" => "va",
9
+ "PMS (Premenstrual syndrome)/ PMDD (Premenstrual Dysphoric Disorder)" => "pms_pmdd",
10
+ "Have never been employed outside the home" => "never_been_employed_outside_home",
11
+ "Professional" => "professional",
12
+ "Not working because of temporary disability, but expect to return to a job" => "temporary_disability_expect_return_job",
13
+ "How long has it been since you last visited a doctor for a routine checkup (routine being not for a particular reason)?" => "visited_doctor_for_routine_checkup",
14
+ "Do you take medications as directed?" => "you_take_medications_as_directed",
15
+ "Do you every leak urine (or) water when you didn't want to?" => "urine_water_you_didnt_want", #checking for () and ' removal
16
+ "Do your biological family members (not adopted) have a \"history\" of any of the following?" => "family_members_history_any_following",
17
+ "Your health:" => "your_health",
18
+ "In general, you would say your health is:" => "you_would_say_your_health"
19
+ }.each{|k, v| Surveyor::Common.to_normalized_string(k).should == v}
20
+ end
21
+
22
+ end
@@ -0,0 +1,30 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Surveyor::Parser do
4
+ before(:each) do
5
+ @parser = Surveyor::Parser.new
6
+ end
7
+ it "should translate shortcuts into full model names" do
8
+ @parser.send(:full, "section").should == "survey_section"
9
+ @parser.send(:full, "g").should == "question_group"
10
+ @parser.send(:full, "repeater").should == "question_group"
11
+ @parser.send(:full, "label").should == "question"
12
+ @parser.send(:full, "vc").should == "validation_condition"
13
+ @parser.send(:full, "vcondition").should == "validation_condition"
14
+ end
15
+ it "should translate 'condition' based on context" do
16
+ @parser.send(:full, "condition").should == "dependency_condition"
17
+ @parser.send(:full, "c").should == "dependency_condition"
18
+ @parser.context[:validation] = Validation.new
19
+ @parser.send(:full, "condition").should == "validation_condition"
20
+ @parser.send(:full, "c").should == "validation_condition"
21
+ @parser.context[:validation] = nil
22
+ @parser.send(:full, "condition").should == "dependency_condition"
23
+ @parser.send(:full, "c").should == "dependency_condition"
24
+ end
25
+ it "should identify models that take blocks" do
26
+ @parser.send(:block_models).should == %w(survey survey_section question_group)
27
+ end
28
+
29
+
30
+ end
@@ -8,11 +8,12 @@ describe Answer, "when creating a new answer" do
8
8
  it "should be valid" do
9
9
  @answer.should be_valid
10
10
  end
11
-
12
- it "should be invalid without a question_id" do
13
- @answer.question_id = nil
14
- @answer.should_not be_valid
15
- end
11
+
12
+ # this causes issues with building and saving answers to questions within a grid.
13
+ # it "should be invalid without a question_id" do
14
+ # @answer.question_id = nil
15
+ # @answer.should_not be_valid
16
+ # end
16
17
 
17
18
  it "should have 'default' renderer with nil question.pick and response_class" do
18
19
  @answer.question = Factory(:question, :pick => nil)