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/lib/surveyor/common.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module AnswerMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :question
|
7
|
+
base.send :has_many, :responses
|
8
|
+
base.send :has_many, :validations, :dependent => :destroy
|
9
|
+
|
10
|
+
# Scopes
|
11
|
+
base.send :default_scope, :order => "display_order ASC"
|
12
|
+
|
13
|
+
# Validations
|
14
|
+
base.send :validates_presence_of, :text
|
15
|
+
base.send :validates_numericality_of, :question_id, :allow_nil => false, :only_integer => true
|
16
|
+
end
|
17
|
+
|
18
|
+
# Instance Methods
|
19
|
+
def renderer(q = question)
|
20
|
+
r = [q.pick.to_s, self.response_class].compact.map(&:downcase).join("_")
|
21
|
+
r.blank? ? :default : r.to_sym
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module DependencyConditionMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :answer
|
7
|
+
base.send :belongs_to, :dependency
|
8
|
+
base.send :belongs_to, :dependent_question, :foreign_key => :question_id, :class_name => :question
|
9
|
+
base.send :belongs_to, :question
|
10
|
+
|
11
|
+
# Validations
|
12
|
+
base.send :validates_numericality_of, :dependency_id, :question_id, :answer_id
|
13
|
+
base.send :validates_presence_of, :operator, :rule_key
|
14
|
+
base.send :validates_inclusion_of, :operator, :in => Surveyor::Common::OPERATORS
|
15
|
+
base.send :validates_uniqueness_of, :rule_key, :scope => :dependency_id
|
16
|
+
|
17
|
+
base.send :acts_as_response # includes "as" instance method
|
18
|
+
|
19
|
+
# Class methods
|
20
|
+
base.instance_eval do
|
21
|
+
def operators
|
22
|
+
Surveyor::Common::OPERATORS
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Instance methods
|
28
|
+
def to_hash(response_set)
|
29
|
+
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
|
30
|
+
{rule_key.to_sym => (response and self.is_met?(response))}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Checks to see if the response passed in meets the dependency condition
|
34
|
+
def is_met?(response)
|
35
|
+
klass = response.answer.response_class
|
36
|
+
return case self.operator
|
37
|
+
when "==", "<", ">", "<=", ">="
|
38
|
+
response.as(klass).send(self.operator, self.as(klass))
|
39
|
+
when "!="
|
40
|
+
!(response.as(klass) == self.as(klass))
|
41
|
+
else
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module DependencyMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :question
|
7
|
+
base.send :belongs_to, :question_group
|
8
|
+
base.send :has_many, :dependency_conditions, :dependent => :destroy
|
9
|
+
|
10
|
+
# Validations
|
11
|
+
base.send :validates_presence_of, :rule
|
12
|
+
base.send :validates_format_of, :rule, :with => /^(?:and|or|\)|\(|[A-Z]|\s)+$/ #TODO properly formed parenthesis etc.
|
13
|
+
base.send :validates_numericality_of, :question_id, :if => Proc.new { |d| d.question_group_id.nil? }
|
14
|
+
base.send :validates_numericality_of, :question_group_id, :if => Proc.new { |d| d.question_id.nil? }
|
15
|
+
|
16
|
+
# Attribute aliases
|
17
|
+
base.send :alias_attribute, :dependent_question_id, :question_id
|
18
|
+
end
|
19
|
+
|
20
|
+
# Instance Methods
|
21
|
+
def question_group_id=(i)
|
22
|
+
write_attribute(:question_id, nil) unless i.nil?
|
23
|
+
write_attribute(:question_group_id, i)
|
24
|
+
end
|
25
|
+
|
26
|
+
def question_id=(i)
|
27
|
+
write_attribute(:question_group_id, nil) unless i.nil?
|
28
|
+
write_attribute(:question_id, i)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Has this dependency has been met in the context of response_set?
|
32
|
+
# Substitutes the conditions hash into the rule and evaluates it
|
33
|
+
def is_met?(response_set)
|
34
|
+
ch = conditions_hash(response_set)
|
35
|
+
return false if ch.blank?
|
36
|
+
# logger.debug "rule: #{self.rule.inspect}"
|
37
|
+
# logger.debug "rexp: #{rgx.inspect}"
|
38
|
+
# logger.debug "keyp: #{ch.inspect}"
|
39
|
+
# logger.debug "subd: #{self.rule.gsub(rgx){|m| ch[m.to_sym]}}"
|
40
|
+
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
|
41
|
+
eval(self.rule.gsub(rgx){|m| ch[m.to_sym]})
|
42
|
+
end
|
43
|
+
|
44
|
+
# A hash of the conditions (keyed by rule_key) and their evaluation (boolean) in the context of response_set
|
45
|
+
def conditions_hash(response_set)
|
46
|
+
hash = {}
|
47
|
+
self.dependency_conditions.each{|dc| hash.merge!(dc.to_hash(response_set))}
|
48
|
+
return hash
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module QuestionGroupMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :has_many, :questions
|
7
|
+
base.send :has_one, :dependency
|
8
|
+
end
|
9
|
+
|
10
|
+
# Instance Methods
|
11
|
+
def initialize(*args)
|
12
|
+
super(*args)
|
13
|
+
default_args
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_args
|
17
|
+
self.display_type ||= "inline"
|
18
|
+
end
|
19
|
+
|
20
|
+
def renderer
|
21
|
+
display_type.blank? ? :default : display_type.to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
def dependent?
|
25
|
+
self.dependency != nil
|
26
|
+
end
|
27
|
+
def triggered?(response_set)
|
28
|
+
dependent? ? self.dependency.is_met?(response_set) : true
|
29
|
+
end
|
30
|
+
def css_class(response_set)
|
31
|
+
[(dependent? ? "dependent" : nil), (triggered?(response_set) ? nil : "hidden"), custom_class].compact.join(" ")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module QuestionMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :survey_section
|
7
|
+
base.send :belongs_to, :question_group, :dependent => :destroy
|
8
|
+
base.send :has_many, :answers, :order => "display_order ASC", :dependent => :destroy # it might not always have answers
|
9
|
+
base.send :has_one, :dependency, :dependent => :destroy
|
10
|
+
|
11
|
+
# Scopes
|
12
|
+
base.send :default_scope, :order => "display_order ASC"
|
13
|
+
|
14
|
+
# Validations
|
15
|
+
base.send :validates_presence_of, :text, :survey_section_id, :display_order
|
16
|
+
base.send :validates_inclusion_of, :is_mandatory, :in => [true, false]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Instance Methods
|
20
|
+
def initialize(*args)
|
21
|
+
super(*args)
|
22
|
+
default_args
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_args
|
26
|
+
self.is_mandatory ||= true
|
27
|
+
self.display_type ||= "default"
|
28
|
+
self.pick ||= "none"
|
29
|
+
end
|
30
|
+
|
31
|
+
def mandatory?
|
32
|
+
self.is_mandatory == true
|
33
|
+
end
|
34
|
+
|
35
|
+
def dependent?
|
36
|
+
self.dependency != nil
|
37
|
+
end
|
38
|
+
def triggered?(response_set)
|
39
|
+
dependent? ? self.dependency.is_met?(response_set) : true
|
40
|
+
end
|
41
|
+
def css_class(response_set)
|
42
|
+
[(dependent? ? "dependent" : nil), (triggered?(response_set) ? nil : "hidden"), custom_class].compact.join(" ")
|
43
|
+
end
|
44
|
+
|
45
|
+
def part_of_group?
|
46
|
+
!self.question_group.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
def renderer(g = question_group)
|
50
|
+
r = [g ? g.renderer.to_s : nil, display_type].compact.join("_")
|
51
|
+
r.blank? ? :default : r.to_sym
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module ResponseMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :response_set
|
7
|
+
base.send :belongs_to, :question
|
8
|
+
base.send :belongs_to, :answer
|
9
|
+
|
10
|
+
# Validations
|
11
|
+
base.send :validates_presence_of, :response_set_id, :question_id, :answer_id
|
12
|
+
|
13
|
+
base.send :acts_as_response # includes "as" instance method
|
14
|
+
end
|
15
|
+
|
16
|
+
# Instance Methods
|
17
|
+
def selected
|
18
|
+
!self.new_record?
|
19
|
+
end
|
20
|
+
|
21
|
+
alias_method :selected?, :selected
|
22
|
+
|
23
|
+
def selected=(value)
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def correct?
|
28
|
+
question.correct_answer_id.nil? or self.answer.response_class != "answer" or (question.correct_answer_id.to_i == answer_id.to_i)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s # used in dependency_explanation_helper
|
32
|
+
if self.answer.response_class == "answer" and self.answer_id
|
33
|
+
return self.answer.text
|
34
|
+
else
|
35
|
+
return "#{(self.string_value || self.text_value || self.integer_value || self.float_value || nil).to_s}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module ResponseSetMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :survey
|
7
|
+
base.send :belongs_to, :user
|
8
|
+
base.send :has_many, :responses, :dependent => :destroy
|
9
|
+
|
10
|
+
# Validations
|
11
|
+
base.send :validates_presence_of, :survey_id
|
12
|
+
base.send :validates_associated, :responses
|
13
|
+
base.send :validates_uniqueness_of, :access_code
|
14
|
+
|
15
|
+
# Attributes
|
16
|
+
base.send :attr_protected, :completed_at
|
17
|
+
base.send :attr_accessor, :current_section_id
|
18
|
+
|
19
|
+
# Callbacks
|
20
|
+
base.send :after_update, :save_responses
|
21
|
+
end
|
22
|
+
|
23
|
+
# Instance methods
|
24
|
+
def initialize(*args)
|
25
|
+
super(*args)
|
26
|
+
default_args
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_args
|
30
|
+
self.started_at ||= Time.now
|
31
|
+
self.access_code = Surveyor::Common.make_tiny_code
|
32
|
+
end
|
33
|
+
|
34
|
+
def access_code=(val)
|
35
|
+
while ResponseSet.find_by_access_code(val)
|
36
|
+
val = Surveyor::Common.make_tiny_code
|
37
|
+
end
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_csv
|
42
|
+
qcols = Question.content_columns.map(&:name) - %w(created_at updated_at)
|
43
|
+
acols = Answer.content_columns.map(&:name) - %w(created_at updated_at)
|
44
|
+
rcols = Response.content_columns.map(&:name)
|
45
|
+
require 'fastercsv'
|
46
|
+
FCSV(result = "") do |csv|
|
47
|
+
csv << qcols.map{|qcol| "question.#{qcol}"} + acols.map{|acol| "answer.#{acol}"} + rcols.map{|rcol| "response.#{rcol}"}
|
48
|
+
responses.each do |response|
|
49
|
+
csv << qcols.map{|qcol| response.question.send(qcol)} + acols.map{|acol| response.answer.send(acol)} + rcols.map{|rcol| response.send(rcol)}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
def response_for(question_id, answer_id, group = nil)
|
56
|
+
found = responses.detect{|r| r.question_id == question_id && r.answer_id == answer_id && r.response_group.to_s == group.to_s}
|
57
|
+
found.blank? ? responses.new(:question_id => question_id, :answer_id => answer_id, :response_group => group) : found
|
58
|
+
end
|
59
|
+
|
60
|
+
def clear_responses
|
61
|
+
question_ids = Question.find_all_by_survey_section_id(current_section_id).map(&:id)
|
62
|
+
responses.select{|r| question_ids.include? r.question_id}.map(&:destroy)
|
63
|
+
responses.reload
|
64
|
+
end
|
65
|
+
|
66
|
+
def response_attributes=(response_attributes)
|
67
|
+
response_attributes.each do |question_id, responses_hash|
|
68
|
+
# Response.delete_all(["response_set_id =? AND question_id =?", self.id, question_id])
|
69
|
+
if (answer_id = responses_hash[:answer_id])
|
70
|
+
if (!responses_hash[:answer_id].empty?) # Dropdowns return answer id but have an empty value if they are not set... ignoring those.
|
71
|
+
#radio or dropdown - only one response
|
72
|
+
responses.build({:question_id => question_id, :answer_id => answer_id, :survey_section_id => current_section_id}.merge(responses_hash[answer_id] || {}))
|
73
|
+
end
|
74
|
+
else
|
75
|
+
#possibly multiples responses - unresponded radios end up here too
|
76
|
+
# we use the variable question_id, not the "question_id" in the response_hash
|
77
|
+
responses_hash.delete_if{|k,v| k == "question_id"}.each do |answer_id, response_hash|
|
78
|
+
unless response_hash.delete_if{|k,v| v.blank?}.empty?
|
79
|
+
responses.build({:question_id => question_id, :answer_id => answer_id, :survey_section_id => current_section_id}.merge(response_hash))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def response_group_attributes=(response_attributes)
|
87
|
+
response_attributes.each do |question_id, responses_group_hash|
|
88
|
+
# Response.delete_all(["response_set_id =? AND question_id =?", self.id, question_id])
|
89
|
+
responses_group_hash.each do |response_group_number, group_hash|
|
90
|
+
if (answer_id = group_hash[:answer_id]) # if group_hash has an answer_id key we treat it differently
|
91
|
+
if (!group_hash[:answer_id].empty?) # dropdowns return empty values in answer_ids if they are not selected
|
92
|
+
#radio or dropdown - only one response
|
93
|
+
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] || {}))
|
94
|
+
end
|
95
|
+
else
|
96
|
+
#possibly multiples responses - unresponded radios end up here too
|
97
|
+
# we use the variable question_id in the key, not the "question_id" in the response_hash... same with response_group key
|
98
|
+
group_hash.delete_if{|k,v| (k == "question_id") or (k == "response_group")}.each do |answer_id, inner_hash|
|
99
|
+
unless inner_hash.delete_if{|k,v| v.blank?}.empty?
|
100
|
+
responses.build({:question_id => question_id, :answer_id => answer_id, :response_group => response_group_number, :survey_section_id => current_section_id}.merge(inner_hash))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def save_responses
|
110
|
+
responses.each{|response| response.save(false)}
|
111
|
+
end
|
112
|
+
|
113
|
+
def complete!
|
114
|
+
self.completed_at = Time.now
|
115
|
+
end
|
116
|
+
|
117
|
+
def correct?
|
118
|
+
responses.all?(&:correct?)
|
119
|
+
end
|
120
|
+
def correctness_hash
|
121
|
+
{ :questions => survey.sections_with_questions.map(&:questions).flatten.compact.size,
|
122
|
+
:responses => responses.compact.size,
|
123
|
+
:correct => responses.find_all(&:correct?).compact.size
|
124
|
+
}
|
125
|
+
end
|
126
|
+
def mandatory_questions_complete?
|
127
|
+
progress_hash[:triggered_mandatory] == progress_hash[:triggered_mandatory_completed]
|
128
|
+
end
|
129
|
+
def progress_hash
|
130
|
+
qs = survey.sections_with_questions.map(&:questions).flatten
|
131
|
+
ds = dependencies(qs.map(&:id))
|
132
|
+
triggered = qs - ds.select{|d| !d.is_met?(self)}.map(&:question)
|
133
|
+
{ :questions => qs.compact.size,
|
134
|
+
:triggered => triggered.compact.size,
|
135
|
+
:triggered_mandatory => triggered.select{|q| q.mandatory?}.compact.size,
|
136
|
+
:triggered_mandatory_completed => triggered.select{|q| q.mandatory? and is_answered?(q)}.compact.size
|
137
|
+
}
|
138
|
+
end
|
139
|
+
def is_answered?(question)
|
140
|
+
%w(label image).include?(question.display_type) or !is_unanswered?(question)
|
141
|
+
end
|
142
|
+
def is_unanswered?(question)
|
143
|
+
self.responses.detect{|r| r.question_id == question.id}.nil?
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns the number of response groups (count of group responses enterted) for this question group
|
147
|
+
def count_group_responses(questions)
|
148
|
+
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
|
149
|
+
end
|
150
|
+
|
151
|
+
def unanswered_dependencies
|
152
|
+
dependencies.select{|d| d.is_met?(self) and self.is_unanswered?(d.question)}.map(&:question)
|
153
|
+
end
|
154
|
+
|
155
|
+
def all_dependencies
|
156
|
+
arr = dependencies.partition{|d| d.is_met?(self) }
|
157
|
+
{: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}"}}
|
158
|
+
end
|
159
|
+
|
160
|
+
# Check existence of responses to questions from a given survey_section
|
161
|
+
def no_responses_for_section?(section)
|
162
|
+
self.responses.count(:conditions => {:survey_section_id => section.id}) == 0
|
163
|
+
end
|
164
|
+
|
165
|
+
protected
|
166
|
+
|
167
|
+
def dependencies(question_ids = nil)
|
168
|
+
question_ids ||= Question.find_all_by_survey_section_id(current_section_id).map(&:id)
|
169
|
+
depdendecy_ids = DependencyCondition.all(:conditions => {:question_id => question_ids}).map(&:dependency_id)
|
170
|
+
Dependency.find(depdendecy_ids, :include => :dependency_conditions)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module SurveyMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :has_many, :sections, :class_name => "SurveySection", :order => 'display_order'
|
7
|
+
base.send :has_many, :sections_with_questions, :include => :questions, :class_name => "SurveySection", :order => 'display_order'
|
8
|
+
base.send :has_many, :response_sets
|
9
|
+
|
10
|
+
# Scopes
|
11
|
+
base.send :named_scope, :with_sections, {:include => :sections}
|
12
|
+
|
13
|
+
# Validations
|
14
|
+
base.send :validates_presence_of, :title
|
15
|
+
base.send :validates_uniqueness_of, :access_code
|
16
|
+
|
17
|
+
# Class methods
|
18
|
+
base.instance_eval do
|
19
|
+
def to_normalized_string(value)
|
20
|
+
# replace non-alphanumeric with "-". remove repeat "-"s. don't start or end with "-"
|
21
|
+
value.to_s.downcase.gsub(/[^a-z0-9]/,"-").gsub(/-+/,"-").gsub(/-$|^-/,"")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Instance methods
|
27
|
+
def initialize(*args)
|
28
|
+
super(*args)
|
29
|
+
default_args
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_args
|
33
|
+
self.inactive_at ||= DateTime.now
|
34
|
+
end
|
35
|
+
|
36
|
+
def title=(value)
|
37
|
+
self.access_code = Surveyor::Common.to_normalized_string(value)
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
def active?
|
42
|
+
self.active_as_of?(DateTime.now)
|
43
|
+
end
|
44
|
+
def active_as_of?(datetime)
|
45
|
+
(self.active_at.nil? or self.active_at < datetime) and (self.inactive_at.nil? or self.inactive_at > datetime)
|
46
|
+
end
|
47
|
+
def activate!
|
48
|
+
self.active_at = DateTime.now
|
49
|
+
end
|
50
|
+
def deactivate!
|
51
|
+
self.inactive_at = DateTime.now
|
52
|
+
end
|
53
|
+
def active_at=(datetime)
|
54
|
+
self.inactive_at = nil if !datetime.nil? and !self.inactive_at.nil? and self.inactive_at < datetime
|
55
|
+
super(datetime)
|
56
|
+
end
|
57
|
+
def inactive_at=(datetime)
|
58
|
+
self.active_at = nil if !datetime.nil? and !self.active_at.nil? and self.active_at > datetime
|
59
|
+
super(datetime)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module SurveySectionMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :has_many, :questions, :order => "display_order ASC", :dependent => :destroy
|
7
|
+
base.send :belongs_to, :survey
|
8
|
+
|
9
|
+
# Scopes
|
10
|
+
base.send :default_scope, :order => "display_order ASC"
|
11
|
+
base.send :named_scope, :with_includes, { :include => {:questions => [:answers, :question_group, {:dependency => :dependency_conditions}]}}
|
12
|
+
|
13
|
+
# Validations
|
14
|
+
base.send :validates_presence_of, :title, :survey, :display_order
|
15
|
+
end
|
16
|
+
|
17
|
+
# Instance Methods
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module ValidationConditionMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :validation
|
7
|
+
|
8
|
+
# Scopes
|
9
|
+
|
10
|
+
# Validations
|
11
|
+
base.send :validates_numericality_of, :validation_id #, :question_id, :answer_id
|
12
|
+
base.send :validates_presence_of, :operator, :rule_key
|
13
|
+
base.send :validates_inclusion_of, :operator, :in => Surveyor::Common::OPERATORS
|
14
|
+
base.send :validates_uniqueness_of, :rule_key, :scope => :validation_id
|
15
|
+
|
16
|
+
base.send :acts_as_response # includes "as" instance method
|
17
|
+
|
18
|
+
# Class methods
|
19
|
+
base.instance_eval do
|
20
|
+
def operators
|
21
|
+
Surveyor::Common::OPERATORS
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Instance Methods
|
27
|
+
def to_hash(response)
|
28
|
+
{rule_key.to_sym => (response.nil? ? false : self.is_valid?(response))}
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_valid?(response)
|
32
|
+
klass = response.answer.response_class
|
33
|
+
compare_to = Response.find_by_question_id_and_answer_id(self.question_id, self.answer_id) || self
|
34
|
+
case self.operator
|
35
|
+
when "==", "<", ">", "<=", ">="
|
36
|
+
response.as(klass).send(self.operator, compare_to.as(klass))
|
37
|
+
when "!="
|
38
|
+
!(response.as(klass) == compare_to.as(klass))
|
39
|
+
when "=~"
|
40
|
+
return false if compare_to != self
|
41
|
+
!(response.as(klass).to_s =~ Regexp.new(self.regexp || "")).nil?
|
42
|
+
else
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Surveyor
|
2
|
+
module Models
|
3
|
+
module ValidationMethods
|
4
|
+
def self.included(base)
|
5
|
+
# Associations
|
6
|
+
base.send :belongs_to, :answer
|
7
|
+
base.send :has_many, :validation_conditions, :dependent => :destroy
|
8
|
+
|
9
|
+
# Scopes
|
10
|
+
|
11
|
+
# Validations
|
12
|
+
base.send :validates_presence_of, :rule
|
13
|
+
base.send :validates_format_of, :rule, :with => /^(?:and|or|\)|\(|[A-Z]|\s)+$/
|
14
|
+
base.send :validates_numericality_of, :answer_id
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
# Instance Methods
|
19
|
+
def is_valid?(response_set)
|
20
|
+
ch = conditions_hash(response_set)
|
21
|
+
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
|
22
|
+
# logger.debug "v: #{self.inspect}"
|
23
|
+
# logger.debug "rule: #{self.rule.inspect}"
|
24
|
+
# logger.debug "rexp: #{rgx.inspect}"
|
25
|
+
# logger.debug "keyp: #{ch.inspect}"
|
26
|
+
# logger.debug "subd: #{self.rule.gsub(rgx){|m| ch[m.to_sym]}}"
|
27
|
+
eval(self.rule.gsub(rgx){|m| ch[m.to_sym]})
|
28
|
+
end
|
29
|
+
|
30
|
+
# A hash of the conditions (keyed by rule_key) and their evaluation (boolean) in the context of response_set
|
31
|
+
def conditions_hash(response_set)
|
32
|
+
hash = {}
|
33
|
+
response = response_set.responses.detect{|r| r.answer_id.to_i == self.answer_id.to_i}
|
34
|
+
# logger.debug "r: #{response.inspect}"
|
35
|
+
self.validation_conditions.each{|vc| hash.merge!(vc.to_hash(response))}
|
36
|
+
return hash
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/surveyor.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'surveyor/common'
|
2
2
|
require 'surveyor/acts_as_response'
|
3
3
|
|
4
|
-
|
5
|
-
Dir.glob(File.join(File.dirname(__FILE__),'..','app','models','*.rb')).each{|f| require f}
|
6
|
-
Dir.glob(File.join(File.dirname(__FILE__),'..','app','helpers','*.rb')).each{|f| require f}
|
4
|
+
#
|
5
|
+
# Dir.glob(File.join(File.dirname(__FILE__),'..','app','models','*.rb')).each{|f| require f}
|
6
|
+
# Dir.glob(File.join(File.dirname(__FILE__),'..','app','helpers','*.rb')).each{|f| require f}
|
data/rails/init.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
require 'surveyor'
|
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.14.
|
8
|
+
s.version = "0.14.5"
|
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-
|
12
|
+
s.date = %q{2010-09-01}
|
13
13
|
s.email = %q{yoon@northwestern.edu}
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"README.md"
|
@@ -56,11 +56,8 @@ Gem::Specification.new do |s|
|
|
56
56
|
"features/surveyor.feature",
|
57
57
|
"generators/extend_surveyor/extend_surveyor_generator.rb",
|
58
58
|
"generators/extend_surveyor/templates/EXTENDING_SURVEYOR",
|
59
|
-
"generators/extend_surveyor/templates/extensions/survey_extensions.rb",
|
60
59
|
"generators/extend_surveyor/templates/extensions/surveyor_controller.rb",
|
61
|
-
"generators/extend_surveyor/templates/extensions/surveyor_controller_extensions.rb",
|
62
60
|
"generators/extend_surveyor/templates/extensions/surveyor_custom.html.erb",
|
63
|
-
"generators/extend_surveyor/templates/extensions/surveyor_helper_extensions.rb",
|
64
61
|
"generators/surveyor/surveyor_generator.rb",
|
65
62
|
"generators/surveyor/templates/README",
|
66
63
|
"generators/surveyor/templates/assets/images/222222_11x11_icon_arrows_leftright.gif",
|
@@ -145,6 +142,17 @@ Gem::Specification.new do |s|
|
|
145
142
|
"lib/surveyor.rb",
|
146
143
|
"lib/surveyor/acts_as_response.rb",
|
147
144
|
"lib/surveyor/common.rb",
|
145
|
+
"lib/surveyor/models/answer_methods.rb",
|
146
|
+
"lib/surveyor/models/dependency_condition_methods.rb",
|
147
|
+
"lib/surveyor/models/dependency_methods.rb",
|
148
|
+
"lib/surveyor/models/question_group_methods.rb",
|
149
|
+
"lib/surveyor/models/question_methods.rb",
|
150
|
+
"lib/surveyor/models/response_methods.rb",
|
151
|
+
"lib/surveyor/models/response_set_methods.rb",
|
152
|
+
"lib/surveyor/models/survey_methods.rb",
|
153
|
+
"lib/surveyor/models/survey_section_methods.rb",
|
154
|
+
"lib/surveyor/models/validation_condition_methods.rb",
|
155
|
+
"lib/surveyor/models/validation_methods.rb",
|
148
156
|
"lib/surveyor/surveyor_controller_methods.rb",
|
149
157
|
"lib/tasks/surveyor_tasks.rake",
|
150
158
|
"rails/init.rb",
|