flow_state 0.1.6 → 0.2.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: 957aaac94425fb52e1566d1e89c8084c01f021b5fefff56a9899ee69d06ff8fc
4
- data.tar.gz: 8b5949635034e88ed15fe31ae1c3c4e6cd6343785a823c9251e2c88911d7520a
3
+ metadata.gz: d474458f9e1405cd005acba075a3c0bcf21497f086f874171dbe47fe8497aac2
4
+ data.tar.gz: 56b3ab22b7a5b46bcaa79dad827861123dc24d31ecc91fc1f66f7b85b503545d
5
5
  SHA512:
6
- metadata.gz: 8798ed03aa73c9120d27641692a0165d7a82c4e8d9b4531b74c1483ab87fb5e896fa7e323611cce81f8d120030bc5c94df79887337befdb7208dcde8190e4d09
7
- data.tar.gz: 499e2d7f6da573ce36e95e491275d42e2846affe7d8def6c02c9db5013417fc495b639eb223bd96a789f9e92b6af4a152a0d8281038b34719fca1490e5663518
6
+ metadata.gz: 84668ec5878cadacbf156e30290ec0225cebc1f8460141872a51eaeead892a4b533e9532386c4c02fd69c0a43e8ea8fd744afaeff089291e62d7e363fb03c88a
7
+ data.tar.gz: e2f992dfee8d35e15c55fcd7036721092e5cda2f282e251fdacea44d12de21b3c8e423db94addbde62987b2f9fd181fe8a67d160e5a6e5b55a8ecdfed3fb6bf9
@@ -0,0 +1,87 @@
1
+ # Flow State 0.1 → 0.2 Migration Guide
2
+
3
+ ---
4
+
5
+ ## 1. Database migration
6
+
7
+ ```ruby
8
+ # db/migrate/xxxxxxxxxxxxxx_flow_state_02_upgrade.rb
9
+ class FlowState02Upgrade < ActiveRecord::Migration[6.1]
10
+ def change
11
+ rename_column :flow_state_flows, :payload, :props
12
+ add_column :flow_state_flows, :type, :string
13
+ add_column :flow_state_flows, :completed_at, :datetime
14
+ add_column :flow_state_flows, :last_errored_at, :datetime
15
+ end
16
+ end
17
+
18
+ ```
19
+
20
+ Run the migration and bump your gem version to `0.2.0`.
21
+
22
+ ---
23
+
24
+ ## 2. Required changes
25
+
26
+ | Area | Old (≤ 0.1) | New (0.2) |
27
+ | ------------------ | -------------------------------------- | ---------------------------------------------------------- |
28
+ | Flow setup | `initial_state` optional | `initial_state` and `completed_state` **required** |
29
+ | Completed handling | manual `after_transition { destroy! }` | `destroy_on_complete` handles cleanup automatically |
30
+ | Column rename | `payload` | `props`, used via `flow.props["key"]` only |
31
+ | Prop accessors | auto-generated methods | removed – use `props["key"]` |
32
+ | Macro rename | `persist :foo, Hash` | `persists :foo, Hash` |
33
+ | Transition keyword | `persists:` | `persist:` |
34
+ | Timestamps | — | `completed_at` and `last_errored_at` tracked automatically |
35
+
36
+ ---
37
+
38
+ ## 3. Example refactor
39
+
40
+ ### Flow definition
41
+
42
+ ```ruby
43
+ class SignupFlow < FlowState::Base
44
+ state :draft
45
+ state :processing
46
+ state :failed, error: true
47
+ state :completed
48
+
49
+ initial_state :draft
50
+ completed_state :completed
51
+ destroy_on_complete
52
+
53
+ prop :user_id, Integer
54
+ persists :external_response, Hash
55
+ end
56
+ ```
57
+
58
+ ### Usage
59
+
60
+ ```ruby
61
+ flow = SignupFlow.create!(props: { "user_id" => 42 })
62
+
63
+ flow.transition!(from: :draft, to: :processing)
64
+
65
+ flow.transition!(
66
+ from: :processing,
67
+ to: :completed,
68
+ persist: :external_response
69
+ ) { { status: 200 } }
70
+
71
+ # If destroy_on_complete was set:
72
+ # flow.destroyed? # => true
73
+ # Otherwise:
74
+ # flow.completed_at # => Time
75
+ # flow.last_errored_at # => nil (unless failed state was hit)
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 4. Cleanup tips
81
+
82
+ - Remove any `after_transition { destroy! }` logic and replace with `destroy_on_complete`.
83
+ - Stop calling dynamic prop getters like `flow.name`; use `flow.props["name"]` instead.
84
+ - Rename all usages of `persist` (macro) to `persists`.
85
+ - Update any `persists:` keyword args to `persist:` in your `transition!` calls.
86
+
87
+ You're now on **Flow State 0.2.0**.
data/README.md CHANGED
@@ -4,251 +4,194 @@
4
4
 
5
5
  ---
6
6
 
7
- **FlowState** provides a clean, Rails-native way to model **stepped workflows** as explicit, durable workflows, with support for persisting arbitrary artefacts between transitions. It lets you define each step, move between states safely, track execution history, and persist payloads ("artefacts") in a type-safe way — without using metaprogramming, `method_missing`, or other hidden magic.
7
+ **FlowState** is a small gem for Rails, for building *state-machine–style* workflows that persist every step, artefact and decision to your database.
8
+ Everything is explicit – no metaprogramming, no hidden callbacks, no magic helpers.
8
9
 
9
- Perfect for workflows that rely on third party resources and integrations.
10
- Every workflow instance, transition and artefact is persisted to the database.
11
- Every change happens through clear, intention-revealing methods that you define yourself.
10
+ Use it when you need to:
12
11
 
13
- Built for real-world systems where you need to:
14
- - Track complex, multi-step processes
15
- - Handle failures gracefully with error states and retries
16
- - Persist state and interim data across asynchronous jobs
17
- - Store and type-check arbitrary payloads (artefacts) between steps
18
- - Avoid race conditions via database locks and explicit guards
12
+ * orchestrate multi-step jobs that call external services
13
+ * restart safely after crashes or retries
14
+ * inspect an audit trail of *what happened, when and why*
15
+ * attach typed artefacts (payloads) to a given transition
19
16
 
20
17
  ---
21
18
 
22
- ## Key Features
19
+ ## What’s new in 0.2
23
20
 
24
- - **Explicit transitions** Every state change is triggered manually via a method you define.
25
- - **Full execution history** — Every transition is recorded with timestamps and a history table.
26
- - **Error recovery** Model and track failures directly with error states.
27
- - **Typed payloads** — Strongly-typed metadata attached to every workflow.
28
- - **Artefact persistence** Declare named and typed artefacts to persist between specific transitions.
29
- - **Guard clauses** Protect transitions with guards that raise if conditions aren’t met.
30
- - **Persistence-first** Workflow state and payloads are stored in your database, not memory.
31
- - **No Magic** — No metaprogramming, no dynamic method generation, no `method_missing` tricks.
21
+ | Change | Why it matters |
22
+ | ----------------------------------------------------- | ----------------------------------------------------------------------------------------- |
23
+ | **`initial_state` & `completed_state` are mandatory** | Keeps definitions explicit and prevents silent mis-configuration. |
24
+ | **`destroy_on_complete` macro** | One-liner to delete finished flows replaces manual `after_transition { destroy! }`. |
25
+ | **`payload` → `props` column** | Aligns storage with the `prop` DSL (`flow.props["key"]`). No more auto-generated getters. |
26
+ | **`persist` macro `persists`** | Reads better, matches the transition keyword (`persist:`). |
27
+ | **`completed_at` & `last_errored_at` timestamps** | Easier querying: `where(completed_at: ..)` or `where.not(last_errored_at: nil)`. |
32
28
 
33
- ---
34
-
35
- ## Installation
36
-
37
- Add to your bundle:
38
-
39
- ```bash
40
- bundle add flow_state
41
- ```
42
-
43
- Generate the tables:
44
-
45
- ```bash
46
- bin/rails generate flow_state:install
47
- bin/rails db:migrate
48
- ```
29
+ See the [migration guide](./MIGRATION_0_1_to_0_2.md) for a drop-in migration.
49
30
 
50
31
  ---
51
32
 
52
- ## Example: Saving a third party API response to local database
53
-
54
- Suppose you want to build a workflow that:
55
- - Fetches a response from a third party API
56
- - Allows for retrying the fetch on failure
57
- - And persists the response to the workflow
58
- - Then saves the persisted response to the database
59
- - As two separate, encapsulated jobs
60
- - Tracking each step, while protecting against race conditions
33
+ ## Quick example syncing an API and saving the result
61
34
 
62
- ---
63
-
64
- ### Define your Flow
35
+ ### 1 Define the flow
65
36
 
66
37
  ```ruby
67
- class SyncThirdPartyApiFlow < FlowState::Base
68
- prop :my_record_id, String
69
- prop :third_party_id, String
38
+ class SyncApiFlow < FlowState::Base
39
+ # typed metadata saved in the JSON `props` column
40
+ prop :record_id, String
41
+ prop :remote_api_id, String
70
42
 
43
+ # states
71
44
  state :pending
72
- state :picked
73
- state :fetching_third_party_api
74
- state :fetched_third_party_api
75
- state :failed_to_fetch_third_party_api, error: true
76
- state :saving_my_record
77
- state :saved_my_record
78
- state :failed_to_save_my_record, error: true
79
- state :completed
45
+ state :fetching
46
+ state :fetched
47
+ state :saving
48
+ state :saved
49
+ state :failed_fetch, error: true
50
+ state :failed_save, error: true
51
+ state :done
80
52
 
81
- persist :third_party_api_response
53
+ # mandatory
54
+ initial_state :pending
55
+ completed_state :done
56
+ destroy_on_complete # <— remove if you prefer to keep rows
82
57
 
83
- initial_state :pending
58
+ # artefacts persisted at runtime
59
+ persists :api_response, Hash
84
60
 
85
- def pick!
86
- transition!(
87
- from: %i[pending],
88
- to: :picked,
89
- after_transition: -> { enqueue_fetch }
90
- )
91
- end
61
+ # public API ---------------------------------------------------------
92
62
 
93
- def start_third_party_api_request!
94
- transition!(
95
- from: %i[picked failed_to_fetch_third_party_api],
96
- to: :fetching_third_party_api
97
- )
63
+ def start_fetch!
64
+ transition!(from: :pending, to: :fetching)
98
65
  end
99
66
 
100
- def finish_third_party_api_request!(result)
67
+ def finish_fetch!(response)
101
68
  transition!(
102
- from: :fetching_third_party_api,
103
- to: :fetched_third_party_api,
104
- persists: :third_party_api_response,
105
- after_transition: -> { enqueue_save }
106
- ) { result }
69
+ from: :fetching,
70
+ to: :fetched,
71
+ persist: :api_response,
72
+ after_transition: -> { SaveJob.perform_later(id) }
73
+ ) { response }
107
74
  end
108
75
 
109
- def fail_third_party_api_request!
110
- transition!(
111
- from: :fetching_third_party_api,
112
- to: :failed_to_fetch_third_party_api
113
- )
114
- end
115
-
116
- def start_record_save!
117
- transition!(
118
- from: %i[fetched_third_party_api failed_to_save_my_record],
119
- to: :saving_my_record,
120
- guard: -> { flow_artefacts.where(name: 'third_party_api_response').exists? }
121
- )
76
+ def fail_fetch!
77
+ transition!(from: :fetching, to: :failed_fetch)
122
78
  end
123
79
 
124
- def finish_record_save!
125
- transition!(
126
- from: :saving_my_record,
127
- to: :saved_my_record,
128
- after_transition: -> { complete! }
129
- )
80
+ def start_save!
81
+ transition!(from: :fetched, to: :saving)
130
82
  end
131
83
 
132
- def fail_record_save!
133
- transition!(
134
- from: :saving_my_record,
135
- to: :failed_to_save_my_record
136
- )
84
+ def finish_save!
85
+ transition!(from: :saving, to: :saved, after_transition: -> { complete! })
137
86
  end
138
87
 
139
- def complete!
140
- transition!(from: :saved_my_record, to: :completed, after_transition: -> { destroy })
141
- end
142
-
143
- private
144
-
145
- def enqueue_fetch
146
- FetchThirdPartyJob.perform_later(flow_id: id)
88
+ def fail_save!
89
+ transition!(from: :saving, to: :failed_save)
147
90
  end
148
91
 
149
- def enqueue_save
150
- SaveLocalRecordJob.perform_later(flow_id: id)
92
+ def complete!
93
+ transition!(from: :saved, to: :done)
151
94
  end
152
95
  end
153
96
  ```
154
97
 
155
- ---
156
-
157
- ### Background Jobs
158
-
159
- Each job moves the flow through the correct states, step-by-step.
160
-
161
- ---
162
-
163
- **Create and start the flow**
98
+ ### 2 Kick it off
164
99
 
165
100
  ```ruby
166
- flow = SyncThirdPartyApiFlow.create(
167
- my_record_id: "my_local_record_id",
168
- third_party_id: "some_service_id"
169
- )
101
+ flow = SyncApiFlow.create!(props: {
102
+ "record_id" => record.id,
103
+ "remote_api_id" => remote_id
104
+ })
170
105
 
171
- flow.pick!
106
+ flow.start_fetch!
107
+ FetchJob.perform_later(flow.id)
172
108
  ```
173
109
 
174
- ---
175
-
176
- **Fetch Third Party API Response**
110
+ ### 3 Jobs move the flow
177
111
 
178
112
  ```ruby
179
- class FetchThirdPartyJob < ApplicationJob
180
- retry_on StandardError,
181
- wait: ->(executions) { 10.seconds * (2**executions) },
182
- attempts: 3
113
+ class FetchJob < ApplicationJob
114
+ def perform(flow_id)
115
+ flow = SyncApiFlow.find(flow_id)
116
+
117
+ response = ThirdParty::Client.new(flow.props["remote_api_id"]).get
118
+ flow.finish_fetch!(response)
119
+ rescue StandardError => e
120
+ begin
121
+ flow.fail_fetch!
122
+ rescue StandardError
123
+ nil
124
+ end
125
+ raise e
126
+ end
127
+ end
183
128
 
184
- def perform(flow_id:)
185
- @flow_id = flow_id
129
+ class SaveJob < ApplicationJob
130
+ def perform(flow_id)
131
+ flow = SyncApiFlow.find(flow_id)
186
132
 
187
- flow.start_third_party_api_request!
133
+ flow.start_save!
188
134
 
189
- response = ThirdPartyApiRequest.new(id: flow.third_party_id).to_h
135
+ MyRecord.find(flow.props["record_id"]).update!(payload: artefact(flow, :api_response))
190
136
 
191
- flow.finish_third_party_api_request!(response)
192
- rescue
193
- flow.fail_third_party_api_request!
194
- raise
137
+ flow.finish_save!
138
+ rescue StandardError => e
139
+ begin
140
+ flow.fail_save!
141
+ rescue StandardError
142
+ nil
143
+ end
144
+ raise e
145
+ end
195
146
  end
196
147
 
197
148
  private
198
149
 
199
- def flow
200
- @flow ||= SyncThirdPartyApiFlow.find(@flow_id)
150
+ def artefact(flow, name)
151
+ flow.flow_artefacts.find_by!(name: name.to_s).payload
201
152
  end
202
153
  end
203
154
  ```
204
155
 
205
- ---
156
+ That’s it – every step, timestamp, artefact and error is stored automatically.
206
157
 
207
- **Save Result to Local Database**
158
+ ---
208
159
 
209
- ```ruby
210
- class SaveLocalRecordJob < ApplicationJob
211
- def perform(flow_id:)
212
- @flow_id = flow_id
160
+ ## API reference
213
161
 
214
- flow.start_record_save!
162
+ ### DSL macros
215
163
 
216
- record.update!(payload: third_party_payload)
164
+ | Macro | Description |
165
+ | --------------------------------- | --------------------------------------------------------------------- |
166
+ | `state :name, error: false` | Declare a state. `error: true` marks it as a failure state. |
167
+ | `initial_state :name` | **Required.** First state assigned to new flows. |
168
+ | `completed_state :name` | **Required.** Terminal state that marks the flow as finished. |
169
+ | `destroy_on_complete(flag: true)` | Delete the row automatically once the flow reaches `completed_state`. |
170
+ | `prop :key, Type` | Typed key stored in JSONB `props`. Access via `flow.props["key"]`. |
171
+ | `persists :name, Type` | Declare an artefact that can be saved during a transition. |
217
172
 
218
- flow.finish_record_save!
219
- rescue
220
- flow.fail_record_save!
221
- raise
222
- end
173
+ ### Instance helpers
223
174
 
224
- private
175
+ | Method | Use |
176
+ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
177
+ | `transition!(from:, to:, guard: nil, persist: nil, after_transition: nil) { ... }` | Perform a state change with optional guard, artefact persistence and callback. |
178
+ | `completed?` | `true` if `current_state == completed_state`. |
179
+ | `errored?` | `true` if the current state is marked `error: true`. |
225
180
 
226
- def flow
227
- @flow ||= SyncThirdPartyApiFlow.find(@flow_id)
228
- end
181
+ ---
229
182
 
230
- def third_party_payload
231
- flow.flow_artefacts
232
- .find_by!(name: 'third_party_api_response')
233
- .payload
234
- end
183
+ ## Installation
235
184
 
236
- def record
237
- @record ||= MyRecord.find(flow.my_record_id)
238
- end
239
- end
185
+ ```bash
186
+ bundle add flow_state
187
+ bin/rails generate flow_state:install
188
+ bin/rails db:migrate
240
189
  ```
241
190
 
242
- ---
243
-
244
- ## Why use FlowState?
245
-
246
- Because it enables you to model workflows explicitly,
247
- and track real-world execution reliably —
248
- **without any magic**.
191
+ Follow the [migration guide](./MIGRATION_0_1_to_0_2.md) if you’re upgrading from 0.1.
249
192
 
250
193
  ---
251
194
 
252
195
  ## License
253
196
 
254
- MIT.
197
+ MIT
@@ -3,11 +3,14 @@
3
3
  module FlowState
4
4
  # Base Model to be extended by app flows
5
5
  class Base < ActiveRecord::Base # rubocop:disable Metrics/ClassLength
6
- class UnknownStateError < StandardError; end
6
+ class UnknownStateError < StandardError; end
7
7
  class InvalidTransitionError < StandardError; end
8
8
  class PayloadValidationError < StandardError; end
9
- class GuardFailedError < StandardError; end
10
- class UnknownArtefactError < StandardError; end
9
+ class PropsValidationError < StandardError; end
10
+ class GuardFailedError < StandardError; end
11
+ class UnknownArtefactError < StandardError; end
12
+ class MissingInitialStateError < StandardError; end
13
+ class MissingCompletedStateError < StandardError; end
11
14
 
12
15
  DEPRECATOR = ActiveSupport::Deprecation.new(FlowState::VERSION, 'FlowState')
13
16
 
@@ -32,12 +35,23 @@ module FlowState
32
35
  name ? @initial_state = name.to_sym : @initial_state
33
36
  end
34
37
 
38
+ def completed_state(name = nil)
39
+ name ? @completed_state = name.to_sym : @completed_state
40
+ end
41
+
42
+ def destroy_on_complete(flag: true)
43
+ @destroy_on_complete = flag
44
+ end
45
+
46
+ def destroy_on_complete?
47
+ !!@destroy_on_complete
48
+ end
49
+
35
50
  def prop(name, type)
36
- payload_schema[name.to_sym] = type
37
- define_method(name) { payload&.dig(name.to_s) }
51
+ props_schema[name.to_sym] = type
38
52
  end
39
53
 
40
- def persist(name, type)
54
+ def persists(name, type)
41
55
  artefact_schema[name.to_sym] = type
42
56
  end
43
57
 
@@ -49,8 +63,8 @@ module FlowState
49
63
  @error_states ||= []
50
64
  end
51
65
 
52
- def payload_schema
53
- @payload_schema ||= {}
66
+ def props_schema
67
+ @props_schema ||= {}
54
68
  end
55
69
 
56
70
  def artefact_schema
@@ -59,13 +73,15 @@ module FlowState
59
73
  end
60
74
 
61
75
  validates :current_state, presence: true
62
- validate :validate_payload
76
+ validate :validate_props
77
+ after_commit :handle_completion, on: :update
63
78
 
79
+ after_initialize :validate_initial_states!, if: :new_record?
64
80
  after_initialize :assign_initial_state, if: :new_record?
65
81
 
66
- def transition!(from:, to:, guard: nil, persists: nil, after_transition: nil, &block)
67
- setup_transition!(from, to, guard, persists, &block)
68
- perform_transition!(to, persists)
82
+ def transition!(from:, to:, guard: nil, persist: nil, after_transition: nil, &block)
83
+ setup_transition!(from, to, guard, persist, &block)
84
+ perform_transition!(to, persist)
69
85
  after_transition&.call
70
86
  end
71
87
 
@@ -73,10 +89,35 @@ module FlowState
73
89
  self.class.error_states.include?(current_state&.to_sym)
74
90
  end
75
91
 
92
+ def completed?
93
+ self.class.completed_state && current_state&.to_sym == self.class.completed_state
94
+ end
95
+
96
+ def handle_completion
97
+ return unless completed?
98
+
99
+ if self.class.destroy_on_complete?
100
+ destroy!
101
+ elsif completed_at.nil?
102
+ update_column(:completed_at, Time.current)
103
+ end
104
+ end
105
+
76
106
  private
77
107
 
108
+ def validate_initial_states!
109
+ init_state = self.class.initial_state
110
+ comp_state = self.class.completed_state
111
+
112
+ raise MissingInitialStateError, "#{self.class} must declare initial_state" unless init_state
113
+ raise MissingCompletedStateError, "#{self.class} must declare completed_state" unless comp_state
114
+
115
+ unknown = [init_state, comp_state] - self.class.all_states
116
+ raise UnknownStateError, "unknown #{unknown.join(', ')}" if unknown.any?
117
+ end
118
+
78
119
  def assign_initial_state
79
- self.current_state ||= resolve_initial_state
120
+ self.current_state ||= self.class.initial_state
80
121
  end
81
122
 
82
123
  def setup_transition!(from, to, guard, persists, &block)
@@ -97,7 +138,11 @@ module FlowState
97
138
  transitioned_from: current_state,
98
139
  transitioned_to: to
99
140
  )
100
- update!(current_state: to)
141
+
142
+ attrs = { current_state: to }
143
+ attrs[:last_errored_at] = (Time.current if self.class.error_states.include?(to.to_sym))
144
+ update!(attrs)
145
+
101
146
  persist_artefact! if persists
102
147
  end
103
148
  end
@@ -126,7 +171,8 @@ module FlowState
126
171
  def persist_artefact!
127
172
  expected = self.class.artefact_schema[@artefact_name]
128
173
  unless @artefact_data.is_a?(expected)
129
- raise PayloadValidationError, "artefact #{@artefact_name} must be #{expected}"
174
+ raise PayloadValidationError,
175
+ "artefact #{@artefact_name} must be #{expected}"
130
176
  end
131
177
 
132
178
  @tr.flow_artefacts.create!(
@@ -135,28 +181,22 @@ module FlowState
135
181
  )
136
182
  end
137
183
 
138
- def resolve_initial_state
139
- init = self.class.initial_state || self.class.all_states.first
140
- ensure_known_states!([init]) if init
141
- init
142
- end
143
-
144
184
  def ensure_known_states!(states)
145
185
  unknown = states - self.class.all_states
146
186
  raise UnknownStateError, "unknown #{unknown.join(', ')}" if unknown.any?
147
187
  end
148
188
 
149
- def validate_payload
150
- schema = self.class.payload_schema
189
+ def validate_props
190
+ schema = self.class.props_schema
151
191
  return if schema.empty?
152
192
 
153
193
  schema.each do |key, klass|
154
- v = payload&.dig(key.to_s)
155
- raise PayloadValidationError, "#{key} missing" unless v
156
- raise PayloadValidationError, "#{key} must be #{klass}" unless v.is_a?(klass)
194
+ v = props&.dig(key.to_s)
195
+ raise PropsValidationError, "#{key} missing" unless v
196
+ raise PropsValidationError, "#{key} must be #{klass}" unless v.is_a?(klass)
157
197
  end
158
- rescue PayloadValidationError => e
159
- errors.add(:payload, e.message)
198
+ rescue PropsValidationError => e
199
+ errors.add(:props, e.message)
160
200
  end
161
201
  end
162
202
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FlowState
4
- VERSION = '0.1.6'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -4,8 +4,11 @@
4
4
  class CreateFlowStateFlows < ActiveRecord::Migration[8.0]
5
5
  def change
6
6
  create_table :flow_state_flows do |t|
7
+ t.string :type, null: false
7
8
  t.string :current_state, null: false
8
- t.json :payload
9
+ t.datetime :completed_at
10
+ t.datetime :last_errored_at
11
+ t.json :props
9
12
  t.timestamps
10
13
  end
11
14
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flow_state
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Garrett
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-04-22 00:00:00.000000000 Z
11
+ date: 2025-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -37,6 +37,7 @@ files:
37
37
  - ".rubocop.yml"
38
38
  - CHANGELOG.md
39
39
  - LICENSE.txt
40
+ - MIGRATION_0_1_to_0_2.md
40
41
  - README.md
41
42
  - Rakefile
42
43
  - lib/flow_state.rb
@@ -58,7 +59,13 @@ metadata:
58
59
  source_code_uri: https://github.com/hyperlaunch/flow-state
59
60
  changelog_uri: https://github.com/hyperlaunch/flow-state/changelog.md
60
61
  rubygems_mfa_required: 'true'
61
- post_install_message:
62
+ post_install_message: |
63
+ **FlowState 0.2 contains breaking changes.**
64
+
65
+ If you are upgrading from any 0.1.x release,
66
+ read the migration guide first:
67
+
68
+ https://github.com/hyperlaunch/flow_state/blob/main/MIGRATION_0_1_to_0_2.md
62
69
  rdoc_options: []
63
70
  require_paths:
64
71
  - lib