state_machine_enum 0.1.2 → 0.1.3

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: 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