surveyor 0.14.5 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
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)