easy_flow 0.8.0 → 0.9.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: c42186355c5aff4867710d2b43d50543a34be2ce290c9748bea1ab11b540e21c
4
- data.tar.gz: e0a902db995da05a8fed9e1c8f841b90f252277f31e2867d59a43a7855bcfa03
3
+ metadata.gz: cf8a8dbfd774f223e6a43af475f4815a3fa9d94c9ce36d58ac546c803e299232
4
+ data.tar.gz: b773f634332cb61f248bbfb03879a322be6baaaacec6d7cf0220a70eb743b72e
5
5
  SHA512:
6
- metadata.gz: 907fdb2b8760253cf3eea079d3dbbd92d23050d296b9c1166b8150dd5d6cff1e52b1b5f069a65b2111786808bfbcf9e097125f92b60a7e725dc958cde58b73df
7
- data.tar.gz: 7db26de4465197e8983fd84c44f2bb2cc78cd79d4a999606adf11b8fa49a3af68699d19b96fa0686355bd53f5b0eaac93126627d7c37eb9b6aa2cb7c3dacc880
6
+ metadata.gz: 6b5de21e4e9c7074abe01b61fab6c3305f34d407dc19584e54c649e046bb138a92067cebaa1774cc15d3f55e2aaa67eb1a6fa73ed0dcec219881f7df31807e4b
7
+ data.tar.gz: e833348051900594e25dc04ffc7a90214544deac6b9f5965e9f3ee79cd1152672253f0679122f3503239552fb8a696285d6c51ff66fefaf955d3e958db91d8c8
@@ -176,7 +176,10 @@ module EasyFlow
176
176
  step_type.settings.coerce(configuration).tap do |values|
177
177
  objections = step_type.settings.objections(values)
178
178
  raise InvalidEdit, objections.join(", ") if objections.any?
179
- end
179
+
180
+ refused = step_type.settings.keep(values)
181
+ raise InvalidEdit, refused.join(", ") if refused.any?
182
+ end.then { |values| step_type.settings.unkept(values) }
180
183
  end
181
184
  end
182
185
  end
@@ -6,14 +6,17 @@ module EasyFlow
6
6
  @flows = ordered_flows
7
7
  end
8
8
 
9
+ def new
10
+ @flow = hosted_flows.new
11
+ end
12
+
9
13
  def create
10
14
  @flow = hosted_flows.new(create_params)
11
15
 
12
16
  if @flow.save
13
17
  redirect_to manage_flow_path(@flow), notice: "Flow created."
14
18
  else
15
- @flows = ordered_flows
16
- render :index, status: :unprocessable_entity
19
+ render :new, status: :unprocessable_entity
17
20
  end
18
21
  end
19
22
 
@@ -18,7 +18,7 @@ module EasyFlow
18
18
 
19
19
  def drawn_step(node)
20
20
  { "id" => node.id, "type" => node.type, "label" => label_for(node),
21
- "config" => node.config, "ports" => [], "ends_here" => ends_here?(node), "begins_here" => begins_here?(node),
21
+ "config" => node.config.merge(kept_values(node)), "ports" => [], "ends_here" => ends_here?(node), "begins_here" => begins_here?(node),
22
22
  "loose" => loose?(node), "choices" => choices_for(node), "placeholder" => false, **placed[node.id] }
23
23
  end
24
24
 
@@ -105,6 +105,10 @@ module EasyFlow
105
105
  end
106
106
  end
107
107
 
108
+ def kept_values(node)
109
+ step_type_for(node)&.settings&.kept_values(node.config).to_h
110
+ end
111
+
108
112
  def label_for(node)
109
113
  Name.of(node, @registry)
110
114
  end
@@ -3,7 +3,7 @@ module EasyFlow
3
3
  attr_reader :fields, :labels, :record_fields, :record_labels, :drawn_from, :outputs_of
4
4
 
5
5
  def initialize(fields: {}, labels: {}, record_fields: {}, record_labels: {}, choices: {},
6
- limits: {}, checks: {}, required: [], drawn_from: {}, outputs_of: {})
6
+ limits: {}, checks: {}, required: [], drawn_from: {}, outputs_of: {}, kept: {})
7
7
  @fields = fields
8
8
  @labels = labels
9
9
  @record_fields = record_fields
@@ -14,12 +14,35 @@ module EasyFlow
14
14
  @required = required
15
15
  @drawn_from = drawn_from
16
16
  @outputs_of = outputs_of
17
+ @kept = kept
17
18
  end
18
19
 
19
20
  def choices
20
21
  @choices.transform_values { |offered| offered.respond_to?(:call) ? offered.call : offered }
21
22
  end
22
23
 
24
+ def kept_values(config)
25
+ @kept.to_h { |name, kept| [ name.to_s, kept[:finder].call(config.to_h)&.public_send(kept[:attribute]) ] }
26
+ end
27
+
28
+ def unkept(config)
29
+ config.to_h.except(*@kept.keys.map(&:to_s))
30
+ end
31
+
32
+ def keep(config)
33
+ refused = []
34
+ ActiveRecord::Base.transaction(requires_new: true) do
35
+ refused = @kept.filter_map do |name, kept|
36
+ record = kept[:finder].call(config.to_h)
37
+ next if record.nil? || record.update(kept[:attribute] => config.to_h[name.to_s])
38
+
39
+ "#{labels[name]} #{record.errors.messages_for(kept[:attribute]).to_sentence}"
40
+ end
41
+ raise ActiveRecord::Rollback if refused.any?
42
+ end
43
+ refused
44
+ end
45
+
23
46
  def naming_steps
24
47
  fields.select { |_name, type| type == :previous_step }.keys
25
48
  end
@@ -13,6 +13,7 @@ module EasyFlow
13
13
  @checks = {}
14
14
  @drawn_from = {}
15
15
  @outputs_of = {}
16
+ @kept = {}
16
17
  @declared_outputs = []
17
18
  @required = []
18
19
  @awaits_input = false
@@ -69,13 +70,14 @@ module EasyFlow
69
70
  @naming = naming
70
71
  end
71
72
 
72
- def setting(name, type: nil, from: nil, outputs_of: nil, label: nil, options: nil, limit: nil, check: nil, required: false, &entries)
73
+ def setting(name, type: nil, from: nil, outputs_of: nil, label: nil, options: nil, limit: nil, check: nil, required: false, kept_on: nil, attribute: nil, &entries)
73
74
  type ||= :from_step if from
74
75
  type ||= :from_step if outputs_of
75
76
  raise UnknownFieldType, "#{type} is not one of #{FIELD_TYPES.join(', ')}" unless FIELD_TYPES.include?(type)
76
77
 
77
78
  @drawn_from[name] = from if from
78
79
  @outputs_of[name] = outputs_of if outputs_of
80
+ @kept[name] = { finder: kept_on, attribute: attribute || name } if kept_on
79
81
  @required += [ name ] if required
80
82
 
81
83
  declare_entries(name, entries) if type == :list
@@ -117,7 +119,7 @@ module EasyFlow
117
119
  def settings
118
120
  Settings.new(fields: @fields, labels: @labels, record_fields: @record_fields,
119
121
  record_labels: @record_labels, choices: @choices, limits: @limits, checks: @checks,
120
- required: @required, drawn_from: @drawn_from, outputs_of: @outputs_of)
122
+ required: @required, drawn_from: @drawn_from, outputs_of: @outputs_of, kept: @kept)
121
123
  end
122
124
  end
123
125
  end
@@ -1,5 +1,9 @@
1
1
  <%= ui_page(max_width: :xl) do %>
2
- <%= ui_page_header(title: "Flows", subtitle: "Build and edit your flows") %>
2
+ <%= ui_page_header(title: "Flows", subtitle: "Build and edit your flows") do |header| %>
3
+ <% header.action do %>
4
+ <%= ui_button(label: "Create", href: flow_routes.new_manage_flow_path) %>
5
+ <% end %>
6
+ <% end %>
3
7
 
4
8
  <%= ui_data_table(
5
9
  items: @flows,
@@ -13,20 +17,4 @@
13
17
  <% end %>
14
18
  <% end %>
15
19
  <% end %>
16
-
17
- <%= ui_section(title: "Create a flow") do %>
18
- <% if @flow&.errors&.any? %>
19
- <%= ui_alert(message: @flow.errors.full_messages.to_sentence, type: :error) %>
20
- <% end %>
21
-
22
- <%= ui_form(action: flow_routes.manage_flows_path, method: :post) do %>
23
- <label class="block text-sm font-medium mb-1">Slug</label>
24
- <%= ui_input(name: "flow[slug]", placeholder: "e.g. business-scorecard") %>
25
-
26
- <label class="block text-sm font-medium mb-1 mt-4">Kind</label>
27
- <%= ui_select(name: "flow[kind]", options: [ [ "Scored", "scored" ], [ "Guide", "guide" ] ]) %>
28
-
29
- <%= ui_button(label: "Create", type: :submit, variant: :secondary) %>
30
- <% end %>
31
- <% end %>
32
20
  <% end %>
@@ -0,0 +1,17 @@
1
+ <%= ui_page(max_width: :md) do %>
2
+ <%= ui_page_header(title: "New flow") %>
3
+
4
+ <% if @flow.errors.any? %>
5
+ <%= ui_alert(message: @flow.errors.full_messages.to_sentence, type: :error) %>
6
+ <% end %>
7
+
8
+ <%= ui_form(action: flow_routes.manage_flows_path, method: :post) do %>
9
+ <label class="block text-sm font-medium mb-1">Slug</label>
10
+ <%= ui_input(name: "flow[slug]", value: @flow.slug, placeholder: "e.g. business-scorecard") %>
11
+
12
+ <label class="block text-sm font-medium mb-1 mt-4">Kind</label>
13
+ <%= ui_select(name: "flow[kind]", options: [ [ "Scored", "scored" ], [ "Guide", "guide" ] ], selected: @flow.kind) %>
14
+
15
+ <%= ui_button(label: "Create", type: :submit) %>
16
+ <% end %>
17
+ <% end %>
data/config/routes.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  EasyFlow::Engine.routes.draw do
2
2
  namespace :manage do
3
- resources :flows, only: [ :index, :create, :show, :edit, :update, :destroy ] do
3
+ resources :flows, only: [ :index, :new, :create, :show, :edit, :update, :destroy ] do
4
4
  resource :definition, only: [ :edit, :update ]
5
5
  resource :preview, only: :show, controller: "previews" do
6
6
  get :step
@@ -1,3 +1,3 @@
1
1
  module EasyFlow
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.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, 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, 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, branching logic, number comparisons, hard-coded setting options, 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 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, acting when a visitor finishes a flow, 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, 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.
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
  ---
@@ -76,7 +76,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
76
76
  4. Ask the developer which hosts' admins should be able to add the type. A registered type is in a host's palette, the step types an admin can add on that host's canvas, only when the host names no list of offered step types or its list includes the type's id. A type that declares `ends_here` is in every host's palette, and a type that declares `begins_here` is in none. When a host that names a list should offer the new type, add the type's id to that host's list through `easy_flow-install`. Leaving a type off a host's list only keeps admins from adding it there, and a step of that type already in one of the host's flows is still drawn and run.
77
77
  5. Use these words to declare the type. Each is called once at class level (or inside the `EasyFlow.step` block):
78
78
  - `step_name "<label>"` — the name admins see on the canvas. Defaults to the id.
79
- - `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.
79
+ - `setting :name, type:, label:, options:, required:, limit:, check:, from:, outputs_of:, kept_on:, attribute:` — 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. `kept_on:` and `attribute:` keep the setting's value on one of the app's own records instead of in the flow; see "Keep a setting on one of the app's records".
80
80
  - `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.
81
81
  - `names_by :setting` or `names_by { |node| ... }` — what the step is called on the canvas, from a setting or computed.
82
82
  - `awaits_input` — the visitor is shown this step and submits an answer to it.
@@ -169,6 +169,28 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
169
169
  - 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.
170
170
  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.
171
171
 
172
+ ### Keep a setting on one of the app's records
173
+
174
+ Use this when an admin should edit a value that belongs to one of the app's own records, such as a customer's name, from a step's settings on the canvas, and the flow should not hold a copy of it.
175
+
176
+ 1. Ask the developer which record holds the value, which of its attributes, and which of the step's other settings identifies the record. Do not pick these.
177
+ 2. Ask the developer which admins may change which records. The finder below is given only the step's settings, not the admin, the host or the owner, so an admin who can type an id into a setting can reach any record the finder will find. If records must be limited to one account or host, stop and ask how the finder can tell, since it has nothing else to read.
178
+ 3. Declare the setting with `kept_on:`, a lambda given the step's settings as a hash with string keys that returns the record or `nil`, and `attribute:`, the record's attribute that holds the value. `attribute:` defaults to the setting's name:
179
+
180
+ ```ruby
181
+ setting :customer, type: :string
182
+ setting :customer_name, type: :string, kept_on: ->(config) { Customer.find_by(id: config["customer"]) }, attribute: :name
183
+ ```
184
+
185
+ 4. What then holds:
186
+ - The canvas shows the value read from the record each time it draws the step, or a blank when the finder returns `nil`.
187
+ - When the admin saves the step's settings, the value is written to the record with `update` at that moment, before the flow is published, and on every save of that step whether or not the value changed. The setting's `type`, `required:` and `check:` are applied first.
188
+ - When the finder returns `nil`, nothing is written and the save goes on.
189
+ - When the record refuses the value, the save is refused with the setting's label followed by the record's own error, such as `Customer name is too long (maximum is 40 characters)`. None of the step's kept settings are written and none of its other settings are saved. This holds when the app already has a transaction open.
190
+ - The value is never stored in the flow's document or its versions, so publishing, undo and a run's pinned version never hold or change it.
191
+ - `node.config` in `process`, `route`, `waits_until`, `answer_check`, `names_by` and `displays_by` does not hold a kept setting. A step that needs the value reads it from the record.
192
+ 5. Restart the server, open a flow on the canvas, add a step of the type, save a value, and check the record holds it and that a value the record refuses shows its error.
193
+
172
194
  ### Serve a host's flows from the app's own controller
173
195
 
174
196
  1. Ask the developer which host this controller serves, what path the visitor pages live under, and what should happen when a visitor finishes. Do not choose the finish behaviour for them.
@@ -239,6 +261,8 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
239
261
  - Registering a type does not put it in the palette of a host that names a list of offered step types without the type's id. Which step types a host offers is set through `easy_flow-install`, not here.
240
262
  - A type that declares `ends_here` is in every host's palette, and a type that declares `begins_here` is in no host's palette.
241
263
  - An `options:` lambda is passed nothing, and one list is offered on every host's canvas, so it cannot narrow the options by admin, account or host.
264
+ - 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`.
265
+ - 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.
242
266
  - A type that routes declares the values its output takes, or the canvas offers no connections to label.
243
267
  - 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.
244
268
  - Always look flows and runs up through a host.
@@ -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, the built-in step types including questions and checklists that can require an answer and explain each answer behind an info button, and branching on an answer or comparing a number, step types the host app registers, 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.
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, the built-in step types including questions and checklists that can require an answer and explain each answer behind an info button, and branching on an answer or comparing a number, step types the host app registers, 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,12 +18,12 @@ 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, the flow checks, and the admin pages.
21
- - **easy_flow-develop** owns building on it: declaring and registering step types, including ones that wait, 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 reading a visitor's run and answers.
21
+ - **easy_flow-develop** owns building on it: declaring and registering step types, including ones that wait 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 reading a visitor's run and answers.
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, use **easy_flow-install**.
26
- - To add a step type, give a step type its own rule for refusing an answer, make what a step shows depend on the visitor's run, hold a run until something outside the flow has happened, 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, make what a step shows depend on the visitor's run, hold a run until something outside the flow has happened, keep a step's setting on one of the app's own records, 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
28
  - 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.
29
29
  - To let a visitor tick several answers on one step, no code is needed: an admin places a checklist step on the canvas.
@@ -43,6 +43,7 @@ easy_flow declares no commands of its own for this local. Its surface is split b
43
43
  - **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.
44
44
  - **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.
45
45
  - **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.
46
+ - **Kept setting** — a setting whose value lives on one of the app's own records rather than in the flow. The step type says how to find that record from the step's other settings and which of its attributes holds the value. The canvas shows the value read from the record, and saving the step's settings writes it straight to the record, so the flow's document and its versions never hold it and undo or publishing does not change it. When no record is found, nothing is written. When the record refuses a value, the save is refused with the record's own error and none of the step's kept settings are written.
46
47
  - **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.
47
48
  - **Start** and **End** — the built-in step types that begin and finish a flow.
48
49
  - **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, a weight, a hint and an info. The visitor picks one answer, and that answer's value is recorded.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: easy_flow-install
3
- description: Use to hook easy_flow into a project — copying and running its migrations, mounting the engine for each host, setting the controller it inherits from, declaring hosts with their layouts, access methods and the step types each offers, choosing the default step drawing, turning on optional checks, and reaching the admin pages.
3
+ description: Use to hook easy_flow into a project — copying and running its migrations, mounting the engine for each host, setting the controller it inherits from, declaring hosts with their layouts, access methods, the record that owns their flows and the step types each offers, choosing the default step drawing, turning on optional checks, and reaching the admin pages.
4
4
  tools: Bash, Read, Edit
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
  ---
@@ -16,7 +16,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
16
16
  - `bin/rails easy_flow:install:migrations` — copies the engine's migrations into the host's `db/migrate`, creating the flow, version and run tables.
17
17
  - `mount EasyFlow::Engine` — the route that serves one host's visitor pages and admin pages under a path, with the host named in `defaults: { easy_flow_host: "<host>" }`.
18
18
  - `EasyFlow.base_controller=` — the name, as a string, of the host controller every engine controller inherits from. Defaults to `"ActionController::Base"`.
19
- - `EasyFlow.host` — declares a named host with its visitor layout, admin layout, admin authentication method, visitor authorization method, refusal method, and the step types its admins can add on the canvas.
19
+ - `EasyFlow.host` — declares a named host with its visitor layout, admin layout, admin authentication method, visitor authorization method, refusal method, owner method, and the step types its admins can add on the canvas.
20
20
  - `EasyFlow.draws_with` — the partial that draws a visitor's step when the step's type names none. Defaults to the engine's own `easy_flow/steps/choosing`.
21
21
  - `EasyFlow.check` — turns on an optional flow check: `:unrouted_value`, `:unfollowed_path` or `:dead_end`.
22
22
  - `/manage/flows` — the admin pages, under each mount path, where flows are listed, created, drawn on the canvas, previewed and published.
@@ -26,7 +26,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
26
26
  1. Confirm the app has Keystone UI installed (`keystone_ui` in the Gemfile). easy_flow's controllers use its helpers and it is not pulled in by easy_flow. If it is missing, stop and hand off to the `keystone_ui-install` local before continuing.
27
27
  2. Read the `keystone_ui` version in the app's `Gemfile.lock`. easy_flow's visitor pages pass each answer's info text to Keystone UI's radio cards and checkbox rows, and easy_flow is built against `keystone_ui` 0.27.0. If the app's version is older, ask the developer whether to run `bundle update keystone_ui` before continuing.
28
28
  3. Add `gem "easy_flow"` to the host's `Gemfile` and run `bundle install`.
29
- 4. Run `bin/rails easy_flow:install:migrations`, then `bin/rails db:migrate`. This writes four migrations into `db/migrate` and updates `db/schema.rb`.
29
+ 4. Run `bin/rails easy_flow:install:migrations`, then `bin/rails db:migrate`. This writes five migrations into `db/migrate` and updates `db/schema.rb`.
30
30
  5. Ask the developer which hosts the app needs. A host is one part of the app that owns its own set of flows, and one host never sees another's flows. Ask for each host's name and the path it is served under.
31
31
  6. Ask the developer which controller the engine should inherit from. Choosing `"ApplicationController"` gives the engine the app's own authentication methods and helpers. Create `config/initializers/easy_flow.rb` and set it on the first line:
32
32
 
@@ -43,6 +43,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
43
43
  host.admin_authentication_method = :authenticate_admin!
44
44
  host.visitor_authorization_method = :easy_flow_visitor_permitted?
45
45
  host.refusal_method = :refuse_flow
46
+ host.owner_method = :current_account
46
47
  host.offers = %i[question checklist condition switch compare]
47
48
  end
48
49
  ```
@@ -52,6 +53,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
52
53
  - `admin_authentication_method` — a method on the base controller, called with no arguments before every admin page. With none set, the admin pages are open to anyone.
53
54
  - `visitor_authorization_method` — a method on the base controller, called with the flow, returning true when the visitor may run it. With none set, every visitor is refused.
54
55
  - `refusal_method` — a method on the base controller, called with the refusal error when a visitor is refused or a flow is unpublished or withdrawn. With none set, the response is `404 Not Found`.
56
+ - `owner_method` — a method on the base controller, called with no arguments, returning the record whose flows the current request works with, such as the signed-in account. When set, the admin pages list, create and edit only that record's flows, a visitor runs only that record's flows, and two records may each hold a flow with the same slug. With none set, every flow in the host is shared by everyone who reaches it. Ask the developer whether each host's flows belong to one record or are shared.
55
57
  - `offers` — the step types this host's admins can add from the canvas palette, as a list of step type names. With none set, every registered step type is offered. The engine's own names are `question`, `checklist`, `condition`, `switch` and `compare`. A step type the app declares is named by the id passed to `EasyFlow.step`, or after its class when it is a step class, so `Steps::Notify` is `notify`. The End step is offered whether it is listed or not, and the Start step is never offered. Ask the developer which step types each host should offer.
56
58
 
57
59
  Each method named here must exist on the base controller. Ask the developer to point at it or write it. Do not invent its logic.
@@ -84,7 +86,8 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
84
86
  - After publishing a flow with a question or a checklist whose answer has info text, check that the visitor's page shows an info button on that answer. If it does not, compare the app's `keystone_ui` version with the one in step 2.
85
87
  - After publishing a flow with a checklist, check that a visitor can tick several answers and go on, and that a checklist marked required refuses to go on with nothing ticked.
86
88
  - After upgrading easy_flow, run `bin/rails easy_flow:install:migrations` again and then `bin/rails db:migrate`. Only migrations the app does not already have are copied.
89
+ - After setting a host's `owner_method`, check that two different owners each see only their own flows on `<mount path>/manage/flows`.
87
90
  - After setting a host's `offers`, check that the canvas palette on that host's `<mount path>/manage/flows` lists only those step types and End.
88
91
  - Taking a step type off a host's `offers` removes it from the palette only. Steps of that type already in the host's flows stay in them and keep running.
89
92
  - The initializer runs once at boot, so a change to it needs a server restart.
90
- - Declaring step types, serving a host's flows from the app's own controllers and routes, and reading runs and answers are out of scope here. They belong to the `easy_flow-develop` local.
93
+ - Declaring step types, including settings kept on the app's own records, serving a host's flows from the app's own controllers and routes, and reading runs and answers are out of scope here. They belong to the `easy_flow-develop` local.
@@ -29,6 +29,9 @@ sources:
29
29
  - app/controllers/easy_flow/manage/base_controller.rb
30
30
  - app/models/easy_flow/step.rb
31
31
  - app/models/easy_flow/step_type/declaration.rb
32
+ - app/models/easy_flow/settings.rb
33
+ - app/models/easy_flow/canvas.rb
34
+ - app/controllers/easy_flow/manage/canvas_controller.rb
32
35
  - app/models/easy_flow/steps/question.rb
33
36
  - app/models/easy_flow/steps/checklist.rb
34
37
  - app/models/easy_flow/step_type.rb
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.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - tylercschneider
@@ -127,6 +127,7 @@ files:
127
127
  - app/views/easy_flow/manage/definitions/edit.html.erb
128
128
  - app/views/easy_flow/manage/flows/edit.html.erb
129
129
  - app/views/easy_flow/manage/flows/index.html.erb
130
+ - app/views/easy_flow/manage/flows/new.html.erb
130
131
  - app/views/easy_flow/manage/flows/show.html.erb
131
132
  - app/views/easy_flow/manage/versions/index.html.erb
132
133
  - app/views/easy_flow/steps/_choosing.html.erb