active_record_survey 0.1.25 → 0.1.26
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +19 -18
- data/active_record_survey.gemspec +1 -1
- data/lib/active_record_survey/instance_node.rb +2 -2
- data/lib/active_record_survey/node/answer/chained.rb +12 -7
- data/lib/active_record_survey/node/answer.rb +79 -79
- data/lib/active_record_survey/node/question.rb +5 -18
- data/lib/active_record_survey/node.rb +8 -4
- data/lib/active_record_survey/node_map.rb +25 -15
- data/lib/active_record_survey/survey.rb +7 -30
- data/lib/active_record_survey/version.rb +1 -1
- data/lib/generators/active_record_survey/templates/migration_0.1.0.rb +2 -0
- data/lib/generators/active_record_survey/templates/migration_0.1.26.rb +9 -0
- data/spec/active_record_survey/node/answer/boolean_spec.rb +14 -15
- data/spec/active_record_survey/node/answer/rank_spec.rb +6 -7
- data/spec/active_record_survey/node/answer/scale_spec.rb +4 -5
- data/spec/active_record_survey/node/answer/text_spec.rb +1 -2
- data/spec/active_record_survey/node/answer_spec.rb +128 -40
- data/spec/active_record_survey/node/question_spec.rb +79 -34
- data/spec/active_record_survey/survey_spec.rb +13 -13
- data/spec/factories/active_record_survey/survey.rb +10 -20
- data/spec/spec_helper.rb +4 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d9764b3647e3386e4d60b9753940515b78881b88
|
4
|
+
data.tar.gz: 899bf6944ac349d04d2aca7b20254837a2a93bd2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 765649c5e0aa37a8b02fac5ef171852d45f4a5145ea414f1486a4fa73f3b95834bee2f68192145681bd3975cba6e1b580eb09d959e139a9c59ea95f2b1d46402
|
7
|
+
data.tar.gz: b23afb8a510de738c55fd823fdc03d498471b4e4c65e4b3eca5a9ff7a919a5359e066b8e0ea782b4adeddbb28083f33c8310d034f24e6d73c4dd9377752e9a26
|
data/README.md
CHANGED
@@ -12,6 +12,11 @@ The goal is to give a simple interface for creating surveys and validating the a
|
|
12
12
|
Release Notes
|
13
13
|
============
|
14
14
|
|
15
|
+
**0.1.26**
|
16
|
+
- Major refactor of answer#build_link and answer#remove_link
|
17
|
+
- `ActiveRecordSurvey::Node` now has a direct reference to its survey. Don't forget to run the install task Update_0_1_26_ActiveRecordSurvey
|
18
|
+
- survey#build_question removed, no longer needed, just use survey.questions.build
|
19
|
+
|
15
20
|
**0.1.24**
|
16
21
|
- Refactored class `ActiveRecordSurvey::Node::Answer::Chain` to module `ActiveRecordSurvey::Node::Answer::Chained` - this functionality makes way more sense implemented as a module.
|
17
22
|
|
@@ -72,38 +77,34 @@ The usage below with `:text => ""` will not actually work unless you implement `
|
|
72
77
|
### Build a basic survey
|
73
78
|
```ruby
|
74
79
|
|
75
|
-
#
|
80
|
+
# Building surveys
|
76
81
|
@survey = ActiveRecordSurvey::Survey.new()
|
77
82
|
|
78
|
-
@q1 = ActiveRecordSurvey::Node::Question
|
79
|
-
@survey.build_question(@q1)
|
83
|
+
@q1 = @survey.questions.build(:type => "ActiveRecordSurvey::Node::Question", :text => "Question #1", :survey => @survey)
|
80
84
|
@q1_a1 = ActiveRecordSurvey::Node::Answer.new(:text => "Q1 Answer #1")
|
81
85
|
@q1_a2 = ActiveRecordSurvey::Node::Answer.new(:text => "Q1 Answer #2")
|
82
86
|
@q1_a3 = ActiveRecordSurvey::Node::Answer.new(:text => "Q1 Answer #3")
|
83
|
-
@q1.build_answer(@q1_a1
|
84
|
-
@q1.build_answer(@q1_a2
|
85
|
-
@q1.build_answer(@q1_a3
|
87
|
+
@q1.build_answer(@q1_a1)
|
88
|
+
@q1.build_answer(@q1_a2)
|
89
|
+
@q1.build_answer(@q1_a3)
|
86
90
|
|
87
|
-
@q2 = ActiveRecordSurvey::Node::Question
|
88
|
-
@survey.build_question(@q2)
|
91
|
+
@q2 = @survey.questions.build(:type => "ActiveRecordSurvey::Node::Question", :text => "Question #2", :survey => @survey)
|
89
92
|
@q2_a1 = ActiveRecordSurvey::Node::Answer.new(:text => "Q2 Answer #1")
|
90
93
|
@q2_a2 = ActiveRecordSurvey::Node::Answer.new(:text => "Q2 Answer #2")
|
91
|
-
@q2.build_answer(@q2_a1
|
92
|
-
@q2.build_answer(@q2_a2
|
94
|
+
@q2.build_answer(@q2_a1)
|
95
|
+
@q2.build_answer(@q2_a2)
|
93
96
|
|
94
|
-
@q3 = ActiveRecordSurvey::Node::Question
|
95
|
-
@survey.build_question(@q3)
|
97
|
+
@q3 = @survey.questions.build(:type => "ActiveRecordSurvey::Node::Question", :text => "Question #3", :survey => @survey)
|
96
98
|
@q3_a1 = ActiveRecordSurvey::Node::Answer.new(:text => "Q3 Answer #1")
|
97
99
|
@q3_a2 = ActiveRecordSurvey::Node::Answer.new(:text => "Q3 Answer #2")
|
98
|
-
@q3.build_answer(@q3_a1
|
99
|
-
@q3.build_answer(@q3_a2
|
100
|
+
@q3.build_answer(@q3_a1)
|
101
|
+
@q3.build_answer(@q3_a2)
|
100
102
|
|
101
|
-
@q4 = ActiveRecordSurvey::Node::Question
|
102
|
-
@survey.build_question(@q4)
|
103
|
+
@q4 = @survey.questions.build(:type => "ActiveRecordSurvey::Node::Question", :text => "Question #4", :survey => @survey)
|
103
104
|
@q4_a1 = ActiveRecordSurvey::Node::Answer.new(:text => "Q4 Answer #1")
|
104
105
|
@q4_a2 = ActiveRecordSurvey::Node::Answer.new(:text => "Q4 Answer #2")
|
105
|
-
@q4.build_answer(@q4_a1
|
106
|
-
@q4.build_answer(@q4_a2
|
106
|
+
@q4.build_answer(@q4_a1)
|
107
|
+
@q4.build_answer(@q4_a2)
|
107
108
|
|
108
109
|
# Link up Q1
|
109
110
|
@q1_a1.build_link(@q2)
|
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency "activerecord", [">= 3.0", "< 5.0"]
|
22
|
-
spec.add_dependency "awesome_nested_set", [">=
|
22
|
+
spec.add_dependency "awesome_nested_set", [">= 3.0"]
|
23
23
|
if RUBY_PLATFORM == 'java'
|
24
24
|
spec.add_development_dependency "jdbc-sqlite3", "> 0"
|
25
25
|
spec.add_development_dependency "activerecord-jdbcsqlite3-adapter", "> 0"
|
@@ -12,7 +12,7 @@ module ActiveRecordSurvey
|
|
12
12
|
instance_node.errors[:base] << "INVALID_PATH"
|
13
13
|
end
|
14
14
|
|
15
|
-
parent_nodes = self.node.node_maps.collect { |j| j.parent }
|
15
|
+
parent_nodes = self.node.survey.node_maps.select { |i| i.node == self.node }.collect { |j| j.parent }
|
16
16
|
|
17
17
|
# Two instance_nodes on the same node for this instance
|
18
18
|
if self.instance.instance_nodes.select { |i|
|
@@ -21,7 +21,7 @@ module ActiveRecordSurvey
|
|
21
21
|
}.select { |i|
|
22
22
|
# And the two arrays
|
23
23
|
# Two votes share a parent (this means a question has two answers for this instance)
|
24
|
-
(i.node.node_maps.collect { |j| j.parent } & parent_nodes).length > 0
|
24
|
+
(i.node.survey.node_maps.select { |j| i.node == j.node }.collect { |j| j.parent } & parent_nodes).length > 0
|
25
25
|
}.length > 1
|
26
26
|
instance_node.errors[:base] << "DUPLICATE_PATH"
|
27
27
|
end
|
@@ -2,19 +2,24 @@ module ActiveRecordSurvey
|
|
2
2
|
class Answer
|
3
3
|
module Chained
|
4
4
|
# Chain nodes are different - they must find the final answer node added and add to it
|
5
|
-
def build_answer(question_node
|
5
|
+
def build_answer(question_node)
|
6
|
+
self.survey = question_node.survey
|
7
|
+
|
8
|
+
question_node_maps = self.survey.node_maps.select { |i| i.node == question_node && !i.marked_for_destruction? }
|
9
|
+
|
6
10
|
# No node_maps exist yet from this question
|
7
|
-
if
|
11
|
+
if question_node_maps.length === 0
|
8
12
|
# Build our first node-map
|
9
|
-
|
13
|
+
question_node_maps << self.survey.node_maps.build(:node => question_node, :survey => self.survey)
|
10
14
|
end
|
11
15
|
|
12
|
-
|
16
|
+
last_answer_in_chain = (question_node.answers.last || question_node)
|
13
17
|
|
14
18
|
# Each instance of this question needs the answer hung from it
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
self.survey.node_maps.select { |i|
|
20
|
+
i.node == last_answer_in_chain
|
21
|
+
}.each { |node_map|
|
22
|
+
node_map.children << self.survey.node_maps.build(:node => self, :survey => self.survey)
|
18
23
|
}
|
19
24
|
|
20
25
|
true
|
@@ -4,14 +4,18 @@ module ActiveRecordSurvey
|
|
4
4
|
# Validate this node against an instance
|
5
5
|
def validate_node(instance)
|
6
6
|
# Ensure each parent node to this node (the goal here is to hit a question node) is valid
|
7
|
-
!self.node_maps.
|
7
|
+
!self.survey.node_maps.select { |i|
|
8
|
+
i.node == self
|
9
|
+
}.collect { |node_map|
|
8
10
|
node_map.parent.node.validate_node(instance)
|
9
11
|
}.include?(false)
|
10
12
|
end
|
11
13
|
|
12
14
|
# Returns the question that preceeds this answer
|
13
15
|
def question
|
14
|
-
self.node_maps.
|
16
|
+
self.survey.node_maps.select { |i|
|
17
|
+
i.node == self
|
18
|
+
}.collect { |node_map|
|
15
19
|
if node_map.parent && node_map.parent.node
|
16
20
|
# Question is not the next parent - recurse!
|
17
21
|
if node_map.parent.node.class.ancestors.include?(::ActiveRecordSurvey::Node::Answer)
|
@@ -28,9 +32,11 @@ module ActiveRecordSurvey
|
|
28
32
|
|
29
33
|
# Returns the question that follows this answer
|
30
34
|
def next_question
|
31
|
-
self.node_maps.
|
35
|
+
self.survey.node_maps.select { |i|
|
36
|
+
i.node == self && !i.marked_for_destruction?
|
37
|
+
}.each { |answer_node_map|
|
32
38
|
answer_node_map.children.each { |child|
|
33
|
-
if !child.node.nil?
|
39
|
+
if !child.node.nil? && !child.marked_for_destruction?
|
34
40
|
if child.node.class.ancestors.include?(::ActiveRecordSurvey::Node::Question)
|
35
41
|
return child.node
|
36
42
|
elsif child.node.class.ancestors.include?(::ActiveRecordSurvey::Node::Answer)
|
@@ -44,40 +50,32 @@ module ActiveRecordSurvey
|
|
44
50
|
return nil
|
45
51
|
end
|
46
52
|
|
47
|
-
attr_accessor :ancestor_marked_for_destruction
|
48
|
-
protected :ancestor_marked_for_destruction
|
49
|
-
|
50
|
-
before_save do |node|
|
51
|
-
# ------------------------ WARNING ------------------------
|
52
|
-
# This code is to support #remove_link which uses mark_for_destruction
|
53
|
-
# This code is necessary to clean everything up.
|
54
|
-
# Calling save on this answer won't automatically go to its next_question -> node_maps and clean everything up
|
55
|
-
(@ancestor_marked_for_destruction || []).each { |i|
|
56
|
-
i.destroy
|
57
|
-
}
|
58
|
-
end
|
59
|
-
|
60
53
|
# Removes the link
|
61
54
|
def remove_link
|
62
|
-
@ancestor_marked_for_destruction ||= []
|
63
|
-
|
64
55
|
# not linked to a question - nothing to remove!
|
65
56
|
return true if (question = self.next_question).nil?
|
66
57
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
question_nm.mark_for_destruction
|
74
|
-
@ancestor_marked_for_destruction << question_nm
|
75
|
-
end
|
76
|
-
false
|
58
|
+
count = 0
|
59
|
+
to_remove = []
|
60
|
+
self.survey.node_maps.each { |node_map|
|
61
|
+
if node_map.node == question
|
62
|
+
if count > 0
|
63
|
+
to_remove.concat(node_map.self_and_descendants)
|
77
64
|
else
|
78
|
-
|
65
|
+
node_map.parent = nil
|
79
66
|
end
|
80
|
-
|
67
|
+
count = count + 1
|
68
|
+
end
|
69
|
+
|
70
|
+
if node_map.node == self
|
71
|
+
node_map.children = []
|
72
|
+
end
|
73
|
+
}
|
74
|
+
self.survey.node_maps.each { |node_map|
|
75
|
+
if to_remove.include?(node_map)
|
76
|
+
node_map.parent = nil
|
77
|
+
node_map.mark_for_destruction
|
78
|
+
end
|
81
79
|
}
|
82
80
|
end
|
83
81
|
|
@@ -89,48 +87,47 @@ module ActiveRecordSurvey
|
|
89
87
|
raise ArgumentError.new "to_node must inherit from ::ActiveRecordSurvey::Node::Question"
|
90
88
|
end
|
91
89
|
|
90
|
+
if self.question.nil?
|
91
|
+
raise ArgumentError.new "A question is required before calling #build_link"
|
92
|
+
end
|
93
|
+
|
94
|
+
if self.survey.nil?
|
95
|
+
raise ArgumentError.new "A survey is required before calling #build_link"
|
96
|
+
end
|
97
|
+
|
98
|
+
from_node_maps = self.survey.node_maps.select { |i| i.node == self && !i.marked_for_destruction? }
|
99
|
+
|
92
100
|
# Answer has already got a question - throw error
|
93
|
-
if
|
101
|
+
if from_node_maps.select { |i|
|
94
102
|
i.children.length === 0
|
95
103
|
}.length === 0
|
96
104
|
raise RuntimeError.new "This answer has already been linked"
|
97
105
|
end
|
98
106
|
|
99
|
-
#
|
100
|
-
|
101
|
-
# no unused path exists
|
102
|
-
# we need to recursively clone the existing path
|
103
|
-
|
104
|
-
to_node_map = to_node.node_maps.first || to_node.node_maps.build(:node => to_node, :survey => self.node_maps.first.survey)
|
107
|
+
# Because we need something to clone - filter this further below
|
108
|
+
to_node_maps = self.survey.node_maps.select { |i| i.node == to_node && !i.marked_for_destruction? }
|
105
109
|
|
106
|
-
|
107
|
-
self.node_maps.
|
108
|
-
|
109
|
-
}
|
110
|
-
else
|
111
|
-
# Find the node map from this node that has no children
|
112
|
-
from_with_no_children = self.node_maps.select { |i|
|
113
|
-
i.children.length == 0
|
114
|
-
}
|
110
|
+
if to_node_maps.first.nil?
|
111
|
+
to_node_maps << self.survey.node_maps.build(:survey => self.survey, :node => to_node)
|
112
|
+
end
|
115
113
|
|
116
|
-
|
117
|
-
|
118
|
-
|
114
|
+
# Ensure we can through each possible path of getting to this answer
|
115
|
+
to_node_map = to_node_maps.first
|
116
|
+
to_node_map.survey = self.survey # required due to voodoo - we want to use the same survey with the same object_id
|
119
117
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
# We need to clone destinations for each of the subsequent
|
125
|
-
else
|
126
|
-
from_with_no_children.children << to_node_map.recursive_clone
|
127
|
-
end
|
128
|
-
}
|
129
|
-
|
130
|
-
# TODO - check to make sure there is no path to itself
|
118
|
+
# We only want node maps that aren't linked somewhere
|
119
|
+
to_node_maps = to_node_maps.select { |i| i.parent.nil? }
|
120
|
+
while to_node_maps.length < from_node_maps.length do
|
121
|
+
to_node_maps.push(to_node_map.recursive_clone)
|
131
122
|
end
|
132
123
|
|
133
|
-
|
124
|
+
# Link unused node_maps to the new parents
|
125
|
+
from_node_maps.each_with_index { |from_node_map, index|
|
126
|
+
from_node_map.children << to_node_maps[index]
|
127
|
+
}
|
128
|
+
|
129
|
+
# Ensure no infinite loops were created
|
130
|
+
from_node_maps.each { |node_map|
|
134
131
|
# There is a path from Q -> A that is a loop
|
135
132
|
if node_map.has_infinite_loop?
|
136
133
|
raise RuntimeError.new "Infinite loop detected"
|
@@ -138,24 +135,27 @@ module ActiveRecordSurvey
|
|
138
135
|
}
|
139
136
|
end
|
140
137
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
if question_node.node_maps.length === 0
|
148
|
-
# Build our first node-map
|
149
|
-
question_node.node_maps.build(:node => question_node, :survey => survey)
|
150
|
-
end
|
138
|
+
private
|
139
|
+
# By default - answers build off the original question node
|
140
|
+
#
|
141
|
+
# This allows us to easily override the answer building behaviour for different answer types
|
142
|
+
def build_answer(question_node)
|
143
|
+
self.survey = question_node.survey
|
151
144
|
|
152
|
-
|
153
|
-
question_node.node_maps.each { |question_node_map|
|
154
|
-
answer_node_map = self.node_maps.build(:node => self, :survey => survey)
|
155
|
-
question_node_map.children << answer_node_map
|
156
|
-
}
|
145
|
+
question_node_maps = self.survey.node_maps.select { |i| i.node == question_node && !i.marked_for_destruction? }
|
157
146
|
|
158
|
-
|
159
|
-
|
147
|
+
# No node_maps exist yet from this question
|
148
|
+
if question_node_maps.length === 0
|
149
|
+
# Build our first node-map
|
150
|
+
question_node_maps << self.survey.node_maps.build(:node => question_node, :survey => self.survey)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Each instance of this question needs the answer hung from it
|
154
|
+
question_node_maps.each { |question_node_map|
|
155
|
+
question_node_map.children << self.survey.node_maps.build(:node => self, :survey => self.survey)
|
156
|
+
}
|
157
|
+
|
158
|
+
true
|
159
|
+
end
|
160
160
|
end
|
161
161
|
end
|
@@ -7,28 +7,15 @@ module ActiveRecordSurvey
|
|
7
7
|
}.include?(false)
|
8
8
|
end
|
9
9
|
|
10
|
-
# Returns the survey to the question
|
11
|
-
def survey
|
12
|
-
if node_map = self.node_maps.first
|
13
|
-
node_map.survey
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
10
|
# Build an answer off this node
|
18
|
-
def build_answer(answer_node
|
19
|
-
|
20
|
-
|
21
|
-
}.collect { |i|
|
22
|
-
i.survey
|
23
|
-
}.first
|
24
|
-
|
25
|
-
# A survey must either be passed or already present in node_maps
|
26
|
-
if survey.nil?
|
11
|
+
def build_answer(answer_node)
|
12
|
+
# A survey must either be passed or already present in self.node_maps
|
13
|
+
if self.survey.nil?
|
27
14
|
raise ArgumentError.new "A survey must be passed if Question is not yet added to a survey"
|
28
15
|
end
|
29
16
|
|
30
|
-
# Answers actually define how they're built off the parent node
|
31
|
-
answer_node.build_answer
|
17
|
+
# Answers actually define how they're built off the parent node
|
18
|
+
answer_node.send(:build_answer, self)
|
32
19
|
end
|
33
20
|
end
|
34
21
|
end
|
@@ -1,13 +1,16 @@
|
|
1
1
|
module ActiveRecordSurvey
|
2
2
|
class Node < ::ActiveRecord::Base
|
3
3
|
self.table_name = "active_record_survey_nodes"
|
4
|
+
belongs_to :survey, :class_name => "ActiveRecordSurvey::Survey", :foreign_key => :active_record_survey_id
|
4
5
|
has_many :node_maps, :class_name => "ActiveRecordSurvey::NodeMap", :foreign_key => :active_record_survey_node_id, autosave: true
|
5
6
|
has_many :node_validations, :class_name => "ActiveRecordSurvey::NodeValidation", :foreign_key => :active_record_survey_node_id, autosave: true
|
6
7
|
has_many :instance_nodes, :class_name => "ActiveRecordSurvey::InstanceNode", :foreign_key => :active_record_survey_node_id
|
7
8
|
|
8
9
|
# All the answer nodes that follow from this node
|
9
10
|
def answers
|
10
|
-
self.node_maps.
|
11
|
+
self.survey.node_maps.select { |i|
|
12
|
+
i.node == self
|
13
|
+
}.collect { |i|
|
11
14
|
# Get all the children from this node
|
12
15
|
i.children
|
13
16
|
}.flatten.collect { |i|
|
@@ -17,6 +20,7 @@ module ActiveRecordSurvey
|
|
17
20
|
# Only the nodes that are answers
|
18
21
|
i.class.ancestors.include?(::ActiveRecordSurvey::Node::Answer)
|
19
22
|
}.uniq.collect { |i|
|
23
|
+
i.survey = self.survey # ensure that the survey being referenced by the answers is the original survey - needed for keeping consistent node_maps between build_link and remove_link
|
20
24
|
[i] + i.answers
|
21
25
|
}.flatten.uniq
|
22
26
|
end
|
@@ -48,7 +52,7 @@ module ActiveRecordSurvey
|
|
48
52
|
|
49
53
|
# Default behaviour is to recurse up the chain (goal is to hit a question node)
|
50
54
|
def validate_parent_instance_node(instance_node, child_node)
|
51
|
-
!self.node_maps.collect { |node_map|
|
55
|
+
!self.survey.node_maps.select { |i| i.node == self}.collect { |node_map|
|
52
56
|
if node_map.parent
|
53
57
|
node_map.parent.node.validate_parent_instance_node(instance_node, self)
|
54
58
|
# Hit top node
|
@@ -72,7 +76,7 @@ module ActiveRecordSurvey
|
|
72
76
|
# More complex....
|
73
77
|
# Recureses to the parent node to check
|
74
78
|
# This is to validate Node::Question since they don't have instance_nodes directly to validate them
|
75
|
-
parent_validations_passed = !self.node_maps.collect { |node_map|
|
79
|
+
parent_validations_passed = !self.survey.node_maps.select { |i| i.node == self}.collect { |node_map|
|
76
80
|
if node_map.parent
|
77
81
|
node_map.parent.node.validate_parent_instance_node(instance_node, self)
|
78
82
|
# Hit top node
|
@@ -96,7 +100,7 @@ module ActiveRecordSurvey
|
|
96
100
|
|
97
101
|
# Start at each node_map of this node
|
98
102
|
# Find the parent node ma
|
99
|
-
paths = self.node_maps.collect { |node_map|
|
103
|
+
paths = self.survey.node_maps.select { |i| i.node == self }.collect { |node_map|
|
100
104
|
# There is another level to traverse
|
101
105
|
if node_map.parent
|
102
106
|
node_map.parent.node.instance_node_path_to_root?(instance_node)
|
@@ -7,33 +7,34 @@ module ActiveRecordSurvey
|
|
7
7
|
|
8
8
|
validates_presence_of :survey
|
9
9
|
|
10
|
-
after_initialize do |i|
|
11
|
-
# Required for all functions to work without creating
|
12
|
-
i.survey.node_maps << self if i.new_record? && i.survey
|
13
|
-
end
|
14
|
-
|
15
10
|
# Recursively creates a copy of this entire node_map
|
16
11
|
def recursive_clone
|
17
|
-
node_map = self.
|
12
|
+
node_map = self.survey.node_maps.build(:survey => self.survey, :node => self.node)
|
18
13
|
self.children.each { |child_node|
|
14
|
+
child_node.survey = self.survey # required due to voodoo - we want to use the same survey with the same object_id
|
19
15
|
node_map.children << child_node.recursive_clone
|
20
16
|
}
|
21
17
|
node_map
|
22
18
|
end
|
23
19
|
|
24
|
-
def as_map(
|
25
|
-
|
26
|
-
|
20
|
+
def as_map(options)
|
21
|
+
node_maps = options[:node_maps]
|
22
|
+
|
23
|
+
c = (node_maps.nil?)? self.children : node_maps.select { |i|
|
24
|
+
i.parent == self && !i.marked_for_destruction?
|
27
25
|
}
|
28
26
|
|
29
|
-
{
|
30
|
-
|
31
|
-
|
27
|
+
result = {}
|
28
|
+
result.merge!({ :id => self.id, :node_id => self.node.id }) if !options[:no_ids]
|
29
|
+
result.merge!({
|
32
30
|
:type => self.node.class.to_s,
|
33
|
-
:children =>
|
34
|
-
i.as_map(
|
31
|
+
:children => c.collect { |i|
|
32
|
+
i.as_map(options)
|
35
33
|
}
|
36
|
-
}
|
34
|
+
})
|
35
|
+
|
36
|
+
|
37
|
+
result
|
37
38
|
end
|
38
39
|
|
39
40
|
# Gets all the child nodes until one is not an ancestor of klass
|
@@ -57,5 +58,14 @@ module ActiveRecordSurvey
|
|
57
58
|
}
|
58
59
|
false
|
59
60
|
end
|
61
|
+
|
62
|
+
def mark_self_and_children_for_destruction
|
63
|
+
removed = [self]
|
64
|
+
self.mark_for_destruction
|
65
|
+
self.children.each { |i|
|
66
|
+
removed.concat(i.mark_self_and_children_for_destruction)
|
67
|
+
}
|
68
|
+
removed
|
69
|
+
end
|
60
70
|
end
|
61
71
|
end
|
@@ -1,44 +1,21 @@
|
|
1
1
|
module ActiveRecordSurvey
|
2
2
|
class Survey < ::ActiveRecord::Base
|
3
3
|
self.table_name = "active_record_surveys"
|
4
|
-
has_many :node_maps, :class_name => "ActiveRecordSurvey::NodeMap", :foreign_key => :active_record_survey_id
|
4
|
+
has_many :node_maps, :class_name => "ActiveRecordSurvey::NodeMap", :foreign_key => :active_record_survey_id, autosave: true
|
5
5
|
has_many :nodes, -> { distinct }, :through => :node_maps
|
6
|
-
|
7
|
-
def questions
|
8
|
-
self.node_maps.includes(:node).select { |i|
|
9
|
-
i.node.class.ancestors.include?(::ActiveRecordSurvey::Node::Question)
|
10
|
-
}.collect { |i|
|
11
|
-
i.node
|
12
|
-
}.uniq
|
13
|
-
end
|
6
|
+
has_many :questions, :class_name => "ActiveRecordSurvey::Node::Question", :foreign_key => :active_record_survey_id
|
14
7
|
|
15
8
|
def root_node
|
16
9
|
self.node_maps.includes(:node).select { |i| i.depth === 0 }.first
|
17
10
|
end
|
18
11
|
|
19
|
-
def as_map
|
20
|
-
|
12
|
+
def as_map(*args)
|
13
|
+
options = args.extract_options!
|
14
|
+
options[:node_maps] ||= self.node_maps
|
21
15
|
|
22
|
-
|
23
|
-
i.as_map(
|
16
|
+
self.node_maps.select { |i| !i.parent && !i.marked_for_destruction? }.collect { |i|
|
17
|
+
i.as_map(options)
|
24
18
|
}
|
25
19
|
end
|
26
|
-
|
27
|
-
# Build a question for this survey
|
28
|
-
def build_question(question)
|
29
|
-
# build_question only accepts a node that inherits from Question
|
30
|
-
if !question.class.ancestors.include?(::ActiveRecordSurvey::Node::Question)
|
31
|
-
raise ArgumentError.new "Question must inherit from ::ActiveRecordSurvey::Node::Question"
|
32
|
-
end
|
33
|
-
|
34
|
-
# Already added - shouldn't add twice
|
35
|
-
if question.node_maps.select { |node_map|
|
36
|
-
node_map.survey === self
|
37
|
-
}.length > 0
|
38
|
-
raise RuntimeError.new "This question has already been added to the survey"
|
39
|
-
end
|
40
|
-
|
41
|
-
question.node_maps.build(:node => question, :survey => self)
|
42
|
-
end
|
43
20
|
end
|
44
21
|
end
|
@@ -25,6 +25,8 @@ class AddActiveRecordSurvey < ActiveRecord::Migration
|
|
25
25
|
t.integer :parent_id, :null => true, :index => true
|
26
26
|
t.integer :lft, :null => false, :index => true
|
27
27
|
t.integer :rgt, :null => false, :index => true
|
28
|
+
|
29
|
+
# optional fields
|
28
30
|
t.integer :depth, :null => false, :default => 0
|
29
31
|
t.integer :children_count, :null => false, :default => 0
|
30
32
|
|
@@ -3,21 +3,20 @@ require 'spec_helper'
|
|
3
3
|
describe ActiveRecordSurvey::Node::Answer::Boolean, :boolean_spec => true do
|
4
4
|
describe 'a boolean survey is' do
|
5
5
|
before(:all) do
|
6
|
-
@survey = ActiveRecordSurvey::Survey.new
|
7
|
-
|
8
|
-
@q1 = ActiveRecordSurvey::Node::Question.new()
|
9
|
-
@q1_a1 = ActiveRecordSurvey::Node::Answer::Boolean.new()
|
10
|
-
@q1_a2 = ActiveRecordSurvey::Node::Answer::Boolean.new()
|
11
|
-
@q1_a3 = ActiveRecordSurvey::Node::Answer::Boolean.new()
|
12
|
-
@q1_a4 = ActiveRecordSurvey::Node::Answer::Boolean.new()
|
13
|
-
@q1_a5 = ActiveRecordSurvey::Node::Answer::Boolean.new()
|
14
|
-
|
15
|
-
@
|
16
|
-
@q1.build_answer(@
|
17
|
-
@q1.build_answer(@
|
18
|
-
@q1.build_answer(@
|
19
|
-
@q1.build_answer(@
|
20
|
-
@q1.build_answer(@q1_a5, @survey)
|
6
|
+
@survey = ActiveRecordSurvey::Survey.new()
|
7
|
+
|
8
|
+
@q1 = ActiveRecordSurvey::Node::Question.new(:text => "Q1", :survey => @survey)
|
9
|
+
@q1_a1 = ActiveRecordSurvey::Node::Answer::Boolean.new(:text => "A")
|
10
|
+
@q1_a2 = ActiveRecordSurvey::Node::Answer::Boolean.new(:text => "B")
|
11
|
+
@q1_a3 = ActiveRecordSurvey::Node::Answer::Boolean.new(:text => "C")
|
12
|
+
@q1_a4 = ActiveRecordSurvey::Node::Answer::Boolean.new(:text => "D")
|
13
|
+
@q1_a5 = ActiveRecordSurvey::Node::Answer::Boolean.new(:text => "E")
|
14
|
+
|
15
|
+
@q1.build_answer(@q1_a1)
|
16
|
+
@q1.build_answer(@q1_a2)
|
17
|
+
@q1.build_answer(@q1_a3)
|
18
|
+
@q1.build_answer(@q1_a4)
|
19
|
+
@q1.build_answer(@q1_a5)
|
21
20
|
|
22
21
|
@survey.save
|
23
22
|
end
|
@@ -5,19 +5,18 @@ describe ActiveRecordSurvey::Node::Answer::Rank, :rank_spec => true do
|
|
5
5
|
before(:all) do
|
6
6
|
@survey = ActiveRecordSurvey::Survey.new
|
7
7
|
|
8
|
-
@q1 = ActiveRecordSurvey::Node::Question.new()
|
8
|
+
@q1 = ActiveRecordSurvey::Node::Question.new(:survey => @survey)
|
9
9
|
@q1_a1 = ActiveRecordSurvey::Node::Answer::Rank.new()
|
10
10
|
@q1_a2 = ActiveRecordSurvey::Node::Answer::Rank.new()
|
11
11
|
@q1_a3 = ActiveRecordSurvey::Node::Answer::Rank.new()
|
12
12
|
@q1_a4 = ActiveRecordSurvey::Node::Answer::Rank.new()
|
13
13
|
@q1_a5 = ActiveRecordSurvey::Node::Answer::Rank.new()
|
14
14
|
|
15
|
-
@
|
16
|
-
@q1.build_answer(@
|
17
|
-
@q1.build_answer(@
|
18
|
-
@q1.build_answer(@
|
19
|
-
@q1.build_answer(@
|
20
|
-
@q1.build_answer(@q1_a5, @survey)
|
15
|
+
@q1.build_answer(@q1_a1)
|
16
|
+
@q1.build_answer(@q1_a2)
|
17
|
+
@q1.build_answer(@q1_a3)
|
18
|
+
@q1.build_answer(@q1_a4)
|
19
|
+
@q1.build_answer(@q1_a5)
|
21
20
|
|
22
21
|
@survey.save
|
23
22
|
end
|