From a220a073045894171cc3071241834a9f07146e00 Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 29 Feb 2016 15:04:23 +0100 Subject: [PATCH 01/17] Convert permitted_next_nodes into attribute reader Nothing seems to be relying on the "writer" behaviour of this method. Furthermore, I think that this kind of dual-purpose method (both reader and writer) are confusing. So by converting the method into an attribute reader, we're simplifying the code. --- lib/smart_answer/question/base.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb index c6bc8841c3a..3f4df6b8195 100644 --- a/lib/smart_answer/question/base.rb +++ b/lib/smart_answer/question/base.rb @@ -3,6 +3,8 @@ module Question class Base < Node class NextNodeUndefined < StandardError; end + attr_reader :permitted_next_nodes + def initialize(flow, name, options = {}, &block) @save_input_as = nil @validations ||= [] @@ -30,10 +32,6 @@ def validate(message = nil, &block) @validations << [message, block] end - def permitted_next_nodes(*args) - @permitted_next_nodes += args - end - def next_node_for(current_state, input) validate!(current_state, input) next_node = next_node_from_default_function(current_state, input) From 2098dd2aea29ca4d40f403aac3d646f8d8a21e7e Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 29 Feb 2016 15:27:36 +0100 Subject: [PATCH 02/17] Remove unused permitted_next_node? method This method does not appear to be used anywhere. --- lib/smart_answer/question/base.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb index 3f4df6b8195..8b6294593b8 100644 --- a/lib/smart_answer/question/base.rb +++ b/lib/smart_answer/question/base.rb @@ -75,10 +75,6 @@ def question? end private - def permitted_next_node?(next_node) - @permitted_next_nodes.include?(next_node) - end - def validate!(current_state, input) @validations.each do |message, predicate| if !current_state.instance_exec(input, &predicate) From f1dddeba36b2408071595a250c6c3d3f02e0c4fb Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 29 Feb 2016 15:30:09 +0100 Subject: [PATCH 03/17] Rename default_next_node_function to next_node_block The name `default_next_node_function` confused me for two reasons: 1. The instance variable doesn't just store the *default* value; it also stores non-default values. 2. It's more common to talk about methods and blocks in Ruby rather than functions. In this case, the instance variable stores a reference to a block. --- lib/smart_answer/question/base.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb index 8b6294593b8..4e8c24e0127 100644 --- a/lib/smart_answer/question/base.rb +++ b/lib/smart_answer/question/base.rb @@ -8,7 +8,7 @@ class NextNodeUndefined < StandardError; end def initialize(flow, name, options = {}, &block) @save_input_as = nil @validations ||= [] - @default_next_node_function ||= lambda {|_|} + @next_node_block ||= lambda {|_|} @permitted_next_nodes = [] super end @@ -19,10 +19,10 @@ def next_node(next_node = nil, permitted: [], &block) raise "You must specify the permitted next nodes" end @permitted_next_nodes += permitted - @default_next_node_function = block + @next_node_block = block elsif next_node @permitted_next_nodes = [next_node] - @default_next_node_function = proc { next_node } + @next_node_block = proc { next_node } else raise ArgumentError end @@ -34,7 +34,7 @@ def validate(message = nil, &block) def next_node_for(current_state, input) validate!(current_state, input) - next_node = next_node_from_default_function(current_state, input) + next_node = next_node_from_next_node_block(current_state, input) responses_and_input = current_state.responses + [input] raise NextNodeUndefined.new("Next node undefined. Node: #{current_state.current_node}. Responses: #{responses_and_input}") unless next_node unless @permitted_next_nodes.include?(next_node) @@ -87,8 +87,8 @@ def validate!(current_state, input) end end - def next_node_from_default_function(current_state, input) - current_state.instance_exec(input, &@default_next_node_function) + def next_node_from_next_node_block(current_state, input) + current_state.instance_exec(input, &@next_node_block) end end end From cbe0a7a9e79dd3c9c3e2eaaab6a1ea9ceff6980d Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 29 Feb 2016 15:33:40 +0100 Subject: [PATCH 04/17] Inline next_node_from_next_node_block method I don't think this method was adding anything to the code. --- lib/smart_answer/question/base.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb index 4e8c24e0127..42d968f24b9 100644 --- a/lib/smart_answer/question/base.rb +++ b/lib/smart_answer/question/base.rb @@ -34,7 +34,7 @@ def validate(message = nil, &block) def next_node_for(current_state, input) validate!(current_state, input) - next_node = next_node_from_next_node_block(current_state, input) + next_node = current_state.instance_exec(input, &@next_node_block) responses_and_input = current_state.responses + [input] raise NextNodeUndefined.new("Next node undefined. Node: #{current_state.current_node}. Responses: #{responses_and_input}") unless next_node unless @permitted_next_nodes.include?(next_node) @@ -86,10 +86,6 @@ def validate!(current_state, input) end end end - - def next_node_from_next_node_block(current_state, input) - current_state.instance_exec(input, &@next_node_block) - end end end end From 4f7766a6aef3650237b5cdd557a66a9c7826ee2d Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 29 Feb 2016 15:38:17 +0100 Subject: [PATCH 05/17] Remove unnecessary blank line from top of test --- test/unit/question_base_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/question_base_test.rb b/test/unit/question_base_test.rb index 2a0ab341541..6b3a8778dea 100644 --- a/test/unit/question_base_test.rb +++ b/test/unit/question_base_test.rb @@ -1,4 +1,3 @@ - require 'ostruct' require_relative '../test_helper' From e17825bb60bdca0d8ff536704d36c528320f3005 Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 29 Feb 2016 15:38:46 +0100 Subject: [PATCH 06/17] Remove unused require statement from top of test --- test/unit/question_base_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/question_base_test.rb b/test/unit/question_base_test.rb index 6b3a8778dea..b4cd3c9c05e 100644 --- a/test/unit/question_base_test.rb +++ b/test/unit/question_base_test.rb @@ -1,4 +1,3 @@ -require 'ostruct' require_relative '../test_helper' class QuestionBaseTest < ActiveSupport::TestCase From 8a0fd948c1cdbaf04283c9f194e72ca0b90d9275 Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 29 Feb 2016 15:51:08 +0100 Subject: [PATCH 07/17] Improve argument checking for calls to next_node * Consistently use `ArgumentError` if `next_node` is called with incorrect arguments. * Add unit test coverage for the two possible errors. * Add/improve error messages. --- lib/smart_answer/question/base.rb | 4 ++-- test/unit/question_base_test.rb | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb index 42d968f24b9..038fac31f5f 100644 --- a/lib/smart_answer/question/base.rb +++ b/lib/smart_answer/question/base.rb @@ -16,7 +16,7 @@ def initialize(flow, name, options = {}, &block) def next_node(next_node = nil, permitted: [], &block) if block_given? unless permitted.any? - raise "You must specify the permitted next nodes" + raise ArgumentError, 'You must specify at least one permitted next node' end @permitted_next_nodes += permitted @next_node_block = block @@ -24,7 +24,7 @@ def next_node(next_node = nil, permitted: [], &block) @permitted_next_nodes = [next_node] @next_node_block = proc { next_node } else - raise ArgumentError + raise ArgumentError, 'You must specify a block or a single next node key' end end diff --git a/test/unit/question_base_test.rb b/test/unit/question_base_test.rb index b4cd3c9c05e..45f1c36c286 100644 --- a/test/unit/question_base_test.rb +++ b/test/unit/question_base_test.rb @@ -15,7 +15,18 @@ class QuestionBaseTest < ActiveSupport::TestCase assert_equal expected_message, exception.message end - test 'permitted next nodes can be supplied to next_node' do + test 'permitted next nodes must be supplied if next_node is called with block' do + e = assert_raises(ArgumentError) do + SmartAnswer::Question::Base.new(nil, :example) { + next_node do + :done + end + } + end + assert_equal 'You must specify at least one permitted next node', e.message + end + + test 'permitted next nodes supplied to next_node are stored' do q = SmartAnswer::Question::Base.new(nil, :example) { next_node(permitted: [:done]) do :done @@ -24,6 +35,15 @@ class QuestionBaseTest < ActiveSupport::TestCase assert_equal [:done], q.permitted_next_nodes end + test 'single next node key must be supplied if next_node called without block' do + e = assert_raises(ArgumentError) do + SmartAnswer::Question::Base.new(nil, :example) { + next_node + } + end + assert_equal 'You must specify a block or a single next node key', e.message + end + test "State is carried over on a state transition" do q = SmartAnswer::Question::Base.new(nil, :example) { next_node :done From b4ea958aed891b0c9013372d555ee6d967a6e7de Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 29 Feb 2016 16:18:57 +0100 Subject: [PATCH 08/17] Simplify instance var assignments in Question::Base There's nowhere else these instance variables could've been assigned prior to the class' constructor, so it doesn't seem necessary to include the `||` check to see whether they are already assigned. --- lib/smart_answer/question/base.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb index 038fac31f5f..8ee2a486fec 100644 --- a/lib/smart_answer/question/base.rb +++ b/lib/smart_answer/question/base.rb @@ -7,8 +7,8 @@ class NextNodeUndefined < StandardError; end def initialize(flow, name, options = {}, &block) @save_input_as = nil - @validations ||= [] - @next_node_block ||= lambda {|_|} + @validations = [] + @next_node_block = lambda {|_|} @permitted_next_nodes = [] super end From 9bb238691d0150c6f186064a97a7b50981fe6aaf Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 29 Feb 2016 16:22:31 +0100 Subject: [PATCH 09/17] Use lambda vs proc in Question::Base * Calls to `lambda` produce less surprising procs than calls to `proc` or `Proc.new` (in terms of their return behaviour). * Procs produced by calls to `lambda` are stricter about the number of arguments they are passed and so we have to declare a dummy input argument like we do in the constructor. * We were already using `lambda` to set the default value of `@next_node_block` in the constructor and it seems better to be consistent. --- lib/smart_answer/question/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb index 8ee2a486fec..8395edf5154 100644 --- a/lib/smart_answer/question/base.rb +++ b/lib/smart_answer/question/base.rb @@ -22,7 +22,7 @@ def next_node(next_node = nil, permitted: [], &block) @next_node_block = block elsif next_node @permitted_next_nodes = [next_node] - @next_node_block = proc { next_node } + @next_node_block = lambda { |_| next_node } else raise ArgumentError, 'You must specify a block or a single next node key' end From f78895199b9743b1d18968eff875ba7719746972 Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 29 Feb 2016 16:37:26 +0100 Subject: [PATCH 10/17] Improve test coverage when next_node not called There are two ways that a `NextNodeUndefined` exception can be raised: 1. The call to `next_node` does not return a truth-y value 2. No call to `next_node` is made The second of these was not being unit tested. --- test/unit/question_base_test.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/unit/question_base_test.rb b/test/unit/question_base_test.rb index 45f1c36c286..c1f23d8b2ba 100644 --- a/test/unit/question_base_test.rb +++ b/test/unit/question_base_test.rb @@ -198,4 +198,19 @@ class QuestionBaseTest < ActiveSupport::TestCase expected_message = "Next node undefined. Node: #{question_name}. Responses: #{responses}" assert_equal expected_message, error.message end + + test "error if next_node was not called for question" do + question_name = :example + responses = [:blue, :red] + q = SmartAnswer::Question::Base.new(nil, question_name) { + # no call to next_node + } + initial_state = SmartAnswer::State.new(q.name) + initial_state.responses << responses[0] + error = assert_raises(SmartAnswer::Question::Base::NextNodeUndefined) do + q.next_node_for(initial_state, responses[1]) + end + expected_message = "Next node undefined. Node: #{question_name}. Responses: #{responses}" + assert_equal expected_message, error.message + end end From ed85398e5ceb526455c606d0285f048aa6e09e79 Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 29 Feb 2016 16:40:25 +0100 Subject: [PATCH 11/17] Make next_node_block default return value more explicit In the case where no call to `next_node` is made for a question, the code which raises `NextNodeUndefined` relies on the default value for `@next_node_block` returning a false-y value when invoked. This makes that more explicit. --- lib/smart_answer/question/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb index 8395edf5154..95892d0f089 100644 --- a/lib/smart_answer/question/base.rb +++ b/lib/smart_answer/question/base.rb @@ -8,7 +8,7 @@ class NextNodeUndefined < StandardError; end def initialize(flow, name, options = {}, &block) @save_input_as = nil @validations = [] - @next_node_block = lambda {|_|} + @next_node_block = lambda { |_| nil } @permitted_next_nodes = [] super end From c8128e7aca5ac13be84587f89db0f8538844afe8 Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 29 Feb 2016 16:43:08 +0100 Subject: [PATCH 12/17] Convert single-line conditional into multi-line I think this improves the readability, because the line is so long it's easy to miss the conditional. Also it's consistent with the `if` statement immediately below it. --- lib/smart_answer/question/base.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb index 95892d0f089..4c7f39ac424 100644 --- a/lib/smart_answer/question/base.rb +++ b/lib/smart_answer/question/base.rb @@ -36,7 +36,9 @@ def next_node_for(current_state, input) validate!(current_state, input) next_node = current_state.instance_exec(input, &@next_node_block) responses_and_input = current_state.responses + [input] - raise NextNodeUndefined.new("Next node undefined. Node: #{current_state.current_node}. Responses: #{responses_and_input}") unless next_node + unless next_node + raise NextNodeUndefined.new("Next node undefined. Node: #{current_state.current_node}. Responses: #{responses_and_input}") + end unless @permitted_next_nodes.include?(next_node) raise "Next node (#{next_node}) not in list of permitted next nodes (#{@permitted_next_nodes.to_sentence})" end From 578b848d31645e59a973cc0cecd33430b76d01d2 Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 29 Feb 2016 16:59:59 +0100 Subject: [PATCH 13/17] Don't allow multiple calls to next_node per question I'm pretty sure we don't have any places where `next_node` is called multiple times per question and I can't see any sensible justification for allowing it. Constraining the DSL so that it can only be called once means that we can simplify the code and make it easier to reason about. With this guard condition in place, there's no longer any need to add the `permitted_next_nodes` to any stored in a previous call. --- lib/smart_answer/question/base.rb | 5 ++++- test/unit/question_base_test.rb | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb index 4c7f39ac424..a29e61b80fb 100644 --- a/lib/smart_answer/question/base.rb +++ b/lib/smart_answer/question/base.rb @@ -14,11 +14,14 @@ def initialize(flow, name, options = {}, &block) end def next_node(next_node = nil, permitted: [], &block) + if @permitted_next_nodes.any? + raise 'Multiple calls to next_node are not allowed' + end if block_given? unless permitted.any? raise ArgumentError, 'You must specify at least one permitted next node' end - @permitted_next_nodes += permitted + @permitted_next_nodes = permitted @next_node_block = block elsif next_node @permitted_next_nodes = [next_node] diff --git a/test/unit/question_base_test.rb b/test/unit/question_base_test.rb index c1f23d8b2ba..707aa63229d 100644 --- a/test/unit/question_base_test.rb +++ b/test/unit/question_base_test.rb @@ -44,6 +44,16 @@ class QuestionBaseTest < ActiveSupport::TestCase assert_equal 'You must specify a block or a single next node key', e.message end + test 'multiple calls to next_node are not allowed' do + e = assert_raises do + SmartAnswer::Question::Base.new(nil, :example) { + next_node :outcome_one + next_node :outcome_two + } + end + assert_equal 'Multiple calls to next_node are not allowed', e.message + end + test "State is carried over on a state transition" do q = SmartAnswer::Question::Base.new(nil, :example) { next_node :done From ce1208cc5bf7607920f7ebf39eec805ed221bf88 Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Tue, 1 Mar 2016 12:20:13 +0100 Subject: [PATCH 14/17] More direct detection of multiple calls to next_node Previously we were relying on detecting a side-effect of `next_node` having been called previously. Now we rely on whether or not `@next_node_block` has been set which is much more direct and explicit. --- lib/smart_answer/question/base.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb index a29e61b80fb..51ed973e01d 100644 --- a/lib/smart_answer/question/base.rb +++ b/lib/smart_answer/question/base.rb @@ -8,13 +8,13 @@ class NextNodeUndefined < StandardError; end def initialize(flow, name, options = {}, &block) @save_input_as = nil @validations = [] - @next_node_block = lambda { |_| nil } + @default_next_node_block = lambda { |_| nil } @permitted_next_nodes = [] super end def next_node(next_node = nil, permitted: [], &block) - if @permitted_next_nodes.any? + if @next_node_block.present? raise 'Multiple calls to next_node are not allowed' end if block_given? @@ -37,7 +37,7 @@ def validate(message = nil, &block) def next_node_for(current_state, input) validate!(current_state, input) - next_node = current_state.instance_exec(input, &@next_node_block) + next_node = current_state.instance_exec(input, &next_node_block) responses_and_input = current_state.responses + [input] unless next_node raise NextNodeUndefined.new("Next node undefined. Node: #{current_state.current_node}. Responses: #{responses_and_input}") @@ -80,6 +80,10 @@ def question? 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) From cb1b459fb612384ab3343591e35e69d49a5a0fba Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Sat, 5 Mar 2016 11:56:00 +0100 Subject: [PATCH 15/17] Move local variable inside only block where it's used --- lib/smart_answer/question/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb index 51ed973e01d..8b7efa80f51 100644 --- a/lib/smart_answer/question/base.rb +++ b/lib/smart_answer/question/base.rb @@ -38,8 +38,8 @@ def validate(message = nil, &block) def next_node_for(current_state, input) validate!(current_state, input) next_node = current_state.instance_exec(input, &next_node_block) - responses_and_input = current_state.responses + [input] unless next_node + responses_and_input = current_state.responses + [input] raise NextNodeUndefined.new("Next node undefined. Node: #{current_state.current_node}. Responses: #{responses_and_input}") end unless @permitted_next_nodes.include?(next_node) From 704866355095fd3fbb42bf047ddb9b26f5f9af4f Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Sat, 5 Mar 2016 11:58:33 +0100 Subject: [PATCH 16/17] Build exception message separately to avoid long line Also added full-stop to second sentence for consistency. --- lib/smart_answer/question/base.rb | 4 +++- test/unit/question_base_test.rb | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb index 8b7efa80f51..d638d68b0d4 100644 --- a/lib/smart_answer/question/base.rb +++ b/lib/smart_answer/question/base.rb @@ -40,7 +40,9 @@ def next_node_for(current_state, input) next_node = current_state.instance_exec(input, &next_node_block) unless next_node responses_and_input = current_state.responses + [input] - raise NextNodeUndefined.new("Next node undefined. Node: #{current_state.current_node}. Responses: #{responses_and_input}") + message = "Next node undefined. Node: #{current_state.current_node}." + message << " Responses: #{responses_and_input}." + raise NextNodeUndefined.new(message) end unless @permitted_next_nodes.include?(next_node) raise "Next node (#{next_node}) not in list of permitted next nodes (#{@permitted_next_nodes.to_sentence})" diff --git a/test/unit/question_base_test.rb b/test/unit/question_base_test.rb index 707aa63229d..1b69409fa6c 100644 --- a/test/unit/question_base_test.rb +++ b/test/unit/question_base_test.rb @@ -205,7 +205,7 @@ class QuestionBaseTest < ActiveSupport::TestCase error = assert_raises(SmartAnswer::Question::Base::NextNodeUndefined) do q.next_node_for(initial_state, responses[1]) end - expected_message = "Next node undefined. Node: #{question_name}. Responses: #{responses}" + expected_message = "Next node undefined. Node: #{question_name}. Responses: #{responses}." assert_equal expected_message, error.message end @@ -220,7 +220,7 @@ class QuestionBaseTest < ActiveSupport::TestCase error = assert_raises(SmartAnswer::Question::Base::NextNodeUndefined) do q.next_node_for(initial_state, responses[1]) end - expected_message = "Next node undefined. Node: #{question_name}. Responses: #{responses}" + expected_message = "Next node undefined. Node: #{question_name}. Responses: #{responses}." assert_equal expected_message, error.message end end From f441f7c38b1e99ecdfebfaa91d4be33720648d09 Mon Sep 17 00:00:00 2001 From: James Mead <james@floehopper.org> Date: Mon, 7 Mar 2016 17:02:45 +0100 Subject: [PATCH 17/17] Update/improve documentation for next_node Most of these changes document behaviour that existed prior to this pull request, although some of the error messages have changed slightly. The only additional behaviour that was added in this pull request is the check which prevents multiple calls to `next_node`. --- doc/next-node-rules.md | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/doc/next-node-rules.md b/doc/next-node-rules.md index 9f47f6ab93b..23a0722e803 100644 --- a/doc/next-node-rules.md +++ b/doc/next-node-rules.md @@ -18,15 +18,54 @@ next_node(permitted: permitted_next_nodes) do |response| end ``` +## Shortcut + +If the next node for a question is always the same then you can call `next_node` with a single node key. This will automatically add the specified node key to the list of permitted next nodes i.e. there is no need to specify the `:permitted` option. + +```ruby +# For example +next_node :red +``` + ## Errors +### Multiple calls to next_node are not allowed + +Occurs if `next_node` is called more than once within a single question block. + +```ruby +# For example +next_node :red +next_node :green +``` + +### ArgumentError: You must specify at least one permitted next node + +Occurs if the list of permitted next nodes is empty. + +```ruby +# For example +next_node(permitted: []) do + :red +end +``` + +### ArgumentError: You must specify a block or a single next node key + +Occurs if `next_node` is called without a block and with no arguments. + +```ruby +# For example +next_node +``` + ### Next node undefined Occurs if the `next_node` block returns something "falsey" (e.g. `nil`). ```ruby # For example -next_node(permitted: []) do +next_node(permitted: [:red]) do nil end ```