state_machine_enum 0.1.2 → 0.1.3

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: 4902a42140176efda107ea4e5caef0b4bd3f8e64554b2fde61f3507e51e80d96
4
- data.tar.gz: 9f85490534c6667640cd050e13eeaf7ce202bbd5757e7ade6cd4d6cb4c191b90
3
+ metadata.gz: 255a61ddbdf562c086709c6fbe51e37533ac15bf47bd698d0aae2086e2157569
4
+ data.tar.gz: 759c5a66316166c230d37576d2a087cb93978620a1984f9d10ece17b9ba2f89e
5
5
  SHA512:
6
- metadata.gz: 10ae6f605953606f563a80aa15b9cc7a70eedc73593d152aba3d5ab773b0243002dc13b0f66888e83008d6c0a341bee762a5bdbde61712e2310833b1345bc067
7
- data.tar.gz: 8b7c7b452a1e0b962b9ef2f643dcdd220597474c87269dc4565bb5a8974af000e78bed0f2c41f8da40adf663a51bcdd21d0a6b7bb9bb3bab0f8a597c36d90032
6
+ metadata.gz: 8197dc76f3536c972ab610da6259dc4c0d53ebd049addd747965acb925f43be39b4bb78948edf315b3f257ce107f68db3c989a7f7340f8b65931f901a8da505c
7
+ data.tar.gz: f219d01939f927981cdd45b3c72fe76ab3a19bbd7c56028a5c6e5ebae64a36adfa36c61e1310afd0ae2ab78c3697af00f8ddee44f9afb02ee8ccc73935a13e9d
data/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file.
3
3
 
4
4
  This format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
5
 
6
+ ## 0.1.3
7
+
8
+ ### Changed
9
+
10
+ - Added more documentation and improvements to the readme
11
+ - Little code styling
12
+
6
13
  ## 0.1.2
7
14
 
8
15
  ### Added
data/README.md CHANGED
@@ -1,7 +1,10 @@
1
1
  # StateMachineEnum
2
2
 
3
- This concern adds a method called "state_machine_enum" useful for defining an enum using string values along with valid state transitions. Validations will be added for the state transitions and a proper enum is going to be defined. For example:
3
+ This concern adds a method called "state_machine_enum".
4
+ Useful for defining an enum using string values along with valid state transitions.
5
+ Validations will be added for the state transitions and a proper enum is going to be defined.
4
6
 
7
+ For example:
5
8
  ```ruby
6
9
  state_machine_enum :state do |states|
7
10
  states.permit_transition(:created, :approved_pending_settlement)
@@ -13,7 +16,7 @@ end
13
16
 
14
17
  ## Installation
15
18
 
16
- Install the gem and add to the application's Gemfile by executing:
19
+ Install the gem and add it to the application's Gemfile by executing:
17
20
 
18
21
  $ bundle add state_machine_enum
19
22
 
@@ -23,33 +26,117 @@ If bundler is not being used to manage dependencies, install the gem by executin
23
26
 
24
27
  ## Usage
25
28
 
26
- StateMachineEnum needs to be extended and then it could be used, as example in AR model.
29
+ StateMachineEnum needs to be included and then it could be used, for example, in an ActiveRecord model.
27
30
 
28
- ```
31
+ ```ruby
29
32
  class User < ApplicationRecord
30
33
  include StateMachineEnum
31
34
 
32
- state_machine_enum :state do |s|
35
+ state_machine_enum :state, prefix: :state do |s|
33
36
  s.permit_transition(:registered, :active)
34
37
  s.permit_transition(:active, :banned)
35
38
  s.permit_transition(:banned, :active)
36
39
  s.permit_transition(:active, :deleted)
37
40
  end
38
41
  end
42
+
43
+ user = User.new(state: 'active')
44
+ # with the prefix: :state
45
+ user.state_active? # => true
46
+ # or without the prefix: :state
47
+ user.active? # => true
48
+
49
+ # The transition check happens when updating the state like this
50
+ user.update!(state: :registered)
51
+ # or when using the shortcut (add state_ because we have prefix: :state above)
52
+ user.state_registered!
53
+ ```
54
+ The last command throws an InvalidState error: Invalid transition from active to registered
55
+ This is because the state was not permitted to transition back to "registered" from "active".
56
+ If you do want this, `s.permit_transition(:active, :registered)` should be added.
57
+
58
+ # API
59
+
60
+ ## state_machine_enum(state, prefix: nil) &block
61
+ Creation method that sets up the state_machine_enum in your ruby object.
62
+ Note the prefix here to prefix the method. This is optional of course.
63
+ This works the same as when you would add `enum :state, {registered: "registered"}` in rails for example, except when using state_machine_enum
64
+ you don't need to add an `enum` as well, we do this for you.
65
+
66
+ ## after_inline_transition_to(to) &block
67
+ Runs the block inside `after_inline_transition_to` as a before_save action.
68
+ For example the state updates to :registered, but before the model is saved
69
+
70
+ ```ruby
71
+ state_machine_enum :state, prefix: "state" do |s|
72
+ s.permit_transition(:registered, :active)
73
+ s.after_inline_transition_to(:active) do |model|
74
+ model.another_attr = Time.now.utc
75
+ end
76
+ end
77
+ ```
78
+
79
+ `another_attr` is automatically set to the current utc time.
80
+
81
+ ## after_committed_transition_to(to) &block
82
+ Runs the block inside `after_committed_transition_to` as an after_commit action.
83
+ For example if you want to do something after it has committed to the database when the state is
84
+ updated to :registered
85
+
86
+ ```ruby
87
+ state_machine_enum :state, prefix: "state" do |s|
88
+ s.permit_transition(:registered, :active)
89
+ s.after_committed_transition_to(:active) do |model|
90
+ model.send_notification!
91
+ end
92
+ end
39
93
  ```
40
94
 
41
- And then it will offer bunch of convenient methods and callbacks that ensure proper state transitions.
95
+ ## after_any_committed_transition_to(to) &block
96
+ Runs together with all the `after_committed_transition_to` hooks.
97
+ For example if you want to do something after any state update has commited.
42
98
 
99
+ ```ruby
100
+ state_machine_enum :state, prefix: "state" do |s|
101
+ s.permit_transition(:registered, :active)
102
+ s.permit_transition(:active, :suspended)
103
+ s.after_any_committed_transition_to do |model|
104
+ log_changes!
105
+ end
106
+ end
43
107
  ```
44
- user = User.new(state: 'registered')
45
- user.active?
46
- user.registered! # throws InvalidState error, because state can not transition to "registered".
108
+
109
+ ## Ensure methods
110
+ With a couple of ensure methods we can check beforehand for valid state transitions without actually having to do the state transition.
111
+ This allows you to bail out of calls where the model is not in a desired state or won't be able to perform a transition, by raising an InvalidState exception
112
+
113
+ ## ensure_<attribute>_one_of!(state1, state2, etc)
114
+ E.g. seen from the previous examples, calling `ensure_state_one_of!(:registered, :active, :fake)`
115
+ will raise an InvalidState error because :fake is not present in state enum.
116
+
117
+ ## ensure_<attribute>_may_transition_to!(to)
118
+ Calling `ensure_state_may_transition_to!(:active)` when the state is currently in :suspended
119
+ will raise an InvalidState error because we did not permite the transition from :active to :suspended.
120
+
121
+ ## <attribute>_may_transition_to?(to)
122
+ Predicate to check if a transition is possible with the rules we've set.
123
+
124
+ ```ruby
125
+ state_machine_enum :state, prefix: "state" do |s|
126
+ s.permit_transition(:registered, :active)
127
+ end
128
+
129
+ state_may_transition_to?(:active) # => true
47
130
  ```
131
+
48
132
  ## Development
49
133
 
50
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
134
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
135
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
51
136
 
52
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
137
+ To install this gem onto your local machine, run `bundle exec rake install`.
138
+ To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`,
139
+ which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
53
140
 
54
141
  ## Contributing
55
142
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StateMachineEnum
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
5
5
  end
@@ -13,16 +13,13 @@ require "active_support/concern"
13
13
  # states.permit_transition(:created, :rejected)
14
14
  # states.permit_transition(:approved_pending_settlement, :settled)
15
15
  # end
16
-
17
16
  module StateMachineEnum
18
17
  extend ActiveSupport::Concern
19
18
 
19
+ # This keeps track of the states and rules we allow.
20
20
  class StatesCollector
21
- attr_reader :states
22
- attr_reader :after_commit_hooks
23
- attr_reader :common_after_commit_hooks
24
- attr_reader :after_attribute_write_hooks
25
- attr_reader :common_after_write_hooks
21
+ attr_reader :states, :after_commit_hooks, :common_after_commit_hooks,
22
+ :after_attribute_write_hooks, :common_after_write_hooks
26
23
 
27
24
  def initialize
28
25
  @transitions = Set.new
@@ -33,38 +30,42 @@ module StateMachineEnum
33
30
  @common_after_write_hooks = []
34
31
  end
35
32
 
33
+ # Add a 'rule' that allows a transition in a single direction
36
34
  def permit_transition(from, to)
37
35
  @states << from.to_s << to.to_s
38
36
  @transitions << [from.to_s, to.to_s]
39
37
  end
40
38
 
41
- def may_transition?(from, to)
42
- @transitions.include?([from.to_s, to.to_s])
43
- end
44
-
39
+ # Runs after the attributes have changed, but before the state is saved
45
40
  def after_inline_transition_to(target_state, &blk)
46
41
  @after_attribute_write_hooks[target_state.to_s] ||= []
47
42
  @after_attribute_write_hooks[target_state.to_s] << blk.to_proc
48
43
  end
49
44
 
45
+ # Will run after the specified transition has comitted
50
46
  def after_committed_transition_to(target_state, &blk)
51
47
  @after_commit_hooks[target_state.to_s] ||= []
52
48
  @after_commit_hooks[target_state.to_s] << blk.to_proc
53
49
  end
54
50
 
51
+ # A generic block that will run together with every committed transition.
55
52
  def after_any_committed_transition(&blk)
56
53
  @common_after_commit_hooks << blk.to_proc
57
54
  end
58
55
 
59
- def validate(model, attribute_name)
56
+ def _validate(model, attribute_name)
60
57
  return unless model.persisted?
61
58
 
62
59
  was = model.attribute_was(attribute_name)
63
60
  is = model[attribute_name]
64
61
 
65
- unless was == is || @transitions.include?([was, is])
66
- model.errors.add(attribute_name, "Invalid transition from #{was} to #{is}")
67
- end
62
+ return if (was == is) || @transitions.include?([was, is])
63
+
64
+ model.errors.add(attribute_name, "Invalid transition from #{was} to #{is}")
65
+ end
66
+
67
+ def _may_transition?(from, to)
68
+ @transitions.include?([from.to_s, to.to_s])
68
69
  end
69
70
  end
70
71
 
@@ -82,12 +83,13 @@ module StateMachineEnum
82
83
 
83
84
  # Define validations for transitions
84
85
  validates attribute_name, presence: true
85
- validate { |model| collector.validate(model, attribute_name) }
86
+ validate { |model| collector._validate(model, attribute_name) }
86
87
 
87
- # Define inline hooks
88
+ # Define after attribute change (before save) hooks
88
89
  before_save do |model|
89
90
  _value_was, value_has_become = model.changes[attribute_name]
90
91
  next unless value_has_become
92
+
91
93
  hook_procs = collector.after_attribute_write_hooks[value_has_become].to_a + collector.common_after_write_hooks.to_a
92
94
  hook_procs.each do |hook_proc|
93
95
  hook_proc.call(model)
@@ -98,29 +100,34 @@ module StateMachineEnum
98
100
  after_commit do |model|
99
101
  _value_was, value_has_become = model.previous_changes[attribute_name]
100
102
  next unless value_has_become
103
+
101
104
  hook_procs = collector.after_commit_hooks[value_has_become].to_a + collector.common_after_commit_hooks.to_a
102
105
  hook_procs.each do |hook_proc|
103
106
  hook_proc.call(model)
104
107
  end
105
108
  end
106
109
 
107
- # Define the check methods
110
+ # Define the ensure methods
108
111
  define_method(:"ensure_#{attribute_name}_one_of!") do |*allowed_states|
109
112
  val = self[attribute_name]
110
113
  return if Set.new(allowed_states.map(&:to_s)).include?(val)
114
+
111
115
  raise InvalidState, "#{attribute_name} must be one of #{allowed_states.inspect} but was #{val.inspect}"
112
116
  end
113
117
 
114
118
  define_method(:"ensure_#{attribute_name}_may_transition_to!") do |next_state|
115
119
  val = self[attribute_name]
116
120
  raise InvalidState, "#{attribute_name} already is #{val.inspect}" if next_state.to_s == val
117
- raise InvalidState, "#{attribute_name} may not transition from #{val.inspect} to #{next_state.inspect}" unless collector.may_transition?(val, next_state)
121
+ return if collector._may_transition?(val, next_state)
122
+
123
+ raise InvalidState, "#{attribute_name} may not transition from #{val.inspect} to #{next_state.inspect}"
118
124
  end
119
125
 
120
126
  define_method(:"#{attribute_name}_may_transition_to?") do |next_state|
121
127
  val = self[attribute_name]
122
128
  return false if val == next_state.to_s
123
- collector.may_transition?(val, next_state)
129
+
130
+ collector._may_transition?(val, next_state)
124
131
  end
125
132
  end
126
133
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: state_machine_enum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-06-18 00:00:00.000000000 Z
13
+ date: 2024-10-07 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -94,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  requirements: []
97
- rubygems_version: 3.5.9
97
+ rubygems_version: 3.5.18
98
98
  signing_key:
99
99
  specification_version: 4
100
100
  summary: Define possible state transitions for a field