easy_flow 0.4.6 → 0.4.7

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: 05357fc005fef8fd09a227e8f442d9b1a83a126d903dbb221e773bb95eb88e9b
4
- data.tar.gz: 5a064304582303af7139339bb9302154d44adca753a5769e1ef6b69080b22670
3
+ metadata.gz: 92d52fb553f82cf1f206f0257d5d4fdaa0718ec6197ccabf016e6b20107371da
4
+ data.tar.gz: 66a5f5930c064d692b3c8b410174119e697c97b6a9ec47e8e9bffccd7abf7f5c
5
5
  SHA512:
6
- metadata.gz: ed1aa58b5926b761caa482fabe4b1c001528b26f026e754bd1ca0b97aaca928c6622cf4455195943e964162aebf47672469658513912a0b9f610b64b3d34dbf3
7
- data.tar.gz: 9d1fff992c17882c57062fff559f1833f6b33290efed0034a10621158bf6d42a540943a99977e9d7dfca7cd22b6bd4562b36f08755c39dff5e03bdf8b5fdb6de
6
+ metadata.gz: a982c31c2a100d8f74b6d75346200f68c1aad256e2ff87b24267fc509fca46ef19da19fb169f48798d4f33cf44d6dee011164f2a7df3dc4107b9b1cdbe081b43
7
+ data.tar.gz: 97ee4e459a2c25a0ef77b5433e056cfa83ccc40a9b0cf73abad201b9fcec9d5ea6e8079396d6d7ad4ed3ec904d7035c103b9ba045fd81d394096c339903cda80
@@ -17,6 +17,7 @@ module EasyFlow
17
17
  @answers = @progress.recorded
18
18
  @question = @guide.next_step(@answers)
19
19
  @drawing = @guide.drawing_at(@answers)
20
+ flash.now[:alert] = @refused if @refused
20
21
  return render :step if @question
21
22
 
22
23
  render_completion
@@ -103,12 +104,30 @@ module EasyFlow
103
104
  end
104
105
 
105
106
  def submitted_answers
106
- params.fetch(:answers, {}).permit(*@guide.steps.map(&:id)).to_h.symbolize_keys
107
+ answers = params.fetch(:answers, {}).permit(*@guide.steps.map(&:id)).to_h.symbolize_keys
108
+ asked = @guide.step(params[:asked].to_s)
109
+ return answers unless asked
110
+
111
+ answer = answers.fetch(asked.id.to_sym, "")
112
+ @refused = answer_problem(asked, answer)
113
+ @refused ? answers.except(asked.id.to_sym) : answers.merge(asked.id.to_sym => answer)
107
114
  end
108
115
 
109
116
  def record_submitted
110
117
  id, value = params.fetch(:answers, {}).permit(*asked).to_h.first
111
- progress.record(id, value)
118
+ id ||= params[:asked].presence_in(asked)
119
+ return if id.nil?
120
+
121
+ problem = answer_problem(runner_for(run.pinned_definition).step(id.to_s), value)
122
+ return flash[:alert] = problem if problem
123
+
124
+ progress.record(id, value.to_s)
125
+ end
126
+
127
+ def answer_problem(step, answer)
128
+ return unless step && EasyFlow.registry.registered?(step.type)
129
+
130
+ EasyFlow.registry.fetch(step.type).answer_problem(step, answer)
112
131
  end
113
132
 
114
133
  def asked
@@ -40,6 +40,10 @@ module EasyFlow
40
40
  @routing = routing
41
41
  end
42
42
 
43
+ def answer_check(&check)
44
+ @answer_check = check
45
+ end
46
+
43
47
  def step_name(value)
44
48
  @step_name = value
45
49
  end
@@ -103,7 +107,7 @@ module EasyFlow
103
107
  StepType.new(id: @id, step_name: @step_name, settings: settings, awaits_input: @awaits_input,
104
108
  ends_here: @ends_here, begins_here: @begins_here, behaviour: @behaviour, routing: @routing,
105
109
  display: @display, drawn_by: @drawn_by, naming_field: @naming_field, naming: @naming,
106
- outputs: @declared_outputs)
110
+ outputs: @declared_outputs, answer_check: @answer_check)
107
111
  end
108
112
 
109
113
  def settings
@@ -8,7 +8,7 @@ module EasyFlow
8
8
 
9
9
  def initialize(id:, step_name:, settings:, awaits_input:, behaviour:, routing:,
10
10
  ends_here: false, begins_here: false, display: nil, drawn_by: nil,
11
- naming_field: nil, naming: nil, outputs: [])
11
+ naming_field: nil, naming: nil, outputs: [], answer_check: nil)
12
12
  @id = id
13
13
  @step_name = step_name
14
14
  @settings = settings
@@ -22,6 +22,7 @@ module EasyFlow
22
22
  @naming_field = naming_field
23
23
  @naming = naming
24
24
  @outputs = outputs
25
+ @answer_check = answer_check
25
26
  end
26
27
 
27
28
  def display_of(node)
@@ -36,6 +37,10 @@ module EasyFlow
36
37
  @routing&.call(node, state)
37
38
  end
38
39
 
40
+ def answer_problem(node, value)
41
+ @answer_check&.call(node, value)
42
+ end
43
+
39
44
  def name_of(node)
40
45
  @naming&.call(node).presence || node.config[naming_field.to_s].presence
41
46
  end
@@ -7,6 +7,7 @@ module EasyFlow
7
7
 
8
8
  setting :question, type: :string
9
9
  setting :category, type: :string
10
+ setting :required, type: :boolean
10
11
  setting :answers, type: :list, required: true do
11
12
  setting :value, type: :string
12
13
  setting :label, type: :string
@@ -17,6 +18,7 @@ module EasyFlow
17
18
 
18
19
  names_by :question
19
20
  awaits_input
21
+ answer_check { |node, value| "Fill this in to go on." if node.config["required"] && value.blank? }
20
22
 
21
23
  displays_by { |node| Asked.new(id: node.id.to_sym, text: asked(node.config), choices: choices_in(node)) }
22
24
 
@@ -7,7 +7,12 @@
7
7
  <%= ui_progress(value: @answers.size, max: [ @guide.steps_on_path(@answers).size, 1 ].max, label: "Question #{@answers.size + 1}") %>
8
8
  </div>
9
9
 
10
+ <% if flash[:alert] %>
11
+ <div class="mb-6"><%= ui_alert(message: flash[:alert], type: :error) %></div>
12
+ <% end %>
13
+
10
14
  <%= form_with url: step_form[:url], method: step_form[:method], data: { turbo: false } do %>
15
+ <%= hidden_field_tag :asked, @question.id %>
11
16
  <% if carries_answers? %>
12
17
  <% @answers.each do |key, value| %>
13
18
  <%= hidden_field_tag "answers[#{key}]", value %>
@@ -1,3 +1,3 @@
1
1
  module EasyFlow
2
- VERSION = "0.4.6"
2
+ VERSION = "0.4.7"
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, computes a value from earlier answers, or picks the next branch), offering an admin a step setting whose options are read from the app's own records, serving a host's flows from the app's own controller and routes, acting when a visitor finishes a flow, and reading a run's recorded answers and question labels — MUST BE USED instead of hand-rolling questionnaire steps, branching logic, number comparisons, hard-coded setting options, 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, computes a value from earlier answers, or picks the next branch), 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, serving a host's flows from the app's own controller and routes, acting when a visitor finishes a flow, and reading a run's recorded answers and question labels — MUST BE USED instead of hand-rolling questionnaire steps, branching logic, number comparisons, hard-coded setting options, answer validation, 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,7 +9,7 @@ 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 six: start (`:start`), end (`:terminal`), question (`:question`, a question with a list of answers), and 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). Before writing a step type to branch on an answer or a number, ask the developer whether an admin placing one of those three on the canvas is enough. 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 six: start (`:start`), end (`:terminal`), question (`:question`, a question with a list of answers, which the admin can mark required so a blank answer is refused), and 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). Before writing a step type to branch on an answer or a number, ask the developer whether an admin placing one of those three on the canvas is enough. 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.
13
13
 
14
14
  ## Interface
15
15
 
@@ -77,7 +77,8 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
77
77
  - `setting :name, type:, label:, options:, required:, limit:, check:, from:, outputs_of:` — a field the admin fills in on the canvas. `type` is one of `:string`, `:integer`, `:float`, `:boolean`, `:select`, `:multi_select`, `:previous_step`, `:from_step`, `:list`, or boot raises `EasyFlow::UnknownFieldType`. A `:select` or `:multi_select` must pass `options:`, or boot raises the same error. `options:` is either an array of strings, or a lambda taking no arguments that returns one; the lambda is called each time the canvas is opened and each time an admin saves the step's settings, so the options follow the app's data without a restart (`options: -> { Region.order(:name).pluck(:code) }`). A saved setting whose value is no longer among the options is refused the next time the admin saves that step. When the options come from the app's records, ask the developer which records, which column is stored as the value, and whether the list must differ by account or host, since the lambda is passed nothing and the same list is offered everywhere the type is used. A `:list` must take a block of `setting` calls describing one entry. `:previous_step` lets the admin pick an earlier step, and is always required. `outputs_of: :<setting>` offers the outputs of the step chosen in that setting, and `from: :<setting>` offers the values of the output chosen in that setting; either one makes the type `:from_step`. `limit:` is the most values a `:multi_select` takes. `check:` is a lambda given the value that returns an error message, or `nil` when the value is acceptable. `label:` defaults to the name humanized.
78
78
  - `output :name, type:, label:, values:, from:` — a value the step records. `type` is one of `:string`, `:integer`, `:float`, `:boolean`, or boot raises `EasyFlow::UnknownOutputType`. `values:` is an array or a lambda taking the node, listing the values the output can take; the canvas offers these as the connections leaving the step, so a type that routes must declare them. `from: :<setting>` takes the values from the step chosen in that setting.
79
79
  - `names_by :setting` or `names_by { |node| ... }` — what the step is called on the canvas, from a setting or computed.
80
- - `awaits_input` — the visitor is shown this step and must answer it.
80
+ - `awaits_input` — the visitor is shown this step and submits an answer to it.
81
+ - `answer_check { |node, value| ... }` — checks a submitted answer before it is recorded. `value` is the submitted string, or `nil` when the input sent nothing. Return a message to refuse the answer, or `nil` to accept it. Without it every answer is accepted, including a blank one.
81
82
  - `ends_here` / `begins_here` — marks the type as an end or a start of a flow.
82
83
  - `displays_by { |node| ... }` — builds the object handed to the step's partial as the local `step`. Without it the partial receives the node itself.
83
84
  - `drawn_by "<partial>"` — the partial that draws this type for a visitor. Without it the host's default drawing is used, which expects `step.id`, `step.text` and `step.choices`, so a type with its own display shape needs its own partial.
@@ -105,7 +106,16 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
105
106
  <%= number_field_tag "answers[#{step.id}]", nil, in: 1..step.scale %>
106
107
  ```
107
108
 
109
+ The input must submit one value. An input that submits an array or a hash, such as checkboxes named `answers[<step id>][]`, is dropped and treated as blank.
110
+
108
111
  An answer is recorded as the string the visitor submitted. A compare step still reads a typed answer such as `"12"` as the number it spells, and an answer that is missing or is not a number makes it follow its `false` connection.
112
+
113
+ A blank answer is recorded as `""` and the visitor moves on, unless the type's `answer_check` refuses it. When the check returns a message, nothing is recorded and the same step is shown again with that message. This holds whether the flow keeps a stored run or carries its answers in the page, and the page supplies what it needs for both, so the partial adds nothing for it. Ask the developer whether the visitor may leave this step blank, and whether that is fixed for the type or chosen per step by the admin. For a per-step choice, declare a `:boolean` setting and read it in the check:
114
+
115
+ ```ruby
116
+ setting :required, type: :boolean
117
+ answer_check { |node, value| "Fill this in to go on." if node.config["required"] && value.blank? }
118
+ ```
109
119
  7. Restart the server, open a flow on the canvas and check the type is offered, its settings show, and a preview walks through it.
110
120
 
111
121
  ### Serve a host's flows from the app's own controller
@@ -175,6 +185,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
175
185
  - A type that routes declares the values its output takes, or the canvas offers no connections to label.
176
186
  - Always read a run against `run.pinned_definition`, never the flow's live version, since a run keeps the version it started on after a new one is published.
177
187
  - Always look flows and runs up through a host.
178
- - A step's input field is named `answers[<step id>]`. Any other name is ignored.
188
+ - A step's input field is named `answers[<step id>]` and submits one value. Any other name is ignored, and an array or hash is treated as blank.
189
+ - An answer the type's `answer_check` refuses is never recorded, and the visitor is shown the same step with the check's message. An answer no check refuses is recorded, a blank one as `""`.
179
190
  - A `FlowsController` subclass needs all four named routes for its prefix; a missing one raises when the visitor is linked or redirected to it.
180
191
  - The engine installs, mounts and configures hosts, layouts, the default drawing and checks through `easy_flow-install`, not here.
@@ -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, hosts, runs, the built-in step types including branching on an answer or comparing a number, step types the host app registers, and the settings an admin fills in, including picks whose options come from the app's data.
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, hosts, runs, the built-in step types including questions that can require an answer and branching on an answer or comparing a number, step types the host app registers and the checks they make on an answer, and the settings an admin fills in, including picks whose options come from the app's data.
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
  ---
@@ -23,8 +23,9 @@ easy_flow declares no commands of its own for this local. Its surface is split b
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, use **easy_flow-install**.
26
- - To add a step type, put a flow on the app's own pages, or act on what a visitor answered, use **easy_flow-develop**.
26
+ - To add a step type, give a step type its own rule for refusing an answer, put a flow on the app's own pages, or act on what a visitor answered, use **easy_flow-develop**.
27
27
  - 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.
28
+ - To make a visitor answer a question before going on, no code is needed: an admin marks that question step as required on the canvas.
28
29
  - If the question is only what a word below means, this page is the answer.
29
30
 
30
31
  ## Conventions
@@ -35,16 +36,20 @@ easy_flow declares no commands of its own for this local. Its surface is split b
35
36
  - **Canvas** — the admin screen where steps are added, configured, moved, removed and connected, with undo and redo. Publishing happens there.
36
37
  - **Preview** — an admin walking the current flow without starting a stored run.
37
38
  - **Step type** — what a kind of step is: its display name, its settings (the fields an admin fills in), its outputs (the values it records), and whether it waits for the visitor or acts on its own. A step that acts on its own may record a value worked out from the answers so far, pick which connection to follow, or both.
39
+ - **Answer check** — a rule a step type may carry that looks at a visitor's answer and, when the answer is not acceptable, returns the message the visitor sees. A step type with no such rule accepts every answer.
38
40
  - **Setting** — one field an admin fills in when configuring a step on the canvas. It is text, a whole number, a decimal, a yes or no, a pick from a list, several picks from a list, an earlier step, an earlier step's output, or a list of entries that each hold their own fields. A setting can be required, limited to a number of picks, or checked by a rule the step type supplies.
39
41
  - **Options** — the values a pick-from-a-list setting offers. They are either a fixed list written into the step type, or a lookup the app provides that is read each time the canvas or a save asks for them, so they can come from the app's own data. A saved value that is not among the options is refused.
40
42
  - **Output** — a value a step records into the run, with a type and a label. Later steps read outputs, and a setting that points at an earlier step offers that step's known values on the canvas.
41
43
  - **Start** and **End** — the built-in step types that begin and finish a flow.
42
- - **Question** — the built-in step type that asks the visitor: a question text and a list of answers, each with a value, a label and a weight.
44
+ - **Question** — the built-in step type that asks the visitor: a question text, an optional category, a yes-or-no required setting, and a list of answers, each with a value, a label and a weight.
45
+ - **Required question** — a question step the admin has marked required. A blank answer to it is refused with the message "Fill this in to go on."
43
46
  - **Condition** — a built-in branching step that checks whether an earlier step's answer is, or is not, a chosen value, and follows the true or the false connection.
44
47
  - **Switch** — a built-in branching step that follows the connection labelled with an earlier step's answer.
45
48
  - **Compare** — a built-in branching step that reads an earlier step's answer as a number and checks it against an amount by more than, less than, at least or at most, then follows the true or the false connection. When the earlier step recorded several outputs, the admin picks which one. An answer that is missing or is not a number decides false.
46
- - **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.
49
+ - **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.
47
50
  - **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, and how a refusal is answered. Flows are always looked up through a host, so one host never sees another's flows.
48
51
  - **Run** — one visitor's pass through a flow. A run is pinned to the version that was live when it started, so publishing a new version does not change a run already under way. It records each answer by step id, and going back discards the last answer on the path.
49
52
  - **Answers** — the recorded state of a run, keyed by step id. The next step is always worked out from these answers and the connections in the pinned version.
50
- - **Checks** — warnings on a flow's shape: an answer value no connection routes, a path nothing follows, and a step that leads nowhere. The engine turns all three on by default.
53
+ - **Refused answer** — an answer the step's answer check turned down. Nothing is recorded for that step, and the visitor is shown the same step again with the check's message. This works the same whether the flow keeps a stored run or carries its answers in the page.
54
+ - **Blank answer** — a step the visitor leaves blank. On a step whose answer check does not refuse it, such as a question that is not required, the blank is recorded and the visitor moves on to the next step.
55
+ - **Checks** — warnings on a flow's shape: an answer value no connection routes, a path nothing follows, and a step that leads nowhere. The engine turns all three on by default. These are separate from answer checks, which look at what a visitor submits.
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.4.6
4
+ version: 0.4.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - tylercschneider