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