active_record_survey 0.1.35 → 0.1.36
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 +4 -0
- data/lib/active_record_survey/node/answer/chained.rb +0 -16
- data/lib/active_record_survey/node/answer.rb +1 -78
- data/lib/active_record_survey/node/question.rb +21 -0
- data/lib/active_record_survey/node.rb +99 -0
- data/lib/active_record_survey/version.rb +1 -1
- data/spec/active_record_survey/node/question_spec.rb +82 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be07fe6de1d3387ccd71f4c56ea9e33535baf95d
|
4
|
+
data.tar.gz: 8a24d9f1c4459a1f2131b28782dbc035ccbace2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a533e0173ba238d96430fe78348414cb76249abfd568cc4c6f920d745d920ddc3ea3b12462bb2d4577def1b4d8efefed0ccd2b702c49f9e4c3a822a8f4fa779
|
7
|
+
data.tar.gz: 13cd48140d0a0050e487e5a615afa3040b19763268a926db3d93e7758c726f261866f14aa21b3970893508aefadb429e9c47460476c5400fa6c6107ea54aa56d
|
data/README.md
CHANGED
@@ -12,6 +12,10 @@ 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.36**
|
16
|
+
- `ActiveRecordSurvey::Node::Answer#build_link` and `ActiveRecordSurvey::Node::Answer#remove_link` moved to `ActiveRecordSurvey::Node` so that questions can directly follow one another without answers
|
17
|
+
- Implemented `ActiveRecordSurvey::Node::Question#next_questions` to return all questions that follow either directly or through answers
|
18
|
+
|
15
19
|
**0.1.33**
|
16
20
|
- `ActiveRecordSurvey::Node::Answer#sibling_index` method for setting position as well
|
17
21
|
|
@@ -3,7 +3,6 @@ module ActiveRecordSurvey
|
|
3
3
|
module Chained
|
4
4
|
module ClassMethods
|
5
5
|
def self.extended(base)
|
6
|
-
base.before_destroy :before_destroy_rebuild_node_map, prepend: true # prepend is important! otherwise dependent: :destroy on node<->node_map relation is executed first and no records!
|
7
6
|
end
|
8
7
|
end
|
9
8
|
|
@@ -123,21 +122,6 @@ module ActiveRecordSurvey
|
|
123
122
|
end
|
124
123
|
}
|
125
124
|
end
|
126
|
-
|
127
|
-
private
|
128
|
-
# Before a node is destroyed, will re-build the node_map links from parent to child if they exist
|
129
|
-
def before_destroy_rebuild_node_map
|
130
|
-
|
131
|
-
# All the node_maps from this node
|
132
|
-
self.survey.node_maps.select { |i|
|
133
|
-
i.node == self
|
134
|
-
}.each { |node_map|
|
135
|
-
# Remap all of this nodes children to the parent
|
136
|
-
node_map.children.each { |child|
|
137
|
-
node_map.parent.children << child
|
138
|
-
}
|
139
|
-
}
|
140
|
-
end
|
141
125
|
end
|
142
126
|
end
|
143
127
|
end
|
@@ -50,89 +50,12 @@ module ActiveRecordSurvey
|
|
50
50
|
return nil
|
51
51
|
end
|
52
52
|
|
53
|
-
# Removes the link
|
54
|
-
def remove_link
|
55
|
-
# not linked to a question - nothing to remove!
|
56
|
-
return true if (question = self.next_question).nil?
|
57
|
-
|
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)
|
64
|
-
else
|
65
|
-
node_map.parent = nil
|
66
|
-
end
|
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
|
79
|
-
}
|
80
|
-
end
|
81
|
-
|
82
|
-
# Build a link from this node to another node
|
83
|
-
# Building a link actually needs to throw off a whole new clone of all children nodes
|
84
53
|
def build_link(to_node)
|
85
|
-
# build_link only accepts a to_node that inherits from Question
|
86
|
-
if !to_node.class.ancestors.include?(::ActiveRecordSurvey::Node::Question)
|
87
|
-
raise ArgumentError.new "to_node must inherit from ::ActiveRecordSurvey::Node::Question"
|
88
|
-
end
|
89
|
-
|
90
54
|
if self.question.nil?
|
91
55
|
raise ArgumentError.new "A question is required before calling #build_link"
|
92
56
|
end
|
93
57
|
|
94
|
-
|
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
|
-
|
100
|
-
# Answer has already got a question - throw error
|
101
|
-
if from_node_maps.select { |i|
|
102
|
-
i.children.length === 0
|
103
|
-
}.length === 0
|
104
|
-
raise RuntimeError.new "This answer has already been linked"
|
105
|
-
end
|
106
|
-
|
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? }
|
109
|
-
|
110
|
-
if to_node_maps.first.nil?
|
111
|
-
to_node_maps << self.survey.node_maps.build(:survey => self.survey, :node => to_node)
|
112
|
-
end
|
113
|
-
|
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
|
117
|
-
|
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)
|
122
|
-
end
|
123
|
-
|
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|
|
131
|
-
# There is a path from Q -> A that is a loop
|
132
|
-
if node_map.has_infinite_loop?
|
133
|
-
raise RuntimeError.new "Infinite loop detected"
|
134
|
-
end
|
135
|
-
}
|
58
|
+
super(to_node)
|
136
59
|
end
|
137
60
|
|
138
61
|
# Gets index in sibling relationship
|
@@ -25,5 +25,26 @@ module ActiveRecordSurvey
|
|
25
25
|
# Answers actually define how they're built off the parent node
|
26
26
|
answer_node.send(:build_answer, self)
|
27
27
|
end
|
28
|
+
|
29
|
+
# Returns the questions that follows this question (either directly or via its answers)
|
30
|
+
def next_questions
|
31
|
+
list = []
|
32
|
+
|
33
|
+
if question_node_map = self.survey.node_maps.select { |i|
|
34
|
+
i.node == self && !i.marked_for_destruction?
|
35
|
+
}.first
|
36
|
+
question_node_map.children.each { |child|
|
37
|
+
if !child.node.nil? && !child.marked_for_destruction?
|
38
|
+
if child.node.class.ancestors.include?(::ActiveRecordSurvey::Node::Question)
|
39
|
+
list << child.node
|
40
|
+
elsif child.node.class.ancestors.include?(::ActiveRecordSurvey::Node::Answer)
|
41
|
+
list << child.node.next_question
|
42
|
+
end
|
43
|
+
end
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
list.compact.uniq
|
48
|
+
end
|
28
49
|
end
|
29
50
|
end
|
@@ -6,6 +6,8 @@ module ActiveRecordSurvey
|
|
6
6
|
has_many :node_validations, :class_name => "ActiveRecordSurvey::NodeValidation", :foreign_key => :active_record_survey_node_id, autosave: true, dependent: :destroy
|
7
7
|
has_many :instance_nodes, :class_name => "ActiveRecordSurvey::InstanceNode", :foreign_key => :active_record_survey_node_id
|
8
8
|
|
9
|
+
before_destroy :before_destroy_rebuild_node_map, prepend: true # prepend is important! otherwise dependent: :destroy on node<->node_map relation is executed first and no records!
|
10
|
+
|
9
11
|
# All the answer nodes that follow from this node
|
10
12
|
def answers
|
11
13
|
self.survey.node_maps.select { |i|
|
@@ -113,5 +115,102 @@ module ActiveRecordSurvey
|
|
113
115
|
# If recursion reports back to have at least one valid path to root
|
114
116
|
paths.include?(true)
|
115
117
|
end
|
118
|
+
|
119
|
+
# Removes the node_map link
|
120
|
+
def remove_link
|
121
|
+
# not linked to a question - nothing to remove!
|
122
|
+
return true if (question = self.next_question).nil?
|
123
|
+
|
124
|
+
count = 0
|
125
|
+
to_remove = []
|
126
|
+
self.survey.node_maps.each { |node_map|
|
127
|
+
if node_map.node == question
|
128
|
+
if count > 0
|
129
|
+
to_remove.concat(node_map.self_and_descendants)
|
130
|
+
else
|
131
|
+
node_map.parent = nil
|
132
|
+
end
|
133
|
+
count = count + 1
|
134
|
+
end
|
135
|
+
|
136
|
+
if node_map.node == self
|
137
|
+
node_map.children = []
|
138
|
+
end
|
139
|
+
}
|
140
|
+
self.survey.node_maps.each { |node_map|
|
141
|
+
if to_remove.include?(node_map)
|
142
|
+
node_map.parent = nil
|
143
|
+
node_map.mark_for_destruction
|
144
|
+
end
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
# Build a link from this node to another node
|
149
|
+
# Building a link actually needs to throw off a whole new clone of all children nodes
|
150
|
+
def build_link(to_node)
|
151
|
+
# build_link only accepts a to_node that inherits from Question
|
152
|
+
if !to_node.class.ancestors.include?(::ActiveRecordSurvey::Node::Question)
|
153
|
+
raise ArgumentError.new "to_node must inherit from ::ActiveRecordSurvey::Node::Question"
|
154
|
+
end
|
155
|
+
|
156
|
+
if self.survey.nil?
|
157
|
+
raise ArgumentError.new "A survey is required before calling #build_link"
|
158
|
+
end
|
159
|
+
|
160
|
+
from_node_maps = self.survey.node_maps.select { |i| i.node == self && !i.marked_for_destruction? }
|
161
|
+
|
162
|
+
# Answer has already got a question - throw error
|
163
|
+
if from_node_maps.select { |i|
|
164
|
+
i.children.length === 0
|
165
|
+
}.length === 0
|
166
|
+
raise RuntimeError.new "This node has already been linked"
|
167
|
+
end
|
168
|
+
|
169
|
+
# Because we need something to clone - filter this further below
|
170
|
+
to_node_maps = self.survey.node_maps.select { |i| i.node == to_node && !i.marked_for_destruction? }
|
171
|
+
|
172
|
+
if to_node_maps.first.nil?
|
173
|
+
to_node_maps << self.survey.node_maps.build(:survey => self.survey, :node => to_node)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Ensure we can through each possible path of getting to this answer
|
177
|
+
to_node_map = to_node_maps.first
|
178
|
+
to_node_map.survey = self.survey # required due to voodoo - we want to use the same survey with the same object_id
|
179
|
+
|
180
|
+
# We only want node maps that aren't linked somewhere
|
181
|
+
to_node_maps = to_node_maps.select { |i| i.parent.nil? }
|
182
|
+
while to_node_maps.length < from_node_maps.length do
|
183
|
+
to_node_maps.push(to_node_map.recursive_clone)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Link unused node_maps to the new parents
|
187
|
+
from_node_maps.each_with_index { |from_node_map, index|
|
188
|
+
from_node_map.children << to_node_maps[index]
|
189
|
+
}
|
190
|
+
|
191
|
+
# Ensure no infinite loops were created
|
192
|
+
from_node_maps.each { |node_map|
|
193
|
+
# There is a path from Q -> A that is a loop
|
194
|
+
if node_map.has_infinite_loop?
|
195
|
+
raise RuntimeError.new "Infinite loop detected"
|
196
|
+
end
|
197
|
+
}
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
# Before a node is destroyed, will re-build the node_map links from parent to child if they exist
|
202
|
+
def before_destroy_rebuild_node_map
|
203
|
+
# All the node_maps from this node
|
204
|
+
self.survey.node_maps.select { |i|
|
205
|
+
i.node == self
|
206
|
+
}.each { |node_map|
|
207
|
+
# Remap all of this nodes children to the parent
|
208
|
+
node_map.children.each { |child|
|
209
|
+
node_map.parent.children << child
|
210
|
+
}
|
211
|
+
}
|
212
|
+
|
213
|
+
true
|
214
|
+
end
|
116
215
|
end
|
117
216
|
end
|
@@ -1,6 +1,88 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe ActiveRecordSurvey::Node::Question, :question_spec => true do
|
4
|
+
describe "#next_questions" do
|
5
|
+
it "should return an array of all questions following a question, whether they have answers or not" do
|
6
|
+
@survey = ActiveRecordSurvey::Survey.new()
|
7
|
+
|
8
|
+
@q1 = ActiveRecordSurvey::Node::Question.new(:text => "Question #1", :survey => @survey)
|
9
|
+
@q1_a1 = ActiveRecordSurvey::Node::Answer.new(:text => "Q1 Answer #1")
|
10
|
+
@q1_a2 = ActiveRecordSurvey::Node::Answer.new(:text => "Q1 Answer #2")
|
11
|
+
@q1_a3 = ActiveRecordSurvey::Node::Answer.new(:text => "Q1 Answer #3")
|
12
|
+
@q1_a4 = ActiveRecordSurvey::Node::Answer.new(:text => "Q1 Answer #4")
|
13
|
+
@q1.build_answer(@q1_a1)
|
14
|
+
@q1.build_answer(@q1_a2)
|
15
|
+
@q1.build_answer(@q1_a3)
|
16
|
+
@q1.build_answer(@q1_a4)
|
17
|
+
|
18
|
+
@q2 = ActiveRecordSurvey::Node::Question.new(:text => "Question #2", :survey => @survey)
|
19
|
+
@q2_a1 = ActiveRecordSurvey::Node::Answer.new(:text => "Q2 Answer #1")
|
20
|
+
@q2_a2 = ActiveRecordSurvey::Node::Answer.new(:text => "Q2 Answer #2")
|
21
|
+
@q2.build_answer(@q2_a1)
|
22
|
+
@q2.build_answer(@q2_a2)
|
23
|
+
|
24
|
+
@q3 = ActiveRecordSurvey::Node::Question.new(:text => "Question #3", :survey => @survey)
|
25
|
+
@q3_a1 = ActiveRecordSurvey::Node::Answer.new(:text => "Q3 Answer #1")
|
26
|
+
@q3_a2 = ActiveRecordSurvey::Node::Answer.new(:text => "Q3 Answer #2")
|
27
|
+
@q3.build_answer(@q3_a1)
|
28
|
+
@q3.build_answer(@q3_a2)
|
29
|
+
|
30
|
+
@q4 = ActiveRecordSurvey::Node::Question.new(:text => "Question #4", :survey => @survey)
|
31
|
+
@q4_a1 = ActiveRecordSurvey::Node::Answer.new(:text => "Q4 Answer #1")
|
32
|
+
@q4_a2 = ActiveRecordSurvey::Node::Answer.new(:text => "Q4 Answer #2")
|
33
|
+
@q4.build_answer(@q4_a1)
|
34
|
+
@q4.build_answer(@q4_a2)
|
35
|
+
|
36
|
+
@q5 = ActiveRecordSurvey::Node::Question.new(:text => "Question #5", :survey => @survey)
|
37
|
+
|
38
|
+
@q6 = ActiveRecordSurvey::Node::Question.new(:text => "Question #6", :survey => @survey)
|
39
|
+
@q6_a1 = ActiveRecordSurvey::Node::Answer::Boolean.new(:text => "Q6 Answer #1")
|
40
|
+
@q6_a2 = ActiveRecordSurvey::Node::Answer::Boolean.new(:text => "Q6 Answer #2")
|
41
|
+
@q6.build_answer(@q6_a1)
|
42
|
+
@q6.build_answer(@q6_a2)
|
43
|
+
|
44
|
+
@q7 = ActiveRecordSurvey::Node::Question.new(:text => "Question #7", :survey => @survey)
|
45
|
+
|
46
|
+
# Link up Q1
|
47
|
+
@q1_a1.build_link(@q2)
|
48
|
+
@q1_a2.build_link(@q3)
|
49
|
+
@q1_a3.build_link(@q4)
|
50
|
+
|
51
|
+
# Link up Q2
|
52
|
+
@q2_a1.build_link(@q4)
|
53
|
+
@q2_a2.build_link(@q3)
|
54
|
+
|
55
|
+
# Link up Q3
|
56
|
+
@q3_a1.build_link(@q4)
|
57
|
+
@q3_a2.build_link(@q4)
|
58
|
+
|
59
|
+
# Link up Q1A4 -> Q5
|
60
|
+
@q1_a4.build_link(@q5)
|
61
|
+
|
62
|
+
# Link up Q5 -> Q6
|
63
|
+
@q5.build_link(@q6)
|
64
|
+
|
65
|
+
# Link up Q6 -> Q7
|
66
|
+
@q6_a2.build_link(@q7)
|
67
|
+
|
68
|
+
@survey.save
|
69
|
+
|
70
|
+
q1_next_questions = @q1.next_questions
|
71
|
+
q2_next_questions = @q2.next_questions
|
72
|
+
q3_next_questions = @q3.next_questions
|
73
|
+
q4_next_questions = @q4.next_questions
|
74
|
+
q5_next_questions = @q5.next_questions
|
75
|
+
q6_next_questions = @q6.next_questions
|
76
|
+
|
77
|
+
expect(q1_next_questions.length).to eq(4)
|
78
|
+
expect(q2_next_questions.length).to eq(2)
|
79
|
+
expect(q3_next_questions.length).to eq(1)
|
80
|
+
expect(q4_next_questions.length).to eq(0)
|
81
|
+
expect(q5_next_questions.length).to eq(1)
|
82
|
+
expect(q6_next_questions.length).to eq(1)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
4
86
|
describe "#build_answer" do
|
5
87
|
before(:each) do
|
6
88
|
@survey = ActiveRecordSurvey::Survey.new()
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_survey
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.36
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Butch Marshall
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|