surveyor 0.14.4 → 0.14.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +7 -2
- data/VERSION +1 -1
- data/app/models/answer.rb +2 -18
- data/app/models/dependency.rb +2 -45
- data/app/models/dependency_condition.rb +2 -42
- data/app/models/question.rb +2 -49
- data/app/models/question_group.rb +2 -27
- data/app/models/response.rb +2 -34
- data/app/models/response_set.rb +3 -222
- data/app/models/survey.rb +2 -55
- data/app/models/survey_section.rb +2 -12
- data/app/models/validation.rb +2 -32
- data/app/models/validation_condition.rb +2 -42
- data/generators/extend_surveyor/templates/EXTENDING_SURVEYOR +1 -0
- data/lib/surveyor/common.rb +1 -0
- data/lib/surveyor/models/answer_methods.rb +25 -0
- data/lib/surveyor/models/dependency_condition_methods.rb +47 -0
- data/lib/surveyor/models/dependency_methods.rb +52 -0
- data/lib/surveyor/models/question_group_methods.rb +35 -0
- data/lib/surveyor/models/question_methods.rb +55 -0
- data/lib/surveyor/models/response_methods.rb +40 -0
- data/lib/surveyor/models/response_set_methods.rb +174 -0
- data/lib/surveyor/models/survey_methods.rb +63 -0
- data/lib/surveyor/models/survey_section_methods.rb +21 -0
- data/lib/surveyor/models/validation_condition_methods.rb +48 -0
- data/lib/surveyor/models/validation_methods.rb +40 -0
- data/lib/surveyor.rb +3 -3
- data/rails/init.rb +1 -1
- data/surveyor.gemspec +13 -5
- metadata +15 -7
- data/generators/extend_surveyor/templates/extensions/survey_extensions.rb +0 -24
- data/generators/extend_surveyor/templates/extensions/surveyor_controller_extensions.rb +0 -28
- data/generators/extend_surveyor/templates/extensions/surveyor_helper_extensions.rb +0 -17
data/README.md
CHANGED
@@ -102,7 +102,7 @@ Surveyor's controller, models, and views may be customized via classes in your a
|
|
102
102
|
|
103
103
|
script/generate extend_surveyor
|
104
104
|
|
105
|
-
and check out surveys/
|
105
|
+
and check out surveys/EXTENDING\_SURVEYOR
|
106
106
|
|
107
107
|
# Dependencices
|
108
108
|
|
@@ -114,9 +114,14 @@ To work on the plugin code (for enhancements, and bug fixes, etc...) fork this g
|
|
114
114
|
|
115
115
|
# Changes
|
116
116
|
|
117
|
+
0.14.5
|
118
|
+
|
119
|
+
* use modules to include model methods. re-closes #77
|
120
|
+
* rails init. destroy dependent models
|
121
|
+
|
117
122
|
0.14.4
|
118
123
|
|
119
|
-
*
|
124
|
+
* explicitly require surveyor models and helper. update sweeper syntax. closes #77
|
120
125
|
* cleanup and requires
|
121
126
|
* fixing instructions for extending surveyor. closes #76
|
122
127
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.14.
|
1
|
+
0.14.5
|
data/app/models/answer.rb
CHANGED
@@ -1,20 +1,4 @@
|
|
1
1
|
class Answer < ActiveRecord::Base
|
2
|
-
|
3
|
-
|
4
|
-
belongs_to :question
|
5
|
-
has_many :responses
|
6
|
-
|
7
|
-
# Scopes
|
8
|
-
default_scope :order => "display_order ASC"
|
9
|
-
|
10
|
-
# Validations
|
11
|
-
validates_presence_of :text
|
12
|
-
validates_numericality_of :question_id, :allow_nil => false, :only_integer => true
|
13
|
-
|
14
|
-
# Methods
|
15
|
-
def renderer(q = question)
|
16
|
-
r = [q.pick.to_s, self.response_class].compact.map(&:downcase).join("_")
|
17
|
-
r.blank? ? :default : r.to_sym
|
18
|
-
end
|
19
|
-
|
2
|
+
unloadable
|
3
|
+
include Surveyor::Models::AnswerMethods
|
20
4
|
end
|
data/app/models/dependency.rb
CHANGED
@@ -1,47 +1,4 @@
|
|
1
1
|
class Dependency < ActiveRecord::Base
|
2
|
-
|
3
|
-
|
4
|
-
belongs_to :question
|
5
|
-
belongs_to :question_group
|
6
|
-
has_many :dependency_conditions
|
7
|
-
|
8
|
-
# Validations
|
9
|
-
validates_presence_of :rule
|
10
|
-
validates_format_of :rule, :with => /^(?:and|or|\)|\(|[A-Z]|\s)+$/ #TODO properly formed parenthesis etc.
|
11
|
-
validates_numericality_of :question_id, :if => Proc.new { |d| d.question_group_id.nil? }
|
12
|
-
validates_numericality_of :question_group_id, :if => Proc.new { |d| d.question_id.nil? }
|
13
|
-
|
14
|
-
# Attribute aliases
|
15
|
-
alias_attribute :dependent_question_id, :question_id
|
16
|
-
|
17
|
-
def question_group_id=(i)
|
18
|
-
write_attribute(:question_id, nil) unless i.nil?
|
19
|
-
write_attribute(:question_group_id, i)
|
20
|
-
end
|
21
|
-
|
22
|
-
def question_id=(i)
|
23
|
-
write_attribute(:question_group_id, nil) unless i.nil?
|
24
|
-
write_attribute(:question_id, i)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Has this dependency has been met in the context of response_set?
|
28
|
-
# Substitutes the conditions hash into the rule and evaluates it
|
29
|
-
def is_met?(response_set)
|
30
|
-
ch = conditions_hash(response_set)
|
31
|
-
return false if ch.blank?
|
32
|
-
# logger.debug "rule: #{self.rule.inspect}"
|
33
|
-
# logger.debug "rexp: #{rgx.inspect}"
|
34
|
-
# logger.debug "keyp: #{ch.inspect}"
|
35
|
-
# logger.debug "subd: #{self.rule.gsub(rgx){|m| ch[m.to_sym]}}"
|
36
|
-
rgx = Regexp.new(self.dependency_conditions.map{|dc| ["a","o"].include?(dc.rule_key) ? "#{dc.rule_key}(?!nd|r)" : dc.rule_key}.join("|")) # exclude and, or
|
37
|
-
eval(self.rule.gsub(rgx){|m| ch[m.to_sym]})
|
38
|
-
end
|
39
|
-
|
40
|
-
# A hash of the conditions (keyed by rule_key) and their evaluation (boolean) in the context of response_set
|
41
|
-
def conditions_hash(response_set)
|
42
|
-
hash = {}
|
43
|
-
self.dependency_conditions.each{|dc| hash.merge!(dc.to_hash(response_set))}
|
44
|
-
return hash
|
45
|
-
end
|
46
|
-
|
2
|
+
unloadable
|
3
|
+
include Surveyor::Models::DependencyMethods
|
47
4
|
end
|
@@ -1,44 +1,4 @@
|
|
1
1
|
class DependencyCondition < ActiveRecord::Base
|
2
|
-
|
3
|
-
|
4
|
-
OPERATORS = %w(== != < > <= >=) # CONSTANT or @@class_variable when validations listed before class method
|
5
|
-
|
6
|
-
# Associations
|
7
|
-
belongs_to :answer
|
8
|
-
belongs_to :dependency
|
9
|
-
belongs_to :dependent_question, :foreign_key => :question_id, :class_name => :question
|
10
|
-
belongs_to :question
|
11
|
-
|
12
|
-
# Validations
|
13
|
-
validates_numericality_of :dependency_id, :question_id, :answer_id
|
14
|
-
validates_presence_of :operator, :rule_key
|
15
|
-
validates_inclusion_of :operator, :in => OPERATORS
|
16
|
-
validates_uniqueness_of :rule_key, :scope => :dependency_id
|
17
|
-
|
18
|
-
acts_as_response # includes "as" instance method
|
19
|
-
|
20
|
-
# Class methods
|
21
|
-
def self.operators
|
22
|
-
OPERATORS
|
23
|
-
end
|
24
|
-
|
25
|
-
# Instance methods
|
26
|
-
def to_hash(response_set)
|
27
|
-
response = response_set.responses.detect{|r| r.answer_id.to_i == self.answer_id.to_i} || false # eval("nil and false") => nil so return false if no response is found
|
28
|
-
{rule_key.to_sym => (response and self.is_met?(response))}
|
29
|
-
end
|
30
|
-
|
31
|
-
# Checks to see if the response passed in meets the dependency condition
|
32
|
-
def is_met?(response)
|
33
|
-
klass = response.answer.response_class
|
34
|
-
return case self.operator
|
35
|
-
when "==", "<", ">", "<=", ">="
|
36
|
-
response.as(klass).send(self.operator, self.as(klass))
|
37
|
-
when "!="
|
38
|
-
!(response.as(klass) == self.as(klass))
|
39
|
-
else
|
40
|
-
false
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
2
|
+
unloadable
|
3
|
+
include Surveyor::Models::DependencyConditionMethods
|
44
4
|
end
|
data/app/models/question.rb
CHANGED
@@ -1,51 +1,4 @@
|
|
1
1
|
class Question < ActiveRecord::Base
|
2
|
-
|
3
|
-
|
4
|
-
belongs_to :survey_section
|
5
|
-
belongs_to :question_group
|
6
|
-
has_many :answers, :order => "display_order ASC" # it might not always have answers
|
7
|
-
has_one :dependency
|
8
|
-
|
9
|
-
# Scopes
|
10
|
-
default_scope :order => "display_order ASC"
|
11
|
-
|
12
|
-
# Validations
|
13
|
-
validates_presence_of :text, :survey_section_id, :display_order
|
14
|
-
validates_inclusion_of :is_mandatory, :in => [true, false]
|
15
|
-
|
16
|
-
# Instance Methods
|
17
|
-
def initialize(*args)
|
18
|
-
super(*args)
|
19
|
-
default_args
|
20
|
-
end
|
21
|
-
|
22
|
-
def default_args
|
23
|
-
self.is_mandatory ||= true
|
24
|
-
self.display_type ||= "default"
|
25
|
-
self.pick ||= "none"
|
26
|
-
end
|
27
|
-
|
28
|
-
def mandatory?
|
29
|
-
self.is_mandatory == true
|
30
|
-
end
|
31
|
-
|
32
|
-
def dependent?
|
33
|
-
self.dependency != nil
|
34
|
-
end
|
35
|
-
def triggered?(response_set)
|
36
|
-
dependent? ? self.dependency.is_met?(response_set) : true
|
37
|
-
end
|
38
|
-
def css_class(response_set)
|
39
|
-
[(dependent? ? "dependent" : nil), (triggered?(response_set) ? nil : "hidden"), custom_class].compact.join(" ")
|
40
|
-
end
|
41
|
-
|
42
|
-
def part_of_group?
|
43
|
-
!self.question_group.nil?
|
44
|
-
end
|
45
|
-
|
46
|
-
def renderer(g = question_group)
|
47
|
-
r = [g ? g.renderer.to_s : nil, display_type].compact.join("_")
|
48
|
-
r.blank? ? :default : r.to_sym
|
49
|
-
end
|
50
|
-
|
2
|
+
unloadable
|
3
|
+
include Surveyor::Models::QuestionMethods
|
51
4
|
end
|
@@ -1,30 +1,5 @@
|
|
1
1
|
class QuestionGroup < ActiveRecord::Base
|
2
|
-
|
3
|
-
|
4
|
-
has_one :dependency
|
5
|
-
|
6
|
-
# Instance Methods
|
7
|
-
def initialize(*args)
|
8
|
-
super(*args)
|
9
|
-
default_args
|
10
|
-
end
|
11
|
-
|
12
|
-
def default_args
|
13
|
-
self.display_type ||= "inline"
|
14
|
-
end
|
15
|
-
|
16
|
-
def renderer
|
17
|
-
display_type.blank? ? :default : display_type.to_sym
|
18
|
-
end
|
19
|
-
|
20
|
-
def dependent?
|
21
|
-
self.dependency != nil
|
22
|
-
end
|
23
|
-
def triggered?(response_set)
|
24
|
-
dependent? ? self.dependency.is_met?(response_set) : true
|
25
|
-
end
|
26
|
-
def css_class(response_set)
|
27
|
-
[(dependent? ? "dependent" : nil), (triggered?(response_set) ? nil : "hidden"), custom_class].compact.join(" ")
|
28
|
-
end
|
2
|
+
unloadable
|
3
|
+
include Surveyor::Models::QuestionGroupMethods
|
29
4
|
|
30
5
|
end
|
data/app/models/response.rb
CHANGED
@@ -1,37 +1,5 @@
|
|
1
1
|
class Response < ActiveRecord::Base
|
2
|
-
|
2
|
+
unloadable
|
3
3
|
include ActionView::Helpers::SanitizeHelper
|
4
|
-
|
5
|
-
# Associations
|
6
|
-
belongs_to :response_set
|
7
|
-
belongs_to :question
|
8
|
-
belongs_to :answer
|
9
|
-
|
10
|
-
# Validations
|
11
|
-
validates_presence_of :response_set_id, :question_id, :answer_id
|
12
|
-
|
13
|
-
acts_as_response # includes "as" instance method
|
14
|
-
|
15
|
-
def selected
|
16
|
-
!self.new_record?
|
17
|
-
end
|
18
|
-
|
19
|
-
alias_method :selected?, :selected
|
20
|
-
|
21
|
-
def selected=(value)
|
22
|
-
true
|
23
|
-
end
|
24
|
-
|
25
|
-
def correct?
|
26
|
-
question.correct_answer_id.nil? or self.answer.response_class != "answer" or (question.correct_answer_id.to_i == answer_id.to_i)
|
27
|
-
end
|
28
|
-
|
29
|
-
def to_s # used in dependency_explanation_helper
|
30
|
-
if self.answer.response_class == "answer" and self.answer_id
|
31
|
-
return self.answer.text
|
32
|
-
else
|
33
|
-
return "#{(self.string_value || self.text_value || self.integer_value || self.float_value || nil).to_s}"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
4
|
+
include Surveyor::Models::ResponseMethods
|
37
5
|
end
|
data/app/models/response_set.rb
CHANGED
@@ -1,223 +1,4 @@
|
|
1
1
|
class ResponseSet < ActiveRecord::Base
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
belongs_to :user
|
6
|
-
has_many :responses, :dependent => :destroy
|
7
|
-
|
8
|
-
# Validations
|
9
|
-
validates_presence_of :survey_id
|
10
|
-
validates_associated :responses
|
11
|
-
validates_uniqueness_of :access_code
|
12
|
-
|
13
|
-
# Attributes
|
14
|
-
attr_protected :completed_at
|
15
|
-
attr_accessor :current_section_id
|
16
|
-
|
17
|
-
# Callbacks
|
18
|
-
after_update :save_responses
|
19
|
-
|
20
|
-
# Instance methods
|
21
|
-
def initialize(*args)
|
22
|
-
super(*args)
|
23
|
-
default_args
|
24
|
-
end
|
25
|
-
|
26
|
-
def default_args
|
27
|
-
self.started_at ||= Time.now
|
28
|
-
self.access_code = Surveyor::Common.make_tiny_code
|
29
|
-
end
|
30
|
-
|
31
|
-
def access_code=(val)
|
32
|
-
while ResponseSet.find_by_access_code(val)
|
33
|
-
val = Surveyor::Common.make_tiny_code
|
34
|
-
end
|
35
|
-
super
|
36
|
-
end
|
37
|
-
|
38
|
-
def to_csv
|
39
|
-
qcols = Question.content_columns.map(&:name) - %w(created_at updated_at)
|
40
|
-
acols = Answer.content_columns.map(&:name) - %w(created_at updated_at)
|
41
|
-
rcols = Response.content_columns.map(&:name)
|
42
|
-
require 'fastercsv'
|
43
|
-
FCSV(result = "") do |csv|
|
44
|
-
csv << qcols.map{|qcol| "question.#{qcol}"} + acols.map{|acol| "answer.#{acol}"} + rcols.map{|rcol| "response.#{rcol}"}
|
45
|
-
responses.each do |response|
|
46
|
-
csv << qcols.map{|qcol| response.question.send(qcol)} + acols.map{|acol| response.answer.send(acol)} + rcols.map{|rcol| response.send(rcol)}
|
47
|
-
end
|
48
|
-
end
|
49
|
-
result
|
50
|
-
end
|
51
|
-
|
52
|
-
def response_for(question_id, answer_id, group = nil)
|
53
|
-
found = responses.detect{|r| r.question_id == question_id && r.answer_id == answer_id && r.response_group.to_s == group.to_s}
|
54
|
-
found.blank? ? responses.new(:question_id => question_id, :answer_id => answer_id, :response_group => group) : found
|
55
|
-
end
|
56
|
-
|
57
|
-
def clear_responses
|
58
|
-
question_ids = Question.find_all_by_survey_section_id(current_section_id).map(&:id)
|
59
|
-
responses.select{|r| question_ids.include? r.question_id}.map(&:destroy)
|
60
|
-
responses.reload
|
61
|
-
end
|
62
|
-
|
63
|
-
def response_attributes=(response_attributes)
|
64
|
-
response_attributes.each do |question_id, responses_hash|
|
65
|
-
# Response.delete_all(["response_set_id =? AND question_id =?", self.id, question_id])
|
66
|
-
if (answer_id = responses_hash[:answer_id])
|
67
|
-
if (!responses_hash[:answer_id].empty?) # Dropdowns return answer id but have an empty value if they are not set... ignoring those.
|
68
|
-
#radio or dropdown - only one response
|
69
|
-
responses.build({:question_id => question_id, :answer_id => answer_id, :survey_section_id => current_section_id}.merge(responses_hash[answer_id] || {}))
|
70
|
-
end
|
71
|
-
else
|
72
|
-
#possibly multiples responses - unresponded radios end up here too
|
73
|
-
# we use the variable question_id, not the "question_id" in the response_hash
|
74
|
-
responses_hash.delete_if{|k,v| k == "question_id"}.each do |answer_id, response_hash|
|
75
|
-
unless response_hash.delete_if{|k,v| v.blank?}.empty?
|
76
|
-
responses.build({:question_id => question_id, :answer_id => answer_id, :survey_section_id => current_section_id}.merge(response_hash))
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def response_group_attributes=(response_attributes)
|
84
|
-
response_attributes.each do |question_id, responses_group_hash|
|
85
|
-
# Response.delete_all(["response_set_id =? AND question_id =?", self.id, question_id])
|
86
|
-
responses_group_hash.each do |response_group_number, group_hash|
|
87
|
-
if (answer_id = group_hash[:answer_id]) # if group_hash has an answer_id key we treat it differently
|
88
|
-
if (!group_hash[:answer_id].empty?) # dropdowns return empty values in answer_ids if they are not selected
|
89
|
-
#radio or dropdown - only one response
|
90
|
-
responses.build({:question_id => question_id, :answer_id => answer_id, :response_group => response_group_number, :survey_section_id => current_section_id}.merge(group_hash[answer_id] || {}))
|
91
|
-
end
|
92
|
-
else
|
93
|
-
#possibly multiples responses - unresponded radios end up here too
|
94
|
-
# we use the variable question_id in the key, not the "question_id" in the response_hash... same with response_group key
|
95
|
-
group_hash.delete_if{|k,v| (k == "question_id") or (k == "response_group")}.each do |answer_id, inner_hash|
|
96
|
-
unless inner_hash.delete_if{|k,v| v.blank?}.empty?
|
97
|
-
responses.build({:question_id => question_id, :answer_id => answer_id, :response_group => response_group_number, :survey_section_id => current_section_id}.merge(inner_hash))
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def save_responses
|
107
|
-
responses.each{|response| response.save(false)}
|
108
|
-
end
|
109
|
-
|
110
|
-
def complete!
|
111
|
-
self.completed_at = Time.now
|
112
|
-
end
|
113
|
-
|
114
|
-
def correct?
|
115
|
-
responses.all?(&:correct?)
|
116
|
-
end
|
117
|
-
def correctness_hash
|
118
|
-
{ :questions => survey.sections_with_questions.map(&:questions).flatten.compact.size,
|
119
|
-
:responses => responses.compact.size,
|
120
|
-
:correct => responses.find_all(&:correct?).compact.size
|
121
|
-
}
|
122
|
-
end
|
123
|
-
def mandatory_questions_complete?
|
124
|
-
progress_hash[:triggered_mandatory] == progress_hash[:triggered_mandatory_completed]
|
125
|
-
end
|
126
|
-
def progress_hash
|
127
|
-
qs = survey.sections_with_questions.map(&:questions).flatten
|
128
|
-
ds = dependencies(qs.map(&:id))
|
129
|
-
triggered = qs - ds.select{|d| !d.is_met?(self)}.map(&:question)
|
130
|
-
{ :questions => qs.compact.size,
|
131
|
-
:triggered => triggered.compact.size,
|
132
|
-
:triggered_mandatory => triggered.select{|q| q.mandatory?}.compact.size,
|
133
|
-
:triggered_mandatory_completed => triggered.select{|q| q.mandatory? and is_answered?(q)}.compact.size
|
134
|
-
}
|
135
|
-
end
|
136
|
-
def is_answered?(question)
|
137
|
-
%w(label image).include?(question.display_type) or !is_unanswered?(question)
|
138
|
-
end
|
139
|
-
def is_unanswered?(question)
|
140
|
-
self.responses.detect{|r| r.question_id == question.id}.nil?
|
141
|
-
end
|
142
|
-
|
143
|
-
# Returns the number of response groups (count of group responses enterted) for this question group
|
144
|
-
def count_group_responses(questions)
|
145
|
-
questions.map{|q| responses.select{|r| (r.question_id.to_i == q.id.to_i) && !r.response_group.nil?}.group_by(&:response_group).size }.max
|
146
|
-
end
|
147
|
-
|
148
|
-
def unanswered_dependencies
|
149
|
-
dependencies.select{|d| d.is_met?(self) and self.is_unanswered?(d.question)}.map(&:question)
|
150
|
-
end
|
151
|
-
|
152
|
-
def all_dependencies
|
153
|
-
arr = dependencies.partition{|d| d.is_met?(self) }
|
154
|
-
{:show => arr[0].map{|d| d.question_group_id.nil? ? "question_#{d.question_id}" : "question_group_#{d.question_group_id}"}, :hide => arr[1].map{|d| d.question_group_id.nil? ? "question_#{d.question_id}" : "question_group_#{d.question_group_id}"}}
|
155
|
-
end
|
156
|
-
|
157
|
-
# Check existence of responses to questions from a given survey_section
|
158
|
-
def no_responses_for_section?(section)
|
159
|
-
self.responses.count(:conditions => {:survey_section_id => section.id}) == 0
|
160
|
-
end
|
161
|
-
|
162
|
-
protected
|
163
|
-
|
164
|
-
def dependencies(question_ids = nil)
|
165
|
-
question_ids ||= Question.find_all_by_survey_section_id(current_section_id).map(&:id)
|
166
|
-
depdendecy_ids = DependencyCondition.all(:conditions => {:question_id => question_ids}).map(&:dependency_id)
|
167
|
-
Dependency.find(depdendecy_ids, :include => :dependency_conditions)
|
168
|
-
end
|
169
|
-
|
170
|
-
end
|
171
|
-
|
172
|
-
# responses
|
173
|
-
|
174
|
-
# "responses"=>{
|
175
|
-
#string "6"=>{"question_id"=>"6", "20"=>{"string_value"=>"saf"}},
|
176
|
-
#text "7"=>{"question_id"=>"7", "21"=>{"text_value"=>""}},
|
177
|
-
#radio+txt "1"=>{"question_id"=>"1", "answer_id"=>"1", "4"=>{"string_value"=>""}},
|
178
|
-
#radio "2"=>{"answer_id"=>"6"},
|
179
|
-
#radio "3"=>{"answer_id"=>"10"},
|
180
|
-
#check "4"=>{"question_id"=>"4", "answer_id"=>"15"},
|
181
|
-
#check+txt "5"=>{"question_id"=>"5", "16"=>{"selected"=>"1"}, "19"=>{"string_value"=>""}}
|
182
|
-
# },
|
183
|
-
# "survey_code"=>"test_survey",
|
184
|
-
# "commit"=>"Next Section (Utensiles and you!) >>",
|
185
|
-
# "authenticity_token"=>"8bee21081eea820ab1c658358c0baaa2e46de5d1",
|
186
|
-
# "_method"=>"put",
|
187
|
-
# "action"=>"update",
|
188
|
-
# "controller"=>"app",
|
189
|
-
# "response_set_code"=>"T2x8HhCQej",
|
190
|
-
# "section"=>"2"
|
191
|
-
|
192
|
-
# response groups
|
193
|
-
|
194
|
-
# "24"=>{
|
195
|
-
# "0"=>{"response_group"=>"0", "question_id"=>"24", "answer_id"=>"172"}, "1"=>{"response_group"=>"1", "question_id"=>"24", "answer_id"=>"173"},
|
196
|
-
# "2"=>{"response_group"=>"2", "question_id"=>"24", "answer_id"=>""}, "3"=>{"response_group"=>"3", "question_id"=>"24", "answer_id"=>""},
|
197
|
-
# "4"=>{"response_group"=>"4", "question_id"=>"24", "answer_id"=>""}},
|
198
|
-
# where "24" is the question id
|
199
|
-
|
200
|
-
# Some other examples:
|
201
|
-
# "25"=>{
|
202
|
-
# "0"=>{"response_group"=>"0", "question_id"=>"25", "179"=>{"string_value"=>"camry"}},
|
203
|
-
# "1"=>{"response_group"=>"1", "question_id"=>"25", "179"=>{"string_value"=>"f150"}},
|
204
|
-
# "2"=>{"response_group"=>"2", "question_id"=>"25", "179"=>{"string_value"=>""}},
|
205
|
-
# "3"=>{"response_group"=>"3", "question_id"=>"25", "179"=>{"string_value"=>""}},
|
206
|
-
# "4"=>{"response_group"=>"4", "question_id"=>"25", "179"=>{"string_value"=>""}}},
|
207
|
-
#
|
208
|
-
# "26"=>{
|
209
|
-
# "0"=>{"response_group"=>"0", "question_id"=>"26", "180"=>{"string_value"=>"1999"}},
|
210
|
-
# "1"=>{"response_group"=>"1", "question_id"=>"26", "180"=>{"string_value"=>"2004"}},
|
211
|
-
# "2"=>{"response_group"=>"2", "question_id"=>"26", "180"=>{"string_value"=>""}},
|
212
|
-
# "3"=>{"response_group"=>"3", "question_id"=>"26", "180"=>{"string_value"=>""}},
|
213
|
-
# "4"=>{"response_group"=>"4", "question_id"=>"26", "180"=>{"string_value"=>""}}},
|
214
|
-
#
|
215
|
-
# "27"=>{
|
216
|
-
# "0"=>{"182"=>{"integer_value"=>""}, "response_group"=>"0", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
217
|
-
# "1"=>{"182"=>{"integer_value"=>""}, "response_group"=>"1", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
218
|
-
# "2"=>{"182"=>{"integer_value"=>""}, "response_group"=>"2", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
219
|
-
# "3"=>{"182"=>{"integer_value"=>""}, "response_group"=>"3", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
220
|
-
# "4"=>{"182"=>{"integer_value"=>""}, "response_group"=>"4", "question_id"=>"27", "181"=>{"string_value"=>""}}}},
|
221
|
-
|
222
|
-
# 0,1,2,3,4 are the response group numbers
|
223
|
-
# and anything else in the response group hash is handled normally
|
2
|
+
unloadable
|
3
|
+
include Surveyor::Models::ResponseSetMethods
|
4
|
+
end
|
data/app/models/survey.rb
CHANGED
@@ -1,57 +1,4 @@
|
|
1
1
|
class Survey < ActiveRecord::Base
|
2
|
-
|
3
|
-
|
4
|
-
has_many :sections, :class_name => "SurveySection", :order => 'display_order'
|
5
|
-
has_many :sections_with_questions, :include => :questions, :class_name => "SurveySection", :order => 'display_order'
|
6
|
-
has_many :response_sets
|
7
|
-
|
8
|
-
# Scopes
|
9
|
-
named_scope :with_sections, {:include => :sections}
|
10
|
-
|
11
|
-
# Validations
|
12
|
-
validates_presence_of :title
|
13
|
-
validates_uniqueness_of :access_code
|
14
|
-
|
15
|
-
# Class methods
|
16
|
-
def self.to_normalized_string(value)
|
17
|
-
# replace non-alphanumeric with "-". remove repeat "-"s. don't start or end with "-"
|
18
|
-
value.to_s.downcase.gsub(/[^a-z0-9]/,"-").gsub(/-+/,"-").gsub(/-$|^-/,"")
|
19
|
-
end
|
20
|
-
|
21
|
-
# Instance methods
|
22
|
-
def initialize(*args)
|
23
|
-
super(*args)
|
24
|
-
default_args
|
25
|
-
end
|
26
|
-
|
27
|
-
def default_args
|
28
|
-
self.inactive_at ||= DateTime.now
|
29
|
-
end
|
30
|
-
|
31
|
-
def title=(value)
|
32
|
-
self.access_code = Surveyor::Common.to_normalized_string(value)
|
33
|
-
super
|
34
|
-
end
|
35
|
-
|
36
|
-
def active?
|
37
|
-
self.active_as_of?(DateTime.now)
|
38
|
-
end
|
39
|
-
def active_as_of?(datetime)
|
40
|
-
(self.active_at.nil? or self.active_at < datetime) and (self.inactive_at.nil? or self.inactive_at > datetime)
|
41
|
-
end
|
42
|
-
def activate!
|
43
|
-
self.active_at = DateTime.now
|
44
|
-
end
|
45
|
-
def deactivate!
|
46
|
-
self.inactive_at = DateTime.now
|
47
|
-
end
|
48
|
-
def active_at=(datetime)
|
49
|
-
self.inactive_at = nil if !datetime.nil? and !self.inactive_at.nil? and self.inactive_at < datetime
|
50
|
-
super(datetime)
|
51
|
-
end
|
52
|
-
def inactive_at=(datetime)
|
53
|
-
self.active_at = nil if !datetime.nil? and !self.active_at.nil? and self.active_at > datetime
|
54
|
-
super(datetime)
|
55
|
-
end
|
56
|
-
|
2
|
+
unloadable
|
3
|
+
include Surveyor::Models::SurveyMethods
|
57
4
|
end
|
@@ -1,15 +1,5 @@
|
|
1
1
|
class SurveySection < ActiveRecord::Base
|
2
|
-
|
3
|
-
|
4
|
-
has_many :questions, :order => "display_order ASC"
|
5
|
-
belongs_to :survey
|
6
|
-
|
7
|
-
# Scopes
|
8
|
-
default_scope :order => "display_order ASC"
|
9
|
-
named_scope :with_includes, { :include => {:questions => [:answers, :question_group, {:dependency => :dependency_conditions}]}}
|
10
|
-
|
11
|
-
# Validations
|
12
|
-
validates_presence_of :title, :survey, :display_order
|
13
|
-
|
2
|
+
unloadable
|
3
|
+
include Surveyor::Models::SurveySectionMethods
|
14
4
|
end
|
15
5
|
|
data/app/models/validation.rb
CHANGED
@@ -1,34 +1,4 @@
|
|
1
1
|
class Validation < ActiveRecord::Base
|
2
|
-
|
3
|
-
|
4
|
-
belongs_to :answer
|
5
|
-
has_many :validation_conditions
|
6
|
-
|
7
|
-
# Scopes
|
8
|
-
|
9
|
-
# Validations
|
10
|
-
validates_presence_of :rule
|
11
|
-
validates_format_of :rule, :with => /^(?:and|or|\)|\(|[A-Z]|\s)+$/
|
12
|
-
validates_numericality_of :answer_id
|
13
|
-
|
14
|
-
# Instance Methods
|
15
|
-
def is_valid?(response_set)
|
16
|
-
ch = conditions_hash(response_set)
|
17
|
-
rgx = Regexp.new(self.validation_conditions.map{|vc| ["a","o"].include?(vc.rule_key) ? "#{vc.rule_key}(?!nd|r)" : vc.rule_key}.join("|")) # exclude and, or
|
18
|
-
# logger.debug "v: #{self.inspect}"
|
19
|
-
# logger.debug "rule: #{self.rule.inspect}"
|
20
|
-
# logger.debug "rexp: #{rgx.inspect}"
|
21
|
-
# logger.debug "keyp: #{ch.inspect}"
|
22
|
-
# logger.debug "subd: #{self.rule.gsub(rgx){|m| ch[m.to_sym]}}"
|
23
|
-
eval(self.rule.gsub(rgx){|m| ch[m.to_sym]})
|
24
|
-
end
|
25
|
-
|
26
|
-
# A hash of the conditions (keyed by rule_key) and their evaluation (boolean) in the context of response_set
|
27
|
-
def conditions_hash(response_set)
|
28
|
-
hash = {}
|
29
|
-
response = response_set.responses.detect{|r| r.answer_id.to_i == self.answer_id.to_i}
|
30
|
-
# logger.debug "r: #{response.inspect}"
|
31
|
-
self.validation_conditions.each{|vc| hash.merge!(vc.to_hash(response))}
|
32
|
-
return hash
|
33
|
-
end
|
2
|
+
unloadable
|
3
|
+
include Surveyor::Models::ValidationMethods
|
34
4
|
end
|
@@ -1,44 +1,4 @@
|
|
1
1
|
class ValidationCondition < ActiveRecord::Base
|
2
|
-
|
3
|
-
|
4
|
-
OPERATORS = %w(== != < > <= >= =~)
|
5
|
-
|
6
|
-
# Associations
|
7
|
-
belongs_to :validation
|
8
|
-
|
9
|
-
# Scopes
|
10
|
-
|
11
|
-
# Validations
|
12
|
-
validates_numericality_of :validation_id #, :question_id, :answer_id
|
13
|
-
validates_presence_of :operator, :rule_key
|
14
|
-
validates_inclusion_of :operator, :in => OPERATORS
|
15
|
-
validates_uniqueness_of :rule_key, :scope => :validation_id
|
16
|
-
|
17
|
-
acts_as_response # includes "as" instance method
|
18
|
-
|
19
|
-
# Class methods
|
20
|
-
def self.operators
|
21
|
-
OPERATORS
|
22
|
-
end
|
23
|
-
|
24
|
-
# Instance Methods
|
25
|
-
def to_hash(response)
|
26
|
-
{rule_key.to_sym => (response.nil? ? false : self.is_valid?(response))}
|
27
|
-
end
|
28
|
-
|
29
|
-
def is_valid?(response)
|
30
|
-
klass = response.answer.response_class
|
31
|
-
compare_to = Response.find_by_question_id_and_answer_id(self.question_id, self.answer_id) || self
|
32
|
-
case self.operator
|
33
|
-
when "==", "<", ">", "<=", ">="
|
34
|
-
response.as(klass).send(self.operator, compare_to.as(klass))
|
35
|
-
when "!="
|
36
|
-
!(response.as(klass) == compare_to.as(klass))
|
37
|
-
when "=~"
|
38
|
-
return false if compare_to != self
|
39
|
-
!(response.as(klass).to_s =~ Regexp.new(self.regexp || "")).nil?
|
40
|
-
else
|
41
|
-
false
|
42
|
-
end
|
43
|
-
end
|
2
|
+
unloadable
|
3
|
+
include Surveyor::Models::ValidationConditionMethods
|
44
4
|
end
|