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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: abc46124837badbc6906ffb68ad2dfb368c1a79baadb7d90064527d2c22b5afc
4
- data.tar.gz: 0b7c420e4a634798411eae000309d0b7f156db80745e8091aa3525a2d1488ca9
3
+ metadata.gz: da663a5e7b1efa3dd4494fee8bcd665783b2abaf4decc948d9b538c800439833
4
+ data.tar.gz: 31fadae9e9c629c966a5ad0f23db3cee60cf27e7536491d48b2e78d7248c9318
5
5
  SHA512:
6
- metadata.gz: f25f298ab42306d5cf579b41c3382a11e205e5fe8f7f159fd82c456f7651f3af99d4753b8e3789911f8cb069f32940c2b1230e7701a64bea11f5068af443e4bc
7
- data.tar.gz: e3eea7741b07995f8ba036c49036cb405c90563c15df46df8e499eb39805e9b2d61fa904e729e87227cd21383767105e854cbc2c865540d94d9cdd201b74830b
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/badge.svg)
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 multi state for `statesman` through `has_one_state_machine` ActiveRecord macro
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
- After you generate your transition classes as well as your state machines, all
9
- you need to add is this in your model:
24
+
10
25
 
11
26
  ```ruby
12
- class MyActiveRecordModel < ActiveRecord::Base
13
- has_one_state_machine :state, state_machine_klass: 'StateMachineKlass', transition_klass: 'MyTransitionKlass'
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
- It also plugs into `ActiveRecord::Reflection` apis, so you can go fancy with
18
- dynamic form generation
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
- MyActiveRecordModel.reflect_on_all_state_machines
21
- MyActiveRecordModel.reflect_on_state_machine(:state)
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
- ## Installation
25
- Add this line to your application's Gemfile:
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
- ```ruby
28
- gem "statesman-multi_state"
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
- And then execute:
32
- ```bash
33
- $ bundle
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
- Or install it yourself as:
37
- ```bash
38
- $ gem install statesman-multi_state
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:, transition_name: transition_klass.to_s.underscore.pluralize.to_sym)
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]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Statesman
4
4
  module MultiState
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.2'
6
6
  end
7
7
  end
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.0
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: 2022-10-07 00:00:00.000000000 Z
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: Handle multi state for statesman through `has_one_state_machine` ActiveRecord
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: Handle multi state for statesman through has_one_state_machine
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: []