statesman-multi_state 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +113 -21
- data/lib/statesman/multi_state/active_record_macro.rb +2 -2
- data/lib/statesman/multi_state/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da663a5e7b1efa3dd4494fee8bcd665783b2abaf4decc948d9b538c800439833
|
4
|
+
data.tar.gz: 31fadae9e9c629c966a5ad0f23db3cee60cf27e7536491d48b2e78d7248c9318
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51fefdd8d1620a90dd189f5299cacc04242542111f3d259920f431058d8f9a62e5c7b112f0dca05d11700082976aff8dfd6d1fc3de7f4058f930450e687c10e3
|
7
|
+
data.tar.gz: 52c6bcbad70363cad930ead5cc0a2f368871dbc9c34787909e7be3ed5f9c5f4bf9525cb29f39c3e8152439f00e7fe48d27b040b91d4e3a2632710a5c604be6ea
|
data/README.md
CHANGED
@@ -1,42 +1,134 @@
|
|
1
|
-
# Statesman::MultiState
|
2
|
-
![Build](https://github.com/chaadow/statesman-multi_state/actions/workflows/ruby.yml
|
1
|
+
# Statesman::MultiState
|
2
|
+
[![Gem](https://img.shields.io/gem/v/statesman-multi_state?style=for-the-badge)](https://rubygems.org/gems/statesman-multi_state) [![Build Status](https://img.shields.io/github/actions/workflow/status/chaadow/statesman-multi_state/ruby.yml?style=for-the-badge)](https://github.com/chaadow/statesman-multi_state/actions/workflows/ruby.yml)
|
3
3
|
|
4
|
-
Handle
|
4
|
+
Handle multiple state machines on the same ActiveRecord model for `statesman` through `has_one_state_machine` ActiveRecord macro.
|
5
|
+
|
6
|
+
You can generate as many state machines and transition classes as you wishes, and plug them on the same model.
|
7
|
+
|
8
|
+
The `has_one_state_machine` will generate all the necessary instance and class methods, prefixed by the state machine name.
|
9
|
+
So each state machine is isolated from each other.
|
10
|
+
|
11
|
+
ActiveRecord scope `.in_state` is also prefixed.
|
12
|
+
|
13
|
+
Finally, you can have the same state machine name on different AR models.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
Add this line to your application's Gemfile, and you should be all set. No need to include anything.
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem "statesman-multi_state"
|
20
|
+
```
|
5
21
|
|
6
22
|
## Usage
|
7
23
|
|
8
|
-
|
9
|
-
you need to add is this in your model:
|
24
|
+
|
10
25
|
|
11
26
|
```ruby
|
12
|
-
class
|
13
|
-
has_one_state_machine :
|
27
|
+
class Order < ActiveRecord::Base
|
28
|
+
has_one_state_machine :business_status, state_machine_klass: 'OrderBusinessStatusStateMachine', transition_klass: 'OrderBusinessStatus'
|
29
|
+
has_one_state_machine :admin_status, state_machine_klass: 'StateMachineKlass', transition_klass: 'MyTransitionKlass'
|
14
30
|
end
|
15
31
|
```
|
16
32
|
|
17
|
-
|
18
|
-
|
33
|
+
Calling `has_one_state_machine` on `business_status` adds the following:
|
34
|
+
- A has_many association with the transition table
|
35
|
+
```ruby
|
36
|
+
has_many :order_business_status_transitions, dependent: :destroy
|
37
|
+
```
|
38
|
+
- a virtual attribute `#business_status_state_form` (using the `ActiveRecord::Attributes` API ) to represent the actual current value of the state machine on the transition table.
|
39
|
+
This also allows to be used in forms directly without the need for nested attributes. Using this API instead of a regular `attr_accessor` allows us to get all the benefits of dirty tracking, so we can track when changes were made, and perform the appropriate transitions.
|
40
|
+
It default to `business_status` current state on the state machine
|
19
41
|
```ruby
|
20
|
-
|
21
|
-
|
42
|
+
attribute :order_business_state_form
|
43
|
+
|
44
|
+
<% form_for @order do |f| %>
|
45
|
+
...
|
46
|
+
f.select :order_business_state_form, ...
|
47
|
+
...
|
48
|
+
<% end %>
|
49
|
+
|
50
|
+
```
|
51
|
+
- An instance method `#business_state_state_machine` pointing to an instance of the state machine. as well as delegating all methods to it.
|
52
|
+
```ruby
|
53
|
+
def business_status_state_machine
|
54
|
+
@business_status_state_machine ||= OrderBusinessStatusStateMachine.new(...)
|
55
|
+
|
56
|
+
# Ex. Order.new.business_status_current_state
|
57
|
+
%w[current_state in_state? transition_to transition_to! can_transition_to? history last_transition
|
58
|
+
last_transition_to].each { delegate _1, to: :business_status_state_machine, prefix: :business_status }
|
59
|
+
end
|
60
|
+
```
|
61
|
+
- ActiveRecord scopes prefixed by the state machine field name :
|
62
|
+
```ruby
|
63
|
+
Order.business_status_in_state('...')
|
64
|
+
Order.business_status_not_in_state('...')
|
65
|
+
|
66
|
+
Order.admin_status_in_state('...')
|
67
|
+
Order.admin_status_not_in_state('...')
|
22
68
|
```
|
69
|
+
- I18n instance and class methods helpers following some convention :
|
70
|
+
```ruby
|
71
|
+
Order.business_status_human_wrapper
|
72
|
+
Order.admin_status_human_wrapper
|
23
73
|
|
24
|
-
|
25
|
-
|
74
|
+
Order.new.business_status_current_state_human
|
75
|
+
Order.new.admin_status_current_state_human
|
76
|
+
```
|
77
|
+
It expects the I18n nomenclature to be like the following having the state machine as a prefix. So for `Order` AR class, and `business_status` as a state machine name, the key should be `business_status_order`
|
78
|
+
```yml
|
79
|
+
en:
|
80
|
+
statesman:
|
81
|
+
business_status_order:
|
82
|
+
user_pending: User Pending
|
83
|
+
processed: Processed
|
84
|
+
admin_status_order:
|
85
|
+
admin_pending: Admin Pending
|
86
|
+
validated: Validated
|
87
|
+
```
|
88
|
+
|
89
|
+
- Finally, and most importantly, an instance method `#save_with_state(**options)` is defined that takes care of making all the necesary transitions, using the virtual attribute mentioned above and checking if the current state changed, and if so, it registers a callback, so it delays the `transition_to` call until all state machines have registered their calls. finally the AR model (Order for example), saves itself, and calls all the callbacks that take care of performing all the transitions.
|
90
|
+
|
91
|
+
This is done this way because:
|
92
|
+
1. Transition table expects the model ( order) to exist before hand, otherwise it would raise an error saying it's missing the Order `belongs_to` association
|
93
|
+
2. By delaying this way it handles both order creation and update, making it fully compatible with creation phase of Order as well as handling multiple state machines
|
94
|
+
|
95
|
+
Here is a unit test demonstrating `save_with_state`
|
96
|
+
```ruby
|
97
|
+
order = Order.new
|
98
|
+
assert_equal :user_pending, order.user_status_current_state.to_sym
|
99
|
+
assert_equal :admin_pending, order.admin_status_current_state.to_sym
|
100
|
+
assert_equal 0, UserStatusOrderTransition.count
|
101
|
+
assert_equal 0, AdminStatusOrderTransition.count
|
26
102
|
|
27
|
-
|
28
|
-
|
103
|
+
# This is equivalent to a user selecting a state in a rails form, using the virtual attributes defined above
|
104
|
+
order.user_status_state_form = 'processed'
|
105
|
+
order.admin_status_state_form = 'validated'
|
106
|
+
|
107
|
+
order.save_with_state # You should call this method from now on in your `#create` and `#update` controller actions
|
108
|
+
|
109
|
+
assert_equal :processed, order.user_status_current_state.to_sym
|
110
|
+
assert_equal :validated, order.admin_status_current_state.to_sym
|
111
|
+
assert_equal 1, UserStatusOrderTransition.count
|
112
|
+
assert_equal 1, AdminStatusOrderTransition.count
|
29
113
|
```
|
30
114
|
|
31
|
-
|
32
|
-
|
33
|
-
|
115
|
+
|
116
|
+
It also plugs into `ActiveRecord::Reflection` APis, so you can go fancy with
|
117
|
+
dynamic form generation
|
118
|
+
```ruby
|
119
|
+
Order.state_machine_reflections # { 'business_status' => Reflection(..), 'admin_status' => Reflection(..) }
|
120
|
+
Order.reflect_on_all_state_machines
|
121
|
+
Order.reflect_on_state_machine(:state)
|
34
122
|
```
|
123
|
+
This is similiar to `has_one_attached` / `has_many` / `belongs_to` reflections.
|
35
124
|
|
36
|
-
|
37
|
-
```
|
38
|
-
|
125
|
+
For multiple states on a model, this can allow you to generate generic partials/components, and iterate on your model dynamically based on the state machines defined on the model.
|
126
|
+
```ruby
|
127
|
+
<% order.class.state_machine_reflections.keys.each do |state| %> # ['business_status, 'admin_status']
|
128
|
+
<%= render "my_state_machine_partial", state: state %>
|
129
|
+
<%>
|
39
130
|
```
|
40
131
|
|
132
|
+
|
41
133
|
## License
|
42
134
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -8,9 +8,9 @@ module Statesman
|
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
10
|
class_methods do
|
11
|
-
def has_one_state_machine(field_name, state_machine_klass:, transition_klass:,
|
11
|
+
def has_one_state_machine(field_name, state_machine_klass:, transition_klass:,
|
12
|
+
transition_name: transition_klass.to_s.underscore.pluralize.to_sym, virtual_attribute_name: "#{field_name}_state_form")
|
12
13
|
state_machine_name = "#{field_name}_state_machine"
|
13
|
-
virtual_attribute_name = "#{field_name}_state_form"
|
14
14
|
|
15
15
|
# To handle STI, this needs to be done to get the base klass
|
16
16
|
base_klass = caller_locations.first.label.split(':').last[...-1]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: statesman-multi_state
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chedli Bourguiba
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-03-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -38,8 +38,8 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 9.0.0
|
41
|
-
description:
|
42
|
-
macro
|
41
|
+
description: Rails/Statesman plugin that handle multiple state machines on the same
|
42
|
+
model through `has_one_state_machine` ActiveRecord macro
|
43
43
|
email:
|
44
44
|
- bourguiba.chedli@gmail.com
|
45
45
|
executables: []
|
@@ -81,5 +81,6 @@ requirements: []
|
|
81
81
|
rubygems_version: 3.1.6
|
82
82
|
signing_key:
|
83
83
|
specification_version: 4
|
84
|
-
summary:
|
84
|
+
summary: Rails/Statesman plugin that handle multiple state machines on the same model
|
85
|
+
through `has_one_state_machine` ActiveRecord macro
|
85
86
|
test_files: []
|