active_record_survey 0.1.25 → 0.1.26
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.
- 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
|