easy_flow 0.14.0 → 0.15.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48b43c9ecd7f02841d19f9fd4f10f42ea49092641bd998fb5750d6b61d9d330f
4
- data.tar.gz: f6b12a0a88c69f17a8aa596dad74c310a936060babb7f76d0f0a7f0f486435fe
3
+ metadata.gz: dc8d494f2652d3eba11522abb8d79201452e751f582065bdd1f45cab4aaa2ce9
4
+ data.tar.gz: '080d31d4e434670298636fb01dbc0681b360d9437169ea34139fa0a4ea851b8c'
5
5
  SHA512:
6
- metadata.gz: b8e4a6145d9ba8309fc633274e280d96409978b18f8a8df957add151423d9b75d1365b606efd444ae6a91d9563c27c9a1bfe0355f3e1e946020fb34d03c963d1
7
- data.tar.gz: 48f5729ef10aa4b053ecf20583c91edbe7cf0f749e4623f85b84733ca6e2663c6e115df0f269a3e987f31d38ff4e4289840b4dc6856785d8611ab194e9d94281
6
+ metadata.gz: 7ab45f2b9fc5d078b20419da119ac4e34b1d5be0fc4953bd4625abb11a05b0a2833f01187463fd66e513c55adac4be6b7357c37b277730896fee5fbeb1941d1c
7
+ data.tar.gz: e8c3dd26565009f8e17a7f21116c69d892730c2cb908f5ee16d5e81e4880eebf8d5626ebf9383ccbd42ee80fcb67c0ef186a41513d4b3db29d1312906631e862
@@ -25,14 +25,23 @@ module EasyFlow
25
25
  end
26
26
 
27
27
  def update
28
- params[:back] ? progress.discard_last : record_submitted
28
+ params[:back] ? go_back : record_submitted
29
29
  redirect_to run_location(run)
30
30
  end
31
31
 
32
32
  private
33
33
 
34
+ def go_back
35
+ guide = runner_for(running_definition)
36
+ loop do
37
+ before = progress.recorded
38
+ progress.discard_last
39
+ break if progress.recorded == before || !guide.fills_in_next?(progress.recorded)
40
+ end
41
+ end
42
+
34
43
  def runner_for(definition)
35
- QuestionRunner.new(definition)
44
+ QuestionRunner.new(definition, host: flow_host)
36
45
  end
37
46
 
38
47
  def run_location(run)
@@ -121,7 +130,11 @@ module EasyFlow
121
130
 
122
131
  def one_answer_back(answers)
123
132
  answered = @guide.state_on_path(answers)
124
- answered.except(answered.keys.last)
133
+ loop do
134
+ answered = answered.except(answered.keys.last)
135
+ break if answered.empty? || !@guide.fills_in_next?(answered)
136
+ end
137
+ answered
125
138
  end
126
139
 
127
140
  def record_submitted
@@ -23,7 +23,7 @@ module EasyFlow
23
23
  end
24
24
 
25
25
  def advance
26
- Runner.new(pinned_definition).run(Progress::Kept.new(self))
26
+ Runner.new(pinned_definition, host: EasyFlow.host_named(flow.host)).run(Progress::Kept.new(self))
27
27
  end
28
28
 
29
29
  def pinned_definition
@@ -1,8 +1,9 @@
1
1
  module EasyFlow
2
2
  class Runner
3
- def initialize(document, registry: EasyFlow.registry)
3
+ def initialize(document, registry: EasyFlow.registry, host: nil)
4
4
  @document = document.to_h
5
5
  @registry = registry
6
+ @host = host
6
7
  @digest = Digest.new(Document.new(@document, registry: registry), registry: registry)
7
8
  end
8
9
 
@@ -23,7 +24,8 @@ module EasyFlow
23
24
  end
24
25
 
25
26
  def next_step(state, run: nil)
26
- shown(@digest.next_step(named(state)), run)
27
+ node = @digest.next_step(named(state))
28
+ narrowed(shown(node, run), node, state)
27
29
  end
28
30
 
29
31
  def run(progress)
@@ -34,6 +36,11 @@ module EasyFlow
34
36
  progress.ended unless node
35
37
  end
36
38
 
39
+ def fills_in_next?(state)
40
+ node = @digest.next_step(named(state))
41
+ node.present? && decided?(node, state)
42
+ end
43
+
37
44
  def drawing_at(state)
38
45
  Drawing.of(@digest.next_step(named(state)), @registry)
39
46
  end
@@ -61,7 +68,23 @@ module EasyFlow
61
68
  end
62
69
 
63
70
  def goes_on?(node, state)
64
- acts?(node) || ready?(node, state)
71
+ acts?(node) || ready?(node, state) || decided?(node, state)
72
+ end
73
+
74
+ def allowed(node, state)
75
+ @host&.answers_allowed(node, state.symbolize_keys)
76
+ end
77
+
78
+ def narrowed(display, node, state)
79
+ choices = node && allowed(node, state)
80
+ return display unless choices && display.respond_to?(:choices)
81
+
82
+ display.with(choices: display.choices.select { |choice| choices.include?(choice.value) })
83
+ end
84
+
85
+ def decided?(node, state)
86
+ choices = allowed(node, state)
87
+ !choices.nil? && choices.size <= 1
65
88
  end
66
89
 
67
90
  def ready?(node, state)
@@ -69,6 +92,8 @@ module EasyFlow
69
92
  end
70
93
 
71
94
  def result_of(node, state)
95
+ return allowed(node, state).first.to_s if decided?(node, state)
96
+
72
97
  acts?(node) ? @registry.fetch(node.type).process(node, state) : true
73
98
  end
74
99
 
@@ -1,7 +1,7 @@
1
1
  module EasyFlow
2
2
  class Host
3
3
  attr_reader :name
4
- attr_writer :layout, :admin_layout, :offers
4
+ attr_writer :layout, :admin_layout, :offers, :allowed_answers
5
5
  attr_accessor :admin_authentication_method, :visitor_authorization_method, :refusal_method, :owner_method
6
6
 
7
7
  def initialize(name)
@@ -24,6 +24,10 @@ module EasyFlow
24
24
  true
25
25
  end
26
26
 
27
+ def answers_allowed(step, answers)
28
+ @allowed_answers&.call(step, answers)
29
+ end
30
+
27
31
  def offers?(step_type)
28
32
  @offers.nil? || @offers.map(&:to_s).include?(step_type.to_s)
29
33
  end
@@ -1,3 +1,3 @@
1
1
  module EasyFlow
2
- VERSION = "0.14.0"
2
+ VERSION = "0.15.0"
3
3
  end
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: easy_flow-develop
3
- description: Use PROACTIVELY for adding a step type to easy_flow flows (a step that asks the visitor something, takes several answers at once, computes a value from earlier answers, picks the next branch, or holds the visitor until something outside the flow has happened), showing a step differently depending on the visitor's stored run, branching on what a visitor ticked on a checklist, moving a paused run on once what it waits for has happened, refusing a blank or invalid answer to a step with a message, offering an admin a step setting whose options are read from the app's own records, letting an admin pick another flow of the same host in a step's settings, adding a step type that starts another flow and works out which flow and version to run from its own settings, letting an admin edit a value stored on one of the app's own records from a step's settings on the canvas, serving a host's flows from the app's own controller and routes, starting a visitor's run on a published version of a flow other than the live one, acting when a visitor finishes a flow, acting on the output a visitor's flow ended with, keeping a flow from being published while a step that starts another flow names a flow or version that cannot run or leads back to the flow it sits in, finding the run of another flow that a Flow step started, the run a waiting run is waiting on, and the run that started it, sending a visitor back to the run that started a flow once that flow ends, handling a run whose starting run was removed with its flow, handling a Flow step that cannot start its flow or whose flow ended with no output, taking one file or several from a visitor at a step, showing a step's recorded answer by a label the step type works out, and reading a run's recorded answers and question labels, including every answer ticked on a checklist and every visit's answer when a flow loops back to a question already asked — MUST BE USED instead of hand-rolling questionnaire steps, checkbox lists, file upload steps, branching logic, number comparisons, hard-coded setting options, copying a record's value into a flow and back, answer validation, polling or "come back later" pages, flow controllers or answer lookups.
3
+ description: Use PROACTIVELY for adding a step type to easy_flow flows (a step that asks the visitor something, takes several answers at once, computes a value from earlier answers, picks the next branch, or holds the visitor until something outside the flow has happened), showing a step differently depending on the visitor's stored run, branching on what a visitor ticked on a checklist, moving a paused run on once what it waits for has happened, refusing a blank or invalid answer to a step with a message, offering an admin a step setting whose options are read from the app's own records, letting an admin pick another flow of the same host in a step's settings, adding a step type that starts another flow and works out which flow and version to run from its own settings, adding a step type that starts another flow chosen from the visitor's run, such as its owner or its answers, offering a visitor only the answers to a step that their earlier answers allow, filling in a step that has only one possible answer without asking it, skipping a step that does not apply to a visitor, letting an admin edit a value stored on one of the app's own records from a step's settings on the canvas, serving a host's flows from the app's own controller and routes, starting a visitor's run on a published version of a flow other than the live one, acting when a visitor finishes a flow, acting on the output a visitor's flow ended with, keeping a flow from being published while a step that starts another flow names a flow or version that cannot run or leads back to the flow it sits in, finding the run of another flow that a Flow step started, the run a waiting run is waiting on, and the run that started it, sending a visitor back to the run that started a flow once that flow ends, handling a run whose starting run was removed with its flow, handling a Flow step that cannot start its flow or whose flow ended with no output, taking one file or several from a visitor at a step, showing a step's recorded answer by a label the step type works out, and reading a run's recorded answers and question labels, including every answer ticked on a checklist and every visit's answer when a flow loops back to a question already asked — MUST BE USED instead of hand-rolling questionnaire steps, checkbox lists, file upload steps, branching logic, answer lists filtered by earlier answers, skipped questions, number comparisons, hard-coded setting options, copying a record's value into a flow and back, answer validation, polling or "come back later" pages, flow controllers or answer lookups.
4
4
  tools: Read, Write, Edit, Grep
5
5
  scope: guided flows — versioned documents of steps and the connections between them, drawn on a canvas by an admin and run by a visitor one step at a time, with step types the host registers
6
6
  ---
@@ -9,13 +9,14 @@ This local follows the steps below exactly and invents none. Where a step names
9
9
 
10
10
  ## What easy_flow is
11
11
 
12
- A Rails engine for flows an admin draws on a canvas and a visitor runs one step at a time. Each step is an instance of a step type. The engine ships nine: start (`:start`), end (`:terminal`, whose one setting is the output an admin writes for a flow that ends there), question (`:question`, a question with a list of answers of which the visitor picks one, which the admin can mark required so a blank answer is refused), checklist (`:checklist`, a question with a list of answers of which the visitor ticks any number, which the admin can mark required so a visitor who ticks nothing is refused), File upload (`:file_upload`, a question the visitor answers by choosing one file, with an admin setting listing the accepted file types as comma-separated extensions such as `.pdf, .png`, which the admin can mark required so a visitor who chooses no file is refused, and whose recorded answer is the reference the host's file store returned for the file), three that pick a branch from an earlier answer with no code — condition (`:condition`, is or is not a chosen value), switch (`:switch`, follows the connection labelled with the answer) and compare (`:compare`, reads the answer as a number and checks it by more than, less than, at least or at most against an amount) — and Flow (`:flow_step`, names another flow of the same host and the number of one of its versions, with one connection for each different output that version's End steps write, and named on the canvas by that flow's title and the version number, such as `Intake, version 2`). When a stored run stops at a Flow step, a run of the named flow is started on the named version, and the first run waits on the Flow step. When the second run ends with an output, that output is recorded in the first run at the Flow step, and the first run moves on along the connection labelled with that output. When the second run ends with no output, nothing is recorded and the first run stays waiting at the Flow step. On a question and on a checklist the admin can give each answer info, a longer explanation the visitor opens with an info button beside that answer. An admin may connect a step back to an earlier one, so a flow can ask the same question more than once, and each visit's answer is kept. A File upload step takes a file only in a flow the admin set to save each step. In a flow set to save on finish or to save nothing, a visitor who chooses a file is shown `A file can only be uploaded in a flow that saves its runs.` and cannot go on with it. Before writing a step type to ask for one answer or several, to take a file, or to branch on an answer or a number, ask the developer whether an admin placing one of the built-in types on the canvas is enough. None of condition, switch or compare reads a checklist's answer, so branching on what a visitor ticked takes a step type of the app's own that defines `route`. Use this local when the app needs a step type of its own, needs a host's flows on its own pages with its own behaviour when a visitor finishes, or needs to read what a visitor answered. It assumes easy_flow is already installed and a host is declared; if not, hand off to `easy_flow-install` first.
12
+ A Rails engine for flows an admin draws on a canvas and a visitor runs one step at a time. Each step is an instance of a step type. The engine ships nine: start (`:start`), end (`:terminal`, whose one setting is the output an admin writes for a flow that ends there), question (`:question`, a question with a list of answers of which the visitor picks one, which the admin can mark required so a blank answer is refused), checklist (`:checklist`, a question with a list of answers of which the visitor ticks any number, which the admin can mark required so a visitor who ticks nothing is refused), File upload (`:file_upload`, a question the visitor answers by choosing one file, with an admin setting listing the accepted file types as comma-separated extensions such as `.pdf, .png`, which the admin can mark required so a visitor who chooses no file is refused, and whose recorded answer is the reference the host's file store returned for the file), three that pick a branch from an earlier answer with no code — condition (`:condition`, is or is not a chosen value), switch (`:switch`, follows the connection labelled with the answer) and compare (`:compare`, reads the answer as a number and checks it by more than, less than, at least or at most against an amount) — and Flow (`:flow_step`, names another flow of the same host and the number of one of its versions, with one connection for each different output that version's End steps write, and named on the canvas by that flow's title and the version number, such as `Intake, version 2`). When a stored run stops at a Flow step, a run of the named flow is started on the named version, and the first run waits on the Flow step. When the second run ends with an output, that output is recorded in the first run at the Flow step, and the first run moves on along the connection labelled with that output. When the second run ends with no output, nothing is recorded and the first run stays waiting at the Flow step. On a question and on a checklist the admin can give each answer info, a longer explanation the visitor opens with an info button beside that answer. An admin may connect a step back to an earlier one, so a flow can ask the same question more than once, and each visit's answer is kept. A File upload step takes a file only in a flow the admin set to save each step. In a flow set to save on finish or to save nothing, a visitor who chooses a file is shown `A file can only be uploaded in a flow that saves its runs.` and cannot go on with it. Before writing a step type to ask for one answer or several, to take a file, or to branch on an answer or a number, ask the developer whether an admin placing one of the built-in types on the canvas is enough. None of condition, switch or compare reads a checklist's answer, so branching on what a visitor ticked takes a step type of the app's own that defines `route`. Use this local when the app needs a step type of its own, needs a host's flows on its own pages with its own behaviour when a visitor finishes, needs a visitor's earlier answers to decide which answers a later step offers, or needs to read what a visitor answered. It assumes easy_flow is already installed and a host is declared; if not, hand off to `easy_flow-install` first.
13
13
 
14
14
  ## Interface
15
15
 
16
16
  - `EasyFlow::Step` — a module a class includes to declare a step type with class-level words, registered with `.register`.
17
17
  - `EasyFlow.step` — declares and registers a step type in one call from a block of the same words, for a type small enough not to need its own class.
18
18
  - `labels_answer_by` — a word in a step type's declaration giving the label a run's recorded answer to a step of that type is shown by, on the completion page and from `EasyFlow::QuestionRunner#choice_label`.
19
+ - `host.allowed_answers=` — set on a host to a lambda given a step and the answers recorded so far, returning the answers that step may take in this run: several narrows the answers shown, one is recorded without showing the step, and none records a blank and skips it.
19
20
  - `EasyFlow::FlowsController` — the visitor controller; the app subclasses it to serve one host's flows from its own routes and to change what happens at the start, on each step and at the finish.
20
21
  - `hosted_by` — class method on a `FlowsController` subclass naming the host whose flows it serves.
21
22
  - `routed_by` — class method on a `FlowsController` subclass naming the prefix of the app's own route names that the controller redirects and links to.
@@ -125,6 +126,32 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
125
126
  ```
126
127
 
127
128
  Here `OfferFlow.for_plan` is the app's own lookup, returning `{ flow: <id>, version: <number> }` or `nil`.
129
+ - `chooses_flow_from_run { |node, run| { flow: ..., version: ... } }` — for a `starts_a_flow` type, works out the flow and version from the stored run that reached the step, such as its `owner` or its `recorded` answers, instead of from the step's settings. Ask the developer what in the run decides the flow and version, and how. Do not pick.
130
+ - The block is given the node and the stored `EasyFlow::Run`. It returns a hash with the symbol keys `flow:`, the flow's id as an integer or a string, and `version:`, the version's number.
131
+ - It is called only when a stored run stops at the step, once per visit, before that visit's run is started. It is never called on the canvas, when the step is named, or when the flow is saved or published.
132
+ - A type that declares it never reads `chooses_flow` or the settings named `flow` and `version`, so it needs neither setting.
133
+ - Since the flow is not known until a run reaches the step, the step is never marked `missing flow`, `unrunnable version` or `circular`, saving and publishing are never refused for it, and nothing checks that the chosen flow does not lead back to the flow the step is in. Clicking the step on the canvas opens its settings.
134
+ - A step of the type with no `names_by` is named by its id on the canvas and by its `step_name` in `Waiting for <name>.`, so declare `names_by` or a `step_name` a visitor can read.
135
+ - The flow is found by its id alone, not through the host or owner. Ask the developer which flows the block may name, and look the flow up through the run's flow's host or the run's owner inside the block when it must be limited.
136
+ - When the block returns `nil`, a blank `flow:`, or the id of no existing flow, `EasyFlow::InnerFlowError` is raised with a message naming the step, such as `The step pick chose no flow to run`, and no run is started. A retired or withdrawn version raises `EasyFlow::InnerFlowError` as for a Flow step, and a blank version number, one naming no version of the flow, or one naming a draft raises `ActiveRecord::RecordInvalid`.
137
+ - When the started run ends with an output, that output is recorded under the step's id as for a Flow step. When it ends with no output, `"ended"` is recorded instead, and the parent run moves on. A type that routes on what is recorded declares an `output` whose `values:` include `"ended"` beside the outputs the chosen flows can end with. Ask the developer what `"ended"` should lead to.
138
+ - A flow set to save on finish or to save nothing, and an admin's preview, keep no stored run, so the block is not called and no run is started.
139
+
140
+ ```ruby
141
+ module FlowSteps
142
+ class Plan
143
+ include EasyFlow::Step
144
+
145
+ step_name "Plan"
146
+
147
+ starts_a_flow
148
+ waits_until { |_node, _state| false }
149
+ chooses_flow_from_run { |_node, run| PlanFlow.for_customer(run.owner) }
150
+ end
151
+ end
152
+ ```
153
+
154
+ Here `PlanFlow.for_customer` is the app's own lookup, returning `{ flow: <id>, version: <number> }` or `nil`.
128
155
  - `displays_by { |node| ... }` or `displays_by { |node, run| ... }` — builds the object handed to the step's partial as the local `step`. Without it the partial receives the node itself. A block that takes two arguments is also given the visitor's stored `EasyFlow::Run`, which is `nil` when the flow keeps no stored run at this point and in an admin's preview, so the block must handle `nil`. The block is also called with a `nil` run once for each step already answered every time a step page is shown, to count progress, so it must not be slow and must not change anything.
129
156
  - `drawn_by "<partial>"` — the partial that draws this type for a visitor. Without it the host's default drawing is used, which draws one answer to pick and expects `step.id`, `step.text` and `step.choices`, each choice answering `value`, `label`, `hint` and `info`, so a type with its own display shape needs its own partial.
130
157
  6. For a type that computes or routes, define instance methods on the class (or `process { |node, state| ... }` / `route { |node, state| ... }` in the block form):
@@ -216,7 +243,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
216
243
  - The page does not reload itself. Ask the developer how the run should move on when what it waits for happens:
217
244
  - The visitor reloads the page. This needs nothing more, and works whether or not the flow keeps a stored run.
218
245
  - The app calls `run.advance` where the event is handled, such as a webhook or a job. See "Read a run and its answers". This needs a stored run, so it only applies to a flow the admin set to save each step.
219
- 9. Restart the server, open a flow on the canvas of each host that should offer the type, and check the type is in the palette, its settings show, and a preview walks through it. On a host that names a list of offered step types without the type's id, check the type is not in the palette. For a type that declares `starts_a_flow`, check the step is marked `Runs a flow`, that once a flow is picked in its settings clicking the step goes to that flow's page, that the `Back to` button on that page returns to the flow the step is in, and that its `Settings` button opens its settings. Also check that the step shows `missing flow` while no flow is picked, `unrunnable version` while the version number names no live or replaced version, `circular` while it names the flow it sits in, and that publishing is refused until each is fixed. For a type that declares `chooses_flow`, set the settings the block reads and check that clicking the step goes to the flow the block names, that a type with no `names_by` is named on the canvas by that flow's title and version, and that a run stopping at the step starts a run of that flow on that version.
246
+ 9. Restart the server, open a flow on the canvas of each host that should offer the type, and check the type is in the palette, its settings show, and a preview walks through it. On a host that names a list of offered step types without the type's id, check the type is not in the palette. For a type that declares `starts_a_flow`, check the step is marked `Runs a flow`, that once a flow is picked in its settings clicking the step goes to that flow's page, that the `Back to` button on that page returns to the flow the step is in, and that its `Settings` button opens its settings. Also check that the step shows `missing flow` while no flow is picked, `unrunnable version` while the version number names no live or replaced version, `circular` while it names the flow it sits in, and that publishing is refused until each is fixed. For a type that declares `chooses_flow`, set the settings the block reads and check that clicking the step goes to the flow the block names, that a type with no `names_by` is named on the canvas by that flow's title and version, and that a run stopping at the step starts a run of that flow on that version. For a type that declares `chooses_flow_from_run`, start a stored run whose owner or answers the block reads, walk it to the step, and check `run.waiting_on` is a run of the flow and version the block names, and that a run for which the block names no flow raises `EasyFlow::InnerFlowError`.
220
247
 
221
248
  ### Keep a setting on one of the app's records
222
249
 
@@ -272,7 +299,7 @@ Use this when an admin should edit a value that belongs to one of the app's own
272
299
  4. Override only the private methods the developer's answers call for:
273
300
  - `finished(answers, run)` — called when the visitor reaches the end. `answers` is the answers on the path taken, keyed by step id as symbols, each a string or, for a checklist, an array of strings. `run` is the stored `EasyFlow::Run`, or `nil` when the admin set the flow to save nothing. A flow the admin set to save on finish gets its run created at this point. To act on which way the flow ended, read `run.output`; when `run` is `nil` there is no output to read. It must render or redirect. Default: the engine's completion page, which lists each answer on the path taken by its label, a checklist's answer as the labels of everything ticked joined into one sentence, and a File upload step's answer as the name the host's file store gives the file.
274
301
  - `start_run(flow)` — creates the run when a visitor starts a flow the admin set to save each step. Call `super` and change the run it returns, for example to set its `owner` or `label`. To start the run on a version other than the live one, return `EasyFlow::Run.start(flow, version: ...)` instead of calling `super`; see "Start a run on a chosen version".
275
- - `runner_for(definition)` — returns the runner used for each step. Return a subclass of `EasyFlow::QuestionRunner` to change what the step and completion pages read from it.
302
+ - `runner_for(definition)` — returns the runner used for each step. Return a subclass of `EasyFlow::QuestionRunner` to change what the step and completion pages read from it, built with `host: flow_host`, such as `IntakeRunner.new(definition, host: flow_host)`. A runner built without `host:` ignores the host's `allowed_answers`, so every step offers every answer and none is filled in or skipped.
276
303
  5. Visit `/<path>/<slug>` for a published flow and walk it to the end.
277
304
 
278
305
  ### Start a run on a chosen version
@@ -301,7 +328,7 @@ Use this when a visitor's run reaches a Flow step and the app must take the visi
301
328
 
302
329
  1. This needs the engine's migrations installed, which `easy_flow-install` does.
303
330
  2. What holds when a stored run stops at a Flow step, whether on the visitor's page load or on `run.advance`:
304
- - A run of the flow named in the step's settings is started on the version with the number in its settings, or of the flow and version its type's `chooses_flow` block returns. The flow is found by its id alone and not through the host, since a `:flow` setting is not checked on save.
331
+ - A run of the flow named in the step's settings is started on the version with the number in its settings, or of the flow and version its type's `chooses_flow` or `chooses_flow_from_run` block returns. The flow is found by its id alone and not through the host, since a `:flow` setting is not checked on save.
305
332
  - The new run's `parent_run` is the run that reached the step, and its `parent_step` is the step's id as a string, or the visit key such as `"sub@2"` on a later visit in a loop.
306
333
  - The new run is moved on at once past every step that acts without the visitor, the same as `run.advance`. When it stops at a Flow step of its own, that starts a further run the same way.
307
334
  - It is started once per visit to the step, so loading the page again or calling `advance` again starts no second one.
@@ -320,7 +347,7 @@ Use this when a visitor's run reaches a Flow step and the app must take the visi
320
347
  - When the parent run then ends and was itself started by a Flow step, its own output is handed to its parent the same way.
321
348
  - The new run's own `finished` is still called on its visitor's page load. The job does not call `finished` for the parent run, and if the parent run reaches its end, `finished` is called the next time its visitor loads its page.
322
349
  - A visitor who presses Back on the step after a Flow step removes the recorded output, and the parent run waits on the Flow step again. No second run is started, and the parent run moves on again only when the new run's page is loaded or `advance` is called on it.
323
- 4. What holds when the new run ends with no output, which happens when it ends on an End step whose output was left blank, or ends without reaching an End step:
350
+ 4. What holds when the new run ends with no output, which happens when it ends on an End step whose output was left blank, or ends without reaching an End step, and the step that started it is not of a type that declares `chooses_flow_from_run` (for that type, `"ended"` is recorded and the parent run moves on):
324
351
  - Nothing is recorded in the parent run, and the parent run stays at the Flow step showing `Waiting for <name>.` on every page load. Nothing in the engine moves it on.
325
352
  - A job is still queued that fails with `EasyFlow::InnerFlowError` and a message naming the Flow step's id, such as `Nothing was recorded at the Flow step sub, so its run cannot move on`. The job does not move the parent run.
326
353
  - Each later page load of the new run, and each later `advance` on it, queues another job that fails the same way.
@@ -353,6 +380,39 @@ Use this when a visitor's run reaches a Flow step and the app must take the visi
353
380
  12. Ask the developer what should happen when a named flow ends with no output and its parent run is left waiting, such as admins writing an output on every End step of a flow another flow names, or the app watching for the failed job's `EasyFlow::InnerFlowError` and acting on the parent run. Do not pick. Publishing does not refuse a named flow whose End step has a blank output.
354
381
  13. Publish a flow with a Flow step set to save each step, start a run with an owner, walk it to the Flow step, and check `run.waiting_on` is one run of the named flow with that run as its `parent_run` and the same `owner`, and that reloading the page starts no second one. Walk the new run to an End step, and check the parent run records that End step's output at the Flow step and moves on along the connection labelled with it. Walk another new run to an End step with a blank output, and check the parent run stays waiting and the queued job fails with `EasyFlow::InnerFlowError`. While a parent run waits on a new run, check that removing the named flow is refused. Remove the parent run's flow, and check the new run is kept with no `parent_run` and that finishing it neither raises nor queues a job.
355
382
 
383
+ ### Decide which answers a step offers in a run
384
+
385
+ Use this when what a visitor answered earlier decides which answers a later step may take, such as a product sold as a download only being offered `download`.
386
+
387
+ 1. Ask the developer which steps of which host's flows this applies to, which earlier answers decide it, and which answers each combination allows. Do not pick.
388
+ 2. Inside the block that declares the host, set through `easy_flow-install`, assign a lambda:
389
+
390
+ ```ruby
391
+ host.allowed_answers = ->(step, answers) do
392
+ case step.id.split("@").first
393
+ when "sold_as" then %w[download] if answers[:kind] == "pdf"
394
+ end
395
+ end
396
+ ```
397
+
398
+ - `step` is the step the run has stopped at, with `id`, `type` and `config`. On a later visit to a step in a flow that loops, its `id` is the visit key `<id>@<n>`, so compare the part before `@` to match every visit.
399
+ - `answers` is the answers recorded so far, keyed by step id as symbols, with later visits under `:"<id>@<n>"`. A value is a string, an array of strings, or what a computing step recorded.
400
+ - The lambda is called for every step the run stops at: one that awaits input, one that computes a value, one that waits, and a Flow step. It is not called for a step that only routes, a start or an end.
401
+ - It is called several times on every page load and on every `run.advance`, so it must not be slow and must not change anything.
402
+ 3. What it returns decides the step:
403
+ - `nil` — the step offers every answer and behaves as it would with no lambda.
404
+ - An array of two or more strings — the visitor is shown only the answers whose value is in the array, compared exactly as strings. This narrows a question, a checklist, and an app type whose display object answers `choices` and `with(choices:)`, such as one built with `Data.define`. Any other display is shown unchanged.
405
+ - An array of one string — that string is recorded under the step's id without the step being shown, and the run moves on.
406
+ - An empty array — `""` is recorded under the step's id without the step being shown, and the run moves on.
407
+ - For one or none, what is recorded replaces whatever the step's type would do: a computing step's `process` is not called, a waiting step does not wait, and a Flow step starts no run.
408
+ 4. What then holds:
409
+ - It applies on the visitor's step pages of the host's flows, whether the flow keeps a stored run or carries its answers in the page, in an admin's preview, and on `run.advance`, which reads the host the run's flow belongs to.
410
+ - A runner the app builds itself, such as `EasyFlow::QuestionRunner.new(run.pinned_definition)`, ignores it unless built with `host:`.
411
+ - The Back button removes every answer the host filled in after the last step the visitor was shown, and the visitor's answer to that step, and shows that step again.
412
+ - A filled-in or skipped step's answer is in `run.recorded` and on the completion page like any other answer, `""` for a skipped one.
413
+ - The array narrows only what is shown. A submitted value outside it is still recorded unless the step type's `answer_check` refuses it, and `answer_check` is not given the earlier answers. Ask the developer whether that is acceptable.
414
+ 5. Publish a flow on the host, walk it with earlier answers that allow several, one and no answers to a step, and check the step shows only the allowed answers, is filled in, and is skipped. Press Back after a filled-in step and check the visitor returns to the question before it.
415
+
356
416
  ### Read a run and its answers
357
417
 
358
418
  1. Find runs scoped to one host so one host never sees another's: `EasyFlow::Run.joins(:flow).where(easy_flow_definitions: { host: "intake" })`.
@@ -380,7 +440,7 @@ Use this when a visitor's run reaches a Flow step and the app must take the visi
380
440
  - `question_text(id)` — the text of a question or checklist step, given either its id or a visit key such as `"job@2"`. For a step of any other type it returns that step's `question` setting, or `nil` when it has none.
381
441
  - `choice_label(id, value)` — the label the step's type gives the answer with `labels_answer_by`, or else the label of one chosen answer on a question or checklist, or else the value itself. For a File upload step it is the name the host's file store gives the recorded reference, or the reference itself when the store gives none. It takes a visit key the same way as `question_text`. Handed a checklist's array, it returns one string, the label of each ticked value joined into a sentence such as `Email, Phone, and Post`, and an empty string when nothing was ticked. Ask the developer whether a checklist's answers are shown as that one sentence or each on its own; for each on its own, call it once per ticked value with `value.map { |ticked| runner.choice_label(step_id, ticked) }`.
382
442
  - `step(id)` finds the step for either its id or a visit key.
383
- - `next_step(answers, run: nil)` — the next step to show, built by its type's `displays_by`. Pass `run:` when a type's display block takes the run.
443
+ - `next_step(answers, run: nil)` — the next step to show, built by its type's `displays_by`. Pass `run:` when a type's display block takes the run. Its answers are narrowed by the host's `allowed_answers` only when the runner was built with `host:`.
384
444
  - `steps_on_path(answers)` — the steps on the path taken, each built by its type's `displays_by` with a `nil` run.
385
445
  - `steps`, `slug` and `headline` read the rest of the document.
386
446
 
@@ -395,9 +455,14 @@ Use this when a visitor's run reaches a Flow step and the app must take the visi
395
455
  - A setting declared with `kept_on:` lives only on the app's record, written when the admin saves the step, so a step's code never finds it in `node.config`.
396
456
  - A `kept_on:` lambda is given only the step's settings, so it cannot limit which records an admin reaches by admin, account or host.
397
457
  - A `:flow` setting holds a flow's id as a string and is not checked against the flows offered, so code reading it looks the flow up through the host.
398
- - A `starts_a_flow` type reads the flow and version to run from the settings named `flow` and `version`, unless it declares `chooses_flow` and that block returns a choice.
458
+ - A `starts_a_flow` type reads the flow and version to run from its `chooses_flow_from_run` block when it declares one, and otherwise from the settings named `flow` and `version`, unless it declares `chooses_flow` and that block returns a choice.
399
459
  - A `chooses_flow` block is given only the step, finds the flow by id with no host or owner scope, and is called on every canvas draw, name shown, save and publish, so it limits the flows it names itself and changes nothing.
400
460
  - A step of a `chooses_flow` type with no `names_by` is named by the chosen flow's title and version number.
461
+ - A `chooses_flow_from_run` block is called only when a stored run stops at the step, finds the flow by id with no host or owner scope, and is never checked on save or publish, so the block limits the flows it names itself.
462
+ - A run started by a `chooses_flow_from_run` step that ends with no output records `"ended"` at that step, and the parent run moves on.
463
+ - A host's `allowed_answers` lambda is given only the step and the answers recorded so far, and is called many times per page load, so it reads only those and changes nothing.
464
+ - A step the host allows one answer is recorded without being shown, and one it allows none is recorded as `""` and skipped, whatever the step's type.
465
+ - A runner the app builds without `host:` ignores the host's `allowed_answers`.
401
466
  - A flow holding a `starts_a_flow` step whose `flow` names no existing flow, or whose `version` names no live or replaced version of it, or whose `flow` leads back to the flow the step sits in directly or through other flows, cannot be saved as a version or published, and these checks cannot be turned off.
402
467
  - On the canvas, clicking a step of a `starts_a_flow` type whose `flow` setting names a flow goes to that flow's page, which has a `Back to` button to the flow the step is in, and the step's settings open from its `Settings` button.
403
468
  - A type that routes declares the values its output takes, or the canvas offers no connections to label.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: easy_flow-info
3
- description: Use to learn what easy_flow offers — guided flows an admin draws on a canvas and a visitor runs one step at a time, versioned documents of steps and connections, flows that loop back to ask a step again, hosts and the step types each offers its admins, runs and the published version each starts on, and the output a flow gives when it ends, the built-in step types including questions and checklists that can require an answer and explain each answer behind an info button, file upload steps that hand a visitor's file to the app's file store and read it back by its name, and branching on an answer or comparing a number, Flow steps that name another flow, open its canvas when their card is clicked with a button back to the flow it was opened from, start a run of it for the same owner when a stored run reaches them, are flagged when the flow or version they name cannot run or leads back to the flow they sit in, let the waiting run name that inner run, move that run on along the output it ends with, and fail with an error naming the step when its version was taken out of service or its inner run ended with no output, removing a flow and why it is refused while a waiting run depends on it, step types the host app registers, including ones that start another flow and work out which flow and version to run from their own settings and are named after that flow, the checks they make on an answer, what a step shows a visitor and how that can depend on the visitor's run, and steps that hold a run until something outside the flow has happened, and the settings an admin fills in, including picks whose options come from the app's data and settings kept on one of the app's own records.
3
+ description: Use to learn what easy_flow offers — guided flows an admin draws on a canvas and a visitor runs one step at a time, versioned documents of steps and connections, flows that loop back to ask a step again, hosts and the step types each offers its admins, runs and the published version each starts on, and the output a flow gives when it ends, the built-in step types including questions and checklists that can require an answer and explain each answer behind an info button, file upload steps that hand a visitor's file to the app's file store and read it back by its name, and branching on an answer or comparing a number, Flow steps that name another flow, open its canvas when their card is clicked with a button back to the flow it was opened from, start a run of it for the same owner when a stored run reaches them, are flagged when the flow or version they name cannot run or leads back to the flow they sit in, let the waiting run name that inner run, move that run on along the output it ends with, and fail with an error naming the step when its version was taken out of service or its inner run ended with no output, removing a flow and why it is refused while a waiting run depends on it, step types the host app registers, including ones that start another flow and work out which flow and version to run from their own settings and are named after that flow, or choose it from the run they are in, the answers a host allows a step in a run and the steps it fills in or passes over, the checks they make on an answer, what a step shows a visitor and how that can depend on the visitor's run, and steps that hold a run until something outside the flow has happened, and the settings an admin fills in, including picks whose options come from the app's data and settings kept on one of the app's own records.
4
4
  tools: Read
5
5
  scope: guided flows — versioned documents of steps and the connections between them, drawn on a canvas by an admin and run by a visitor one step at a time, with step types the host registers
6
6
  ---
@@ -18,13 +18,13 @@ Reach for it when an app needs a questionnaire, an intake form, or any decision
18
18
  easy_flow declares no commands of its own for this local. Its surface is split between the other two:
19
19
 
20
20
  - **easy_flow-install** owns putting the engine into an app: its migrations, mounting it, choosing the controller it inherits from, naming hosts and the step types each one offers, choosing the default step drawing, setting the file store that keeps uploaded files, the flow checks, and the admin pages.
21
- - **easy_flow-develop** owns building on it: declaring and registering step types, including ones that read an answer back under a label of their own, ones that wait, ones that start another flow, and ones with settings kept on the app's own records, moving a run on once what it waits for has happened, serving a host's flows from the app's own controllers and routes, and starting a run on a chosen published version, reading a visitor's run, its answers, the inner run it is waiting on and the output its flow ended with.
21
+ - **easy_flow-develop** owns building on it: declaring and registering step types, including ones that read an answer back under a label of their own, ones that wait, ones that start another flow, and ones with settings kept on the app's own records, narrowing the answers a host allows a step in a run, moving a run on once what it waits for has happened, serving a host's flows from the app's own controllers and routes, and starting a run on a chosen published version, reading a visitor's run, its answers, the inner run it is waiting on and the output its flow ended with.
22
22
 
23
23
  ## How to use it
24
24
 
25
25
  - To get easy_flow running in an app, or to change how it is configured, including which step types a host's admins can add and where uploaded files are kept, use **easy_flow-install**.
26
26
  - To ask a visitor for a file, an admin places a file upload step on the canvas, and the app must first have a file store set through **easy_flow-install**.
27
- - To add a step type, give a step type its own rule for refusing an answer, give it its own way to read an answer back, make what a step shows depend on the visitor's run, hold a run until something outside the flow has happened, add a step type of the app's own that starts another flow, keep a step's setting on one of the app's own records, put a flow on the app's own pages, start a run on a published version other than the live one, act on what a visitor answered, or act on the output a visitor's flow ended with, use **easy_flow-develop**.
27
+ - To add a step type, give a step type its own rule for refusing an answer, give it its own way to read an answer back, make what a step shows depend on the visitor's run, hold a run until something outside the flow has happened, add a step type of the app's own that starts another flow, including one that chooses the flow from the run it is in, limit which answers a step offers in a run from the answers given so far, keep a step's setting on one of the app's own records, put a flow on the app's own pages, start a run on a published version other than the live one, act on what a visitor answered, or act on the output a visitor's flow ended with, use **easy_flow-develop**.
28
28
  - To give a flow a different output for each way it can end, no code is needed: an admin fills in the output on each End step on the canvas.
29
29
  - To branch a flow on an answer or on a number the visitor gave, no code is needed: an admin places one of the built-in branching steps on the canvas.
30
30
  - To make a visitor answer a question or tick something on a checklist before going on, no code is needed: an admin marks that step as required on the canvas.
@@ -67,13 +67,15 @@ easy_flow declares no commands of its own for this local. Its surface is split b
67
67
  - **Flow step** — a built-in step that names another flow and the number of one of its versions. The admin picks the flow from the same host's flows, listed by title as the admin's list of flows shows them. The step has one connection for each different output the End steps of that version write, and none when no flow or version is picked or no End step there has an output. On the canvas its card is named after the flow's title and the version, such as "Onboarding, version 2", and is marked "Runs a flow". Once a flow is picked, clicking the card opens that flow's canvas, and the card's Settings button opens the step's own settings instead. A canvas opened from a Flow step's card shows a "Back to" button named after the title of the flow it was opened from, or its slug when it has no title, which returns to that flow's canvas. The button only appears when that flow belongs to the same host, and to the same owner when the host has one. While the admin is connecting steps, clicking the card picks it as the connection's target. A run that reaches a Flow step stops there. When the run is a stored run, it starts one inner run of the named flow on the picked version. A run that carries its answers in the page, and an admin's preview, stop at the Flow step, start no run, and never move past it.
68
68
  - **Unrunnable Flow step** — a Flow step whose picked flow or version cannot be run. Its card shows "missing flow" when no flow is picked or the picked flow no longer exists, and "unrunnable version" when the picked flow has no version with that number or that version is a draft, retired or withdrawn. These are always on, unlike the optional checks below, and the flow holding such a step cannot be saved as a new version or published until it is fixed.
69
69
  - **Circular Flow step** — a Flow step that leads back to the flow it sits in. That is either a Flow step naming its own flow, or one naming a flow whose picked version holds a Flow step that leads back the same way, through any number of flows. Each flow along the way is read at the version its Flow step picked. Its card shows "circular", and like an unrunnable Flow step this is always on and keeps the flow from being saved as a new version or published until it is fixed.
70
- - **Flow-starting step type** — a step type of the app's own that starts another flow the way the Flow step does. By default it runs the flow and version named in its settings called flow and version. It may instead carry a rule that works out the flow and version from its other settings, and that choice is then used everywhere a Flow step's choice is: starting the inner run, the unrunnable and circular checks, and the canvas link to that flow. When the step type gives the step no name of its own, the step is named after the flow and version that rule works out, the same way a Flow step is, such as "Onboarding, version 2", on its canvas card and in the "Waiting for" line a visitor sees. Everything said below about a Flow step's inner run and handing back applies to it too.
70
+ - **Flow-starting step type** — a step type of the app's own that starts another flow the way the Flow step does. By default it runs the flow and version named in its settings called flow and version. It may instead carry a rule that works out the flow and version from its other settings, and that choice is then used everywhere a Flow step's choice is: starting the inner run, the unrunnable and circular checks, and the canvas link to that flow. When the step type gives the step no name of its own, the step is named after the flow and version that rule works out, the same way a Flow step is, such as "Onboarding, version 2", on its canvas card and in the "Waiting for" line a visitor sees. Everything said below about a Flow step's inner run and handing back applies to it too, except where a run-chosen flow says otherwise.
71
+ - **Run-chosen flow** — a flow-starting step type of the app's own that chooses the flow and version from the run it is in rather than from its settings, so two runs of one flow can start two different inner flows from the same step. Where there is no run, such as on the canvas, it has no chosen flow. Its steps are left out of the unrunnable and circular checks, so a flow holding one can be published with no flow in its settings. A run that reaches it and has no flow chosen fails with an error naming the step. When its inner run ends with no output, the step records `ended` and its run moves on.
71
72
  - **Inner run** — the run a Flow step starts. It records which run started it and at which step, and that run holds at most one inner run for each Flow step, however often it is moved on. It belongs to the same owner as the run that started it. When the picked version has been retired or withdrawn, no inner run is started and the run that reached the Flow step fails with an error naming that step. While the run that started it is stopped at the Flow step, that run can name the inner run it is waiting on, and it names none when it is stopped at any other step or has finished. Its steps that act on their own run as soon as it starts, and it stops at its first step that asks for input or waits.
72
73
  - **Handing back** — what happens when an inner run ends. Its flow output is recorded in the run that started it, as that run's answer at the Flow step, and a background job then moves that run on along the connection labelled with the output. An inner run that ended with no output records nothing in the run that started it, and the background job then fails with an error naming the Flow step, so that run does not move on. An output is handed back only once, so an inner run moved on again after it ended changes nothing. Because the move happens in a background job, the app must run its jobs for a run to go past a Flow step.
73
74
  - **Waiting step** — a step whose type carries a rule saying whether what it waits for has happened, such as a payment clearing or a document being signed. A run that reaches it stops there, and the visitor sees "Waiting for" followed by the step's name, with no form to submit. Once the rule says it has happened, the run moves past the step the next time the visitor's page is loaded or the app moves the run on, and the step is recorded as `true`.
74
75
  - **Registry** — the list of step types the app has registered, alongside the built-in ones. A step whose type is not registered is not shown or run as that type, and its answers are not checked.
75
- - **Host** — a named part of the app that owns a set of flows. A host sets the visitor layout and the admin layout, how admins are authenticated, how visitors are authorized, how a refusal is answered, and optionally which step types it offers. Flows are always looked up through a host, so one host never sees another's flows.
76
+ - **Host** — a named part of the app that owns a set of flows. A host sets the visitor layout and the admin layout, how admins are authenticated, how visitors are authorized, how a refusal is answered, and optionally which step types it offers and which answers it allows a step in a run. Flows are always looked up through a host, so one host never sees another's flows.
76
77
  - **Offered step types** — the list of step types a host names for its admins to add. A host that names no list offers every registered step type. The list only narrows the palette.
78
+ - **Allowed answers** — a rule a host may carry that looks at a step and the answers given so far in a run and names the answer values that step allows. When it names nothing for a step, the step offers every answer. When it names several, a question or checklist offers only those. When it names exactly one, the step records that answer without being shown, and when it names none, the step is passed over and records a blank answer. This holds in a stored run, a run moved on outside a visitor's request, a flow that carries its answers in the page, and an admin's preview. Going back skips the steps the host filled in and returns to the last step the visitor was shown.
77
79
  - **Palette** — the step types an admin can add on a host's canvas. It never holds the start step, always holds the end step, and otherwise holds the step types the host offers. A step already in a flow whose type the host does not offer is still drawn and run.
78
80
  - **Run** — one visitor's pass through a flow. A run may belong to one of the app's own records, its owner, which the app sets. A run is pinned to the version it started on, so publishing a new version does not change a run already under way. A run starts on the live version unless the app chooses another of the flow's published versions. A run is refused when the chosen version is a draft, is retired or withdrawn, or belongs to another flow. It records each answer by visit, and going back discards the last answer on the path.
79
81
  - **Stored run** and **carried answers** — the two ways a visitor's progress is held. A stored run is saved by the app and found again by its id. A flow that keeps nothing carries the answers given so far in the page itself and sends them along with each step.
@@ -114,6 +114,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
114
114
  - After install, check that removing a flow from `<mount path>/manage/flows` shows "Flow removed." and takes the flow off the list. Removing a flow deletes its versions and its runs, and needs no setting in the initializer.
115
115
  - After publishing a flow with a Flow step, check that while a run of it is stopped at that Flow step, removing the chosen flow is refused and the flow list shows "This flow cannot be removed while" followed by the waiting flow's title, or its slug when it has no title, and "waits on one of its runs". If no message is shown, check that the admin layout shows `flash[:alert]`.
116
116
  - A step type of the app's own that starts another flow is treated as a Flow step by every check above: the "missing flow", "unrunnable version" and "circular" flags, the refusal to remove a flow a waiting run depends on, and the job on `default`. It runs the flow and version its own settings name, or the ones its declaration works out from its other settings, and needs no setting in the initializer beyond being listed in a host's `offers` when that host sets `offers`.
117
+ - A step type of the app's own that chooses the flow to start from the run it is in is the exception. It is never flagged "missing flow", "unrunnable version" or "circular", so a flow holding it can be published with no flow in its settings. When a run reaches it and it chooses no flow, the engine raises `EasyFlow::InnerFlowError` naming the step and starts no run, and the visitor gets the app's error page. When the run it started ends at an End step with no output, the engine records `ended` as the step's answer and the job on `default` moves the run on from it, rather than failing.
117
118
  - After setting `EasyFlow.file_store`, publish a flow with a File upload step whose "What this keeps of a run" detail is "Every answer as it is given", upload a file as a visitor, and check that the completion page shows the name the store gives it.
118
119
  - A File upload step takes a file only in a flow that keeps every answer as it is given. In a flow that keeps nothing or keeps the run only once it finishes, the visitor is told "A file can only be uploaded in a flow that saves its runs." and cannot go on with a file chosen.
119
120
  - A File upload step's "Accepted file types" setting is a comma-separated list of extensions, such as `.pdf, .png`. A file of another kind is refused with a message naming the accepted kinds and is never passed to the file store.
@@ -14,6 +14,7 @@ develop:
14
14
  - EasyFlow::Step
15
15
  - EasyFlow.step
16
16
  - labels_answer_by
17
+ - host.allowed_answers=
17
18
  - EasyFlow::FlowsController
18
19
  - hosted_by
19
20
  - routed_by
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easy_flow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - tylercschneider