surveyor 0.6.1
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/.gitignore +6 -0
- data/MIT-LICENSE +20 -0
- data/README.md +104 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/app/controllers/surveyor_controller.rb +117 -0
- data/app/helpers/survey_form_builder.rb +37 -0
- data/app/helpers/surveyor_helper.rb +66 -0
- data/app/models/answer.rb +21 -0
- data/app/models/dependency.rb +46 -0
- data/app/models/dependency_condition.rb +79 -0
- data/app/models/question.rb +54 -0
- data/app/models/question_group.rb +18 -0
- data/app/models/response.rb +47 -0
- data/app/models/response_set.rb +185 -0
- data/app/models/survey.rb +57 -0
- data/app/models/survey_section.rb +15 -0
- data/app/views/layouts/surveyor_default.html.erb +13 -0
- data/app/views/partials/_answer.html.haml +49 -0
- data/app/views/partials/_question.html.haml +73 -0
- data/app/views/partials/_question_group.html.haml +41 -0
- data/app/views/surveyor/edit.html.haml +32 -0
- data/app/views/surveyor/new.html.haml +17 -0
- data/app/views/surveyor/show.html.haml +12 -0
- data/config/routes.rb +10 -0
- data/generators/surveyor/EXTENDING_SURVEYOR +12 -0
- data/generators/surveyor/extend_surveyor_generator.rb +22 -0
- data/generators/surveyor/surveyor_generator.rb +60 -0
- data/generators/surveyor/templates/README +10 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_arrows_leftright.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_arrows_updown.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_close.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_doc.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_folder_closed.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_folder_open.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_minus.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_plus.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_11x11_icon_resize_se.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_35x9_colorpicker_indicator.gif.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_7x7_arrow_down.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_7x7_arrow_left.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_7x7_arrow_right.gif +0 -0
- data/generators/surveyor/templates/assets/images/222222_7x7_arrow_up.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_arrows_leftright.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_arrows_updown.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_close.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_doc.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_folder_closed.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_folder_open.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_minus.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_11x11_icon_plus.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_7x7_arrow_down.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_7x7_arrow_left.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_7x7_arrow_right.gif +0 -0
- data/generators/surveyor/templates/assets/images/454545_7x7_arrow_up.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_arrows_leftright.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_arrows_updown.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_close.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_doc.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_folder_closed.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_folder_open.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_minus.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_11x11_icon_plus.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_7x7_arrow_down.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_7x7_arrow_left.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_7x7_arrow_right.gif +0 -0
- data/generators/surveyor/templates/assets/images/888888_7x7_arrow_up.gif +0 -0
- data/generators/surveyor/templates/assets/images/dadada_40x100_textures_02_glass_75.png +0 -0
- data/generators/surveyor/templates/assets/images/e6e6e6_40x100_textures_02_glass_75.png +0 -0
- data/generators/surveyor/templates/assets/images/ffffff_40x100_textures_01_flat_0.png +0 -0
- data/generators/surveyor/templates/assets/images/ffffff_40x100_textures_02_glass_65.png +0 -0
- data/generators/surveyor/templates/assets/javascripts/accessibleUISlider.jQuery.js +201 -0
- data/generators/surveyor/templates/assets/javascripts/jquery-1.2.6.js +3549 -0
- data/generators/surveyor/templates/assets/javascripts/jquery-ui-personalized-1.5.3.js +7616 -0
- data/generators/surveyor/templates/assets/javascripts/jquery.form.js +637 -0
- data/generators/surveyor/templates/assets/javascripts/surveyor.js +28 -0
- data/generators/surveyor/templates/assets/stylesheets/jquery-ui-slider-additions.css +71 -0
- data/generators/surveyor/templates/assets/stylesheets/reset.css +46 -0
- data/generators/surveyor/templates/assets/stylesheets/sass/surveyor.sass +245 -0
- data/generators/surveyor/templates/assets/stylesheets/surveyor.css +231 -0
- data/generators/surveyor/templates/assets/stylesheets/ui.theme.css +851 -0
- data/generators/surveyor/templates/extensions/survey_extensions.rb +18 -0
- data/generators/surveyor/templates/extensions/surveyor_controller_extensions.rb +28 -0
- data/generators/surveyor/templates/extensions/surveyor_custom.html.erb +13 -0
- data/generators/surveyor/templates/extensions/surveyor_helper_extensions.rb +18 -0
- data/generators/surveyor/templates/initializers/haml.rb +8 -0
- data/generators/surveyor/templates/initializers/surveyor.rb +11 -0
- data/generators/surveyor/templates/migrate/create_answers.rb +45 -0
- data/generators/surveyor/templates/migrate/create_dependencies.rb +21 -0
- data/generators/surveyor/templates/migrate/create_dependency_conditions.rb +29 -0
- data/generators/surveyor/templates/migrate/create_question_groups.rb +18 -0
- data/generators/surveyor/templates/migrate/create_questions.rb +33 -0
- data/generators/surveyor/templates/migrate/create_response_sets.rb +22 -0
- data/generators/surveyor/templates/migrate/create_responses.rb +33 -0
- data/generators/surveyor/templates/migrate/create_survey_sections.rb +25 -0
- data/generators/surveyor/templates/migrate/create_surveys.rb +25 -0
- data/generators/surveyor/templates/surveys/kitchen_sink_survey.rb +201 -0
- data/generators/surveyor/templates/tasks/surveyor.rb +4 -0
- data/init.rb +1 -0
- data/install.rb +1 -0
- data/lib/surveyor.rb +7 -0
- data/lib/surveyor/config.rb +45 -0
- data/lib/tasks/surveyor_tasks.rake +33 -0
- data/lib/tiny_code.rb +58 -0
- data/lib/xml_formatter.rb +12 -0
- data/script/surveyor/answer.rb +84 -0
- data/script/surveyor/columnizer.rb +36 -0
- data/script/surveyor/dependency.rb +43 -0
- data/script/surveyor/dependency_condition.rb +74 -0
- data/script/surveyor/question.rb +76 -0
- data/script/surveyor/question_group.rb +33 -0
- data/script/surveyor/specs/answer_spec.rb +66 -0
- data/script/surveyor/specs/question_dependency_spec.rb +46 -0
- data/script/surveyor/specs/question_group_spec.rb +9 -0
- data/script/surveyor/specs/question_spec.rb +111 -0
- data/script/surveyor/specs/section_spec.rb +58 -0
- data/script/surveyor/survey.rb +108 -0
- data/script/surveyor/survey_parser.rb +64 -0
- data/script/surveyor/survey_section.rb +153 -0
- data/script/surveyor/whr_dsl.tmproj +244 -0
- data/spec/controllers/surveyor_controller_spec.rb +328 -0
- data/spec/factories.rb +107 -0
- data/spec/models/answer_spec.rb +29 -0
- data/spec/models/dependency_condition_spec.rb +338 -0
- data/spec/models/dependency_spec.rb +82 -0
- data/spec/models/question_group_spec.rb +11 -0
- data/spec/models/question_spec.rb +75 -0
- data/spec/models/response_set_spec.rb +153 -0
- data/spec/models/response_spec.rb +94 -0
- data/spec/models/survey_section_spec.rb +34 -0
- data/spec/models/survey_spec.rb +72 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +14 -0
- data/surveyor.gemspec +187 -0
- data/uninstall.rb +1 -0
- metadata +211 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
class DependencyCondition < ActiveRecord::Base
|
|
2
|
+
# Constants
|
|
3
|
+
OPERATORS = %w(== != < > <= >=) # CONSTANT or @@class_variable when validations listed before class method
|
|
4
|
+
|
|
5
|
+
# Associations
|
|
6
|
+
belongs_to :answer
|
|
7
|
+
belongs_to :dependency
|
|
8
|
+
belongs_to :dependent_question, :foreign_key => :question_id, :class_name => :question
|
|
9
|
+
|
|
10
|
+
# Validations
|
|
11
|
+
validates_numericality_of :dependency_id, :question_id, :answer_id
|
|
12
|
+
validates_presence_of :operator, :rule_key
|
|
13
|
+
validates_inclusion_of :operator, :in => OPERATORS
|
|
14
|
+
validates_uniqueness_of :rule_key, :scope => :dependency_id
|
|
15
|
+
|
|
16
|
+
# Class methods
|
|
17
|
+
def self.operators
|
|
18
|
+
OPERATORS
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Instance methods
|
|
22
|
+
|
|
23
|
+
# The hash used in the dependency parent object to evaluate its rule string
|
|
24
|
+
def to_evaluation_hash(response_set)
|
|
25
|
+
x = {rule_key.to_sym => self.evaluation_of(response_set)}
|
|
26
|
+
# logger.warn "to_evaluation_hash #{x.inspect}"
|
|
27
|
+
return x
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# # Evaluates the condition on the response_set
|
|
31
|
+
# def evaluation_of(response_set)
|
|
32
|
+
# response = response_set.responses.detect{|r| r.answer_id == answer_id} || false # turns out eval("nil and false") => nil so we need to return false if no response is found
|
|
33
|
+
# return(response and self.is_satisfied_by?(response))
|
|
34
|
+
# end
|
|
35
|
+
# Evaluates the condition on the response_set
|
|
36
|
+
def evaluation_of(response_set)
|
|
37
|
+
# response = response_set.find_response(self.answer_id) || false # turns out eval("nil and false") => nil so we need to return false if no response is found
|
|
38
|
+
response = response_set.responses.detect{|r| r.answer_id.to_i == self.answer_id.to_i} || false # turns out eval("nil and false") => nil so we need to return false if no response is found
|
|
39
|
+
# logger.warn "evaluation_of_response #{response.inspect}"
|
|
40
|
+
return(response and self.is_satisfied_by?(response))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Checks to see if the response passed in satisfies the dependency condition
|
|
44
|
+
def is_satisfied_by?(response)
|
|
45
|
+
response_class = response.answer.response_class
|
|
46
|
+
# response.as(response_class).send(operator.to_sym, self.as(response_class))
|
|
47
|
+
return case self.operator
|
|
48
|
+
when "=="
|
|
49
|
+
response.as(response_class) == self.as(response_class)
|
|
50
|
+
when "!="
|
|
51
|
+
response.as(response_class) != self.as(response_class)
|
|
52
|
+
when "<"
|
|
53
|
+
response.as(response_class) < self.as(response_class)
|
|
54
|
+
when ">"
|
|
55
|
+
response.as(response_class) > self.as(response_class)
|
|
56
|
+
when "<="
|
|
57
|
+
response.as(response_class) <= self.as(response_class)
|
|
58
|
+
when ">="
|
|
59
|
+
response.as(response_class) >= self.as(response_class)
|
|
60
|
+
else
|
|
61
|
+
false
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Method that returns the dependency as a particular response_class type
|
|
66
|
+
def as(type_symbol)
|
|
67
|
+
return case type_symbol.to_sym
|
|
68
|
+
when :string, :text, :integer, :float, :datetime
|
|
69
|
+
self.send("#{type_symbol}_value".to_sym)
|
|
70
|
+
when :date
|
|
71
|
+
self.datetime_value.nil? ? nil : self.datetime_value.to_date
|
|
72
|
+
when :time
|
|
73
|
+
self.datetime_value.nil? ? nil : self.datetime_value.to_time
|
|
74
|
+
else # :answer_id
|
|
75
|
+
self.answer_id
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
class Question < ActiveRecord::Base
|
|
2
|
+
|
|
3
|
+
# Associations
|
|
4
|
+
belongs_to :survey_section
|
|
5
|
+
belongs_to :question_group
|
|
6
|
+
has_many :answers # 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.met?(response_set) : true
|
|
37
|
+
end
|
|
38
|
+
def dep_class(response_set)
|
|
39
|
+
dependent? ? triggered?(response_set) ? "dependent" : "hidden dependent" : nil
|
|
40
|
+
end
|
|
41
|
+
def dependency_satisfied?(response_set)
|
|
42
|
+
self.has_dependency? and self.dependency.met?(response_set)
|
|
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
|
+
|
|
54
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class QuestionGroup < ActiveRecord::Base
|
|
2
|
+
|
|
3
|
+
has_many :questions
|
|
4
|
+
|
|
5
|
+
# Instance Methods
|
|
6
|
+
def initialize(*args)
|
|
7
|
+
super(*args)
|
|
8
|
+
default_args
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def default_args
|
|
12
|
+
self.display_type = "inline"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def renderer
|
|
16
|
+
display_type.blank? ? :default : display_type.to_sym
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class Response < ActiveRecord::Base
|
|
2
|
+
include ActionView::Helpers::SanitizeHelper
|
|
3
|
+
|
|
4
|
+
# Associations
|
|
5
|
+
belongs_to :response_set
|
|
6
|
+
belongs_to :question
|
|
7
|
+
belongs_to :answer
|
|
8
|
+
|
|
9
|
+
# Validations
|
|
10
|
+
validates_presence_of :response_set_id, :question_id, :answer_id
|
|
11
|
+
|
|
12
|
+
# Named scopes
|
|
13
|
+
named_scope :in_section, lambda {|section_id| {:include => :question, :conditions => ['questions.survey_section_id =?', section_id.to_i ]}}
|
|
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
|
+
#Method that returns the response as a particular response_class type
|
|
26
|
+
def as(type_symbol)
|
|
27
|
+
return case type_symbol.to_sym
|
|
28
|
+
when :string, :text, :integer, :float, :datetime
|
|
29
|
+
self.send("#{type_symbol}_value".to_sym)
|
|
30
|
+
when :date
|
|
31
|
+
self.datetime_value.nil? ? nil : self.datetime_value.to_date
|
|
32
|
+
when :time
|
|
33
|
+
self.datetime_value.nil? ? nil : self.datetime_value.to_time
|
|
34
|
+
else # :answer_id
|
|
35
|
+
self.answer_id
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_s
|
|
40
|
+
if self.answer.response_class == "answer" and self.answer_id
|
|
41
|
+
return self.answer.text
|
|
42
|
+
else
|
|
43
|
+
return "#{(self.string_value || self.text_value || self.integer_value || self.float_value || nil).to_s}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
class ResponseSet < ActiveRecord::Base
|
|
2
|
+
include TinyCode
|
|
3
|
+
|
|
4
|
+
# Associations
|
|
5
|
+
belongs_to :survey
|
|
6
|
+
belongs_to :user
|
|
7
|
+
has_many :responses, :dependent => :destroy
|
|
8
|
+
|
|
9
|
+
# Validations
|
|
10
|
+
validates_presence_of :survey_id
|
|
11
|
+
validates_associated :responses
|
|
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 = ResponseSet.make_tiny_code
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def response_for(question_id, answer_id, group = nil)
|
|
32
|
+
response_group = group.nil? ? nil : group.to_s # for PostgreSql 8.3 compatibility - response_group is a string
|
|
33
|
+
# found = responses.find_by_question_id_and_answer_id_and_response_group(question_id, answer_id, response_group)
|
|
34
|
+
found = responses.detect{|r| r.question_id == question_id && r.answer_id == answer_id && r.response_group == response_group}
|
|
35
|
+
found.blank? ? responses.new(:question_id => question_id, :answer_id => answer_id, :response_group => response_group) : found
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def clear_responses
|
|
39
|
+
question_ids = Question.find_all_by_survey_section_id(current_section_id).map(&:id)
|
|
40
|
+
# logger.warn "responses: #{responses.inspect}\n"
|
|
41
|
+
responses.select{|r| question_ids.include? r.question_id}.map(&:destroy)
|
|
42
|
+
responses.reload
|
|
43
|
+
# logger.warn "responses: #{responses.inspect}\n"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# "responses"=>{
|
|
47
|
+
#string "6"=>{"question_id"=>"6", "20"=>{"string_value"=>"saf"}},
|
|
48
|
+
#text "7"=>{"question_id"=>"7", "21"=>{"text_value"=>""}},
|
|
49
|
+
#radio+txt "1"=>{"question_id"=>"1", "answer_id"=>"1", "4"=>{"string_value"=>""}},
|
|
50
|
+
#radio "2"=>{"answer_id"=>"6"},
|
|
51
|
+
#radio "3"=>{"answer_id"=>"10"},
|
|
52
|
+
#check "4"=>{"question_id"=>"4", "answer_id"=>"15"},
|
|
53
|
+
#check+txt "5"=>{"question_id"=>"5", "16"=>{"selected"=>"1"}, "19"=>{"string_value"=>""}}
|
|
54
|
+
# },
|
|
55
|
+
# "survey_code"=>"test_survey",
|
|
56
|
+
# "commit"=>"Next Section (Utensiles and you!) >>",
|
|
57
|
+
# "authenticity_token"=>"8bee21081eea820ab1c658358c0baaa2e46de5d1",
|
|
58
|
+
# "_method"=>"put",
|
|
59
|
+
# "action"=>"update",
|
|
60
|
+
# "controller"=>"app",
|
|
61
|
+
# "response_set_code"=>"T2x8HhCQej",
|
|
62
|
+
# "section"=>"2"
|
|
63
|
+
|
|
64
|
+
def response_attributes=(response_attributes)
|
|
65
|
+
response_attributes.each do |question_id, responses_hash|
|
|
66
|
+
# Response.delete_all(["response_set_id =? AND question_id =?", self.id, question_id])
|
|
67
|
+
if (answer_id = responses_hash[:answer_id])
|
|
68
|
+
if (!responses_hash[:answer_id].empty?) # Dropdowns return answer id but have an empty value if they are not set... ignoring those.
|
|
69
|
+
#radio or dropdown - only one response
|
|
70
|
+
responses.build({:question_id => question_id, :answer_id => answer_id}.merge(responses_hash[answer_id] || {}))
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
#possibly multiples responses - unresponded radios end up here too
|
|
74
|
+
# we use the variable question_id, not the "question_id" in the response_hash
|
|
75
|
+
responses_hash.delete_if{|k,v| k == "question_id"}.each do |answer_id, response_hash|
|
|
76
|
+
unless response_hash.delete_if{|k,v| v.blank?}.empty?
|
|
77
|
+
responses.build({:question_id => question_id, :answer_id => answer_id}.merge(response_hash))
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# "24"=>{
|
|
85
|
+
# "0"=>{"response_group"=>"0", "question_id"=>"24", "answer_id"=>"172"}, "1"=>{"response_group"=>"1", "question_id"=>"24", "answer_id"=>"173"},
|
|
86
|
+
# "2"=>{"response_group"=>"2", "question_id"=>"24", "answer_id"=>""}, "3"=>{"response_group"=>"3", "question_id"=>"24", "answer_id"=>""},
|
|
87
|
+
# "4"=>{"response_group"=>"4", "question_id"=>"24", "answer_id"=>""}},
|
|
88
|
+
# where "24" is the question id
|
|
89
|
+
|
|
90
|
+
# Some other examples:
|
|
91
|
+
# "25"=>{
|
|
92
|
+
# "0"=>{"response_group"=>"0", "question_id"=>"25", "179"=>{"string_value"=>"camry"}},
|
|
93
|
+
# "1"=>{"response_group"=>"1", "question_id"=>"25", "179"=>{"string_value"=>"f150"}},
|
|
94
|
+
# "2"=>{"response_group"=>"2", "question_id"=>"25", "179"=>{"string_value"=>""}},
|
|
95
|
+
# "3"=>{"response_group"=>"3", "question_id"=>"25", "179"=>{"string_value"=>""}},
|
|
96
|
+
# "4"=>{"response_group"=>"4", "question_id"=>"25", "179"=>{"string_value"=>""}}},
|
|
97
|
+
#
|
|
98
|
+
# "26"=>{
|
|
99
|
+
# "0"=>{"response_group"=>"0", "question_id"=>"26", "180"=>{"string_value"=>"1999"}},
|
|
100
|
+
# "1"=>{"response_group"=>"1", "question_id"=>"26", "180"=>{"string_value"=>"2004"}},
|
|
101
|
+
# "2"=>{"response_group"=>"2", "question_id"=>"26", "180"=>{"string_value"=>""}},
|
|
102
|
+
# "3"=>{"response_group"=>"3", "question_id"=>"26", "180"=>{"string_value"=>""}},
|
|
103
|
+
# "4"=>{"response_group"=>"4", "question_id"=>"26", "180"=>{"string_value"=>""}}},
|
|
104
|
+
#
|
|
105
|
+
# "27"=>{
|
|
106
|
+
# "0"=>{"182"=>{"integer_value"=>""}, "response_group"=>"0", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
|
107
|
+
# "1"=>{"182"=>{"integer_value"=>""}, "response_group"=>"1", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
|
108
|
+
# "2"=>{"182"=>{"integer_value"=>""}, "response_group"=>"2", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
|
109
|
+
# "3"=>{"182"=>{"integer_value"=>""}, "response_group"=>"3", "question_id"=>"27", "181"=>{"string_value"=>""}},
|
|
110
|
+
# "4"=>{"182"=>{"integer_value"=>""}, "response_group"=>"4", "question_id"=>"27", "181"=>{"string_value"=>""}}}},
|
|
111
|
+
|
|
112
|
+
# 0,1,2,3,4 are the response group numbers
|
|
113
|
+
# and anything else in the response group hash is handled normally
|
|
114
|
+
|
|
115
|
+
# method to process responses in response groups
|
|
116
|
+
def response_group_attributes=(response_attributes)
|
|
117
|
+
response_attributes.each do |question_id, responses_group_hash|
|
|
118
|
+
# Response.delete_all(["response_set_id =? AND question_id =?", self.id, question_id])
|
|
119
|
+
responses_group_hash.each do |response_group_number, group_hash|
|
|
120
|
+
if (answer_id = group_hash[:answer_id]) # if group_hash has an answer_id key we treat it differently
|
|
121
|
+
if (!group_hash[:answer_id].empty?) # dropdowns return empty values in answer_ids if they are not selected
|
|
122
|
+
#radio or dropdown - only one response
|
|
123
|
+
responses.build({:question_id => question_id, :answer_id => answer_id, :response_group => response_group_number}.merge(group_hash[answer_id] || {}))
|
|
124
|
+
end
|
|
125
|
+
else
|
|
126
|
+
#possibly multiples responses - unresponded radios end up here too
|
|
127
|
+
# we use the variable question_id in the key, not the "question_id" in the response_hash... same with response_group key
|
|
128
|
+
group_hash.delete_if{|k,v| (k == "question_id") or (k == "response_group")}.each do |answer_id, inner_hash|
|
|
129
|
+
unless inner_hash.delete_if{|k,v| v.blank?}.empty?
|
|
130
|
+
responses.build({:question_id => question_id, :answer_id => answer_id, :response_group => response_group_number}.merge(inner_hash))
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def find_response(answer_id)
|
|
140
|
+
self.responses.find_by_answer_id(answer_id)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def save_responses
|
|
144
|
+
responses.each do |response|
|
|
145
|
+
response.save(false)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# ResponseSet has an awareness of its internal state
|
|
150
|
+
def empty?
|
|
151
|
+
self.responses.empty?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def complete!
|
|
155
|
+
self.completed_at = Time.now
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def has_answered_question?(question)
|
|
159
|
+
!has_not_answered_question?(question)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def has_not_answered_question?(question)
|
|
163
|
+
self.responses.find_all_by_question_id(question.id).empty?
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Returns the number of response groups (count of group responses enterted) for this question group
|
|
167
|
+
def count_group_responses(questions)
|
|
168
|
+
questions.map{|q| responses.select{|r| r.question_id == q.id && !r.response_group.nil?}.size }.max
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def unanswered_dependencies
|
|
172
|
+
dependencies.select{|d| d.met?(self) and self.has_not_answered_question?(d.question)}.map(&:question)
|
|
173
|
+
end
|
|
174
|
+
def all_dependencies
|
|
175
|
+
arr = dependencies.partition{|d| d.met?(self) }
|
|
176
|
+
{:show => arr[0].map{|d| "question_#{d.question_id}"}, :hide => arr[1].map{|d| "question_#{d.question_id}"}}
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
protected
|
|
180
|
+
|
|
181
|
+
def dependencies
|
|
182
|
+
question_ids = Question.find_all_by_survey_section_id(current_section_id).map(&:id)
|
|
183
|
+
Dependency.depending_on_questions(question_ids)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
class Survey < ActiveRecord::Base
|
|
2
|
+
include TinyCode
|
|
3
|
+
|
|
4
|
+
# Associations
|
|
5
|
+
has_many :sections, :class_name => "SurveySection", :order => 'display_order'
|
|
6
|
+
has_many :sections_with_questions, :include => :questions, :class_name => "SurveySection", :order => 'display_order'
|
|
7
|
+
has_many :response_sets
|
|
8
|
+
|
|
9
|
+
# Scopes
|
|
10
|
+
named_scope :with_sections, {:include => :sections}
|
|
11
|
+
|
|
12
|
+
# Validations
|
|
13
|
+
validates_presence_of :title
|
|
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 = Survey.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
|
+
|
|
57
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class SurveySection < ActiveRecord::Base
|
|
2
|
+
|
|
3
|
+
# Associations
|
|
4
|
+
has_many :questions
|
|
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
|
+
|
|
14
|
+
end
|
|
15
|
+
|