surveyor 0.14.4 → 0.14.5
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.
- 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",
|