-
Notifications
You must be signed in to change notification settings - Fork 120
/
Copy pathbase.rb
107 lines (95 loc) · 3.14 KB
/
base.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
module SmartAnswer
module Question
class Base < Node
class NextNodeUndefined < StandardError; end
def initialize(flow, name, options = {}, &block)
@validations = []
@default_next_node_block = lambda { |_| nil }
@permitted_next_nodes = []
super
end
def next_node(next_node = nil, &block)
if @next_node_block.present?
raise 'Multiple calls to next_node are not allowed'
end
if block_given?
@permitted_next_nodes = :auto
@next_node_block = block
elsif next_node
@permitted_next_nodes = [next_node]
@next_node_block = lambda { |_| next_node }
else
raise ArgumentError, 'You must specify a block or a single next node key'
end
end
def permitted_next_nodes
if @permitted_next_nodes == :auto
parser = NextNodeBlock::Parser.new
@permitted_next_nodes = parser.possible_next_nodes(@next_node_block)
end
@permitted_next_nodes.uniq
end
def validate(message = nil, &block)
@validations << [message, block]
end
def next_node_for(current_state, input)
validate!(current_state, input)
state = current_state.dup.extend(NextNodeBlock::InstanceMethods).freeze
next_node = state.instance_exec(input, &next_node_block)
unless next_node.present?
responses_and_input = current_state.responses + [input]
message = "Next node undefined. Node: #{current_state.current_node}."
message << " Responses: #{responses_and_input}."
raise NextNodeUndefined.new(message)
end
if @permitted_next_nodes == :auto
unless NextNodeBlock.permitted?(next_node)
raise "Next node (#{next_node}) not returned via question or outcome method"
end
end
next_node.to_sym
end
def save_input_as(variable_name)
@save_input_as = variable_name
end
def transition(current_state, raw_input)
input = parse_input(raw_input)
new_state = @next_node_calculations.inject(current_state.dup) do |new_state, calculation|
calculation.evaluate(new_state, input)
end
next_node = next_node_for(new_state, input)
new_state = new_state.transition_to(next_node, input) do |state|
state.save_input_as @save_input_as if @save_input_as
end
@calculations.each do |calculation|
new_state = calculation.evaluate(new_state, input)
end
new_state
end
def parse_input(raw_input)
raw_input
end
def to_response(input)
input
end
def question?
true
end
private
def next_node_block
@next_node_block || @default_next_node_block
end
def validate!(current_state, input)
@validations.each do |message, predicate|
if !current_state.instance_exec(input, &predicate)
if message
raise InvalidResponse, message
else
raise InvalidResponse
end
end
end
end
end
end
end