state_machine_enum 0.1.1 → 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: 78487e87868cb11ef1113ca6f2867c665fc4d146e9fc16aaa5af35c94f2497a6
4
- data.tar.gz: bbc93da1d1d5f6f4a7895fe0e50ea5762b24142102da798f2559b86594d61858
3
+ metadata.gz: 255a61ddbdf562c086709c6fbe51e37533ac15bf47bd698d0aae2086e2157569
4
+ data.tar.gz: 759c5a66316166c230d37576d2a087cb93978620a1984f9d10ece17b9ba2f89e
5
5
  SHA512:
6
- metadata.gz: 0bf79eca0db499b50f9a253af0fdce5a9f11930f972e7ccad1a98b4f66e8fa644ee5c708bf9f49498264def55ecc206bb76cd60f95d6bd3597e803cfbd5bc10f
7
- data.tar.gz: 5bbad2193e08b61c4492c10cb7c2e64a611880c393e4de37a226dd29ff5c85cc0b428db816fdc893a0f10514ece7c018c5b13a952f069d446ec3447274893ba2
6
+ metadata.gz: 8197dc76f3536c972ab610da6259dc4c0d53ebd049addd747965acb925f43be39b4bb78948edf315b3f257ce107f68db3c989a7f7340f8b65931f901a8da505c
7
+ data.tar.gz: f219d01939f927981cdd45b3c72fe76ab3a19bbd7c56028a5c6e5ebae64a36adfa36c61e1310afd0ae2ab78c3697af00f8ddee44f9afb02ee8ccc73935a13e9d
data/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
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
+
6
+ ## 0.1.3
7
+
8
+ ### Changed
9
+
10
+ - Added more documentation and improvements to the readme
11
+ - Little code styling
12
+
13
+ ## 0.1.2
14
+
15
+ ### Added
16
+
17
+ - basic tests for a library
18
+
19
+ ### Changed
20
+
21
+ - #ensure_<attribute>_may_transition_to! method now actually verifies that an attribute can transition to a new state.
22
+
23
+ ## 0.1.1
24
+
25
+ ### Added
26
+
27
+ - Provide real authorts of this gem.
28
+
29
+ ## 0.1.0
30
+
31
+ - Initial release
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.1"
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,28 +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
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}"
117
124
  end
118
125
 
119
126
  define_method(:"#{attribute_name}_may_transition_to?") do |next_state|
120
127
  val = self[attribute_name]
121
128
  return false if val == next_state.to_s
122
- collector.may_transition?(val, next_state)
129
+
130
+ collector._may_transition?(val, next_state)
123
131
  end
124
132
  end
125
133
  end
metadata CHANGED
@@ -1,31 +1,59 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: state_machine_enum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  - Sebastian van Hesteren
9
9
  - Stanislav Katkov
10
10
  autorequire:
11
- bindir: exe
11
+ bindir: bin
12
12
  cert_chain: []
13
- date: 2024-06-07 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
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  requirements:
19
- - - ">="
19
+ - - "~>"
20
20
  - !ruby/object:Gem::Version
21
- version: '6.0'
21
+ version: '7'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
- - - ">="
26
+ - - "~>"
27
27
  - !ruby/object:Gem::Version
28
- version: '6.0'
28
+ version: '7'
29
+ - !ruby/object:Gem::Dependency
30
+ name: sqlite3
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '1.4'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '1.4'
43
+ - !ruby/object:Gem::Dependency
44
+ name: activerecord
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '7'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '7'
29
57
  description: Concern that makes it easy to define and enforce possibe state transitions
30
58
  for a field/object
31
59
  email:
@@ -37,6 +65,7 @@ extensions: []
37
65
  extra_rdoc_files: []
38
66
  files:
39
67
  - ".standard.yml"
68
+ - CHANGELOG.md
40
69
  - LICENSE.txt
41
70
  - README.md
42
71
  - Rakefile
@@ -49,6 +78,7 @@ licenses:
49
78
  metadata:
50
79
  homepage_uri: https://cheddar.me
51
80
  source_code_uri: https://github.com/cheddar-me/state_machine_enum
81
+ changelog_uri: https://github.com/cheddar-me/state_machine_enum/blob/main/CHANGELOG.md
52
82
  post_install_message:
53
83
  rdoc_options: []
54
84
  require_paths:
@@ -64,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
94
  - !ruby/object:Gem::Version
65
95
  version: '0'
66
96
  requirements: []
67
- rubygems_version: 3.5.10
97
+ rubygems_version: 3.5.18
68
98
  signing_key:
69
99
  specification_version: 4
70
100
  summary: Define possible state transitions for a field