rails_machine 0.0.6 → 1.0.0
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 +5 -5
- data/README.rdoc +111 -1
- data/config/locales/rails_machine.en.yml +6 -0
- data/lib/rails_machine/configuration.rb +4 -3
- data/lib/rails_machine/version.rb +1 -1
- data/lib/rails_machine.rb +11 -16
- metadata +19 -23
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d7039049332c0e432df8f5b0607349b0bdc798201fb87c97dfa2c5e2bf95e0c0
|
|
4
|
+
data.tar.gz: c76c94732fc8d6ebdbe440a9cfd683d2da66fef62a096f94c3c58947d9628fd5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 52e2406afb168f5d94a5814958a6ca16923986143319e313c7424f7d1126aa791662faf409e612f0be2a3184f7c2f65201e2a5913af97ea832c5086ff744e7c5
|
|
7
|
+
data.tar.gz: a6566d45aee104ec86c0d57390e6b4d6918884647cf2c26685e7845225bb1654d84acf62219c664cc306a4709dd2b2c5b0af874d0d936096c31ee1dd3571c0e7
|
data/README.rdoc
CHANGED
|
@@ -1,3 +1,113 @@
|
|
|
1
1
|
= RailsMachine
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A small state machine for ActiveRecord models, built on top of Rails enums.
|
|
4
|
+
|
|
5
|
+
== Installation
|
|
6
|
+
|
|
7
|
+
Add to your Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'rails_machine'
|
|
10
|
+
|
|
11
|
+
Requires Ruby >= 3.0 and ActiveRecord >= 7.0.
|
|
12
|
+
|
|
13
|
+
== Usage
|
|
14
|
+
|
|
15
|
+
Add an integer column to your model to hold the state:
|
|
16
|
+
|
|
17
|
+
class AddStateToVehicles < ActiveRecord::Migration[7.0]
|
|
18
|
+
def change
|
|
19
|
+
add_column :vehicles, :state, :integer, default: 0, null: false
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
Include +RailsMachine+ and declare the machine:
|
|
24
|
+
|
|
25
|
+
class Vehicle < ActiveRecord::Base
|
|
26
|
+
include RailsMachine
|
|
27
|
+
|
|
28
|
+
rails_machine do
|
|
29
|
+
state :stopped
|
|
30
|
+
state :idling
|
|
31
|
+
state :driving
|
|
32
|
+
state :broken
|
|
33
|
+
|
|
34
|
+
init_state :stopped
|
|
35
|
+
|
|
36
|
+
transition from: :stopped, to: :idling
|
|
37
|
+
transition from: :idling, to: :stopped
|
|
38
|
+
transition from: :idling, to: :driving
|
|
39
|
+
transition from: :driving, to: :idling
|
|
40
|
+
|
|
41
|
+
transition from: :any, to: :broken
|
|
42
|
+
transition from: :broken, to: :stopped, guards: [->(v) { v.inspected? }]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
== States
|
|
47
|
+
|
|
48
|
+
States are stored as Rails enums. IDs start at 0 and increment by 1, or you can set them explicitly:
|
|
49
|
+
|
|
50
|
+
state :stopped # id 0
|
|
51
|
+
state :idling # id 1
|
|
52
|
+
state :archived, id: 99 # id 99
|
|
53
|
+
state :legacy # id 100
|
|
54
|
+
|
|
55
|
+
You get all the usual enum helpers for free:
|
|
56
|
+
|
|
57
|
+
vehicle.stopped? # => true
|
|
58
|
+
vehicle.idling! # persists the transition
|
|
59
|
+
Vehicle.stopped # scope
|
|
60
|
+
Vehicle.states # { "stopped" => 0, "idling" => 1, ... }
|
|
61
|
+
|
|
62
|
+
Defining the same state name twice raises +ArgumentError+.
|
|
63
|
+
|
|
64
|
+
== Init states
|
|
65
|
+
|
|
66
|
+
+init_state+ declares which states a record is allowed to start in. Records saved with any other initial value fail validation.
|
|
67
|
+
|
|
68
|
+
init_state :stopped
|
|
69
|
+
init_state :broken # multiple allowed
|
|
70
|
+
|
|
71
|
+
If no +init_state+ is declared, any state is accepted on creation.
|
|
72
|
+
|
|
73
|
+
== Transitions
|
|
74
|
+
|
|
75
|
+
Each +transition+ defines a legal +from+ → +to+ pair. Attempting an undeclared transition raises +ActiveRecord::RecordInvalid+ on save (or returns +false+ from +valid?+).
|
|
76
|
+
|
|
77
|
+
+:any+ is a wildcard:
|
|
78
|
+
|
|
79
|
+
transition from: :any, to: :broken # any state can become :broken
|
|
80
|
+
transition from: :broken, to: :any # :broken can become anything
|
|
81
|
+
transition from: :any, to: :any # no restrictions
|
|
82
|
+
|
|
83
|
+
== Guards
|
|
84
|
+
|
|
85
|
+
Guards are callables that receive the record and must return truthy for the transition to pass:
|
|
86
|
+
|
|
87
|
+
transition from: :broken, to: :stopped, guards: [->(v) { v.inspected? }]
|
|
88
|
+
|
|
89
|
+
If every guard on every matching transition returns falsy, validation adds a +:guard_failed+ error.
|
|
90
|
+
|
|
91
|
+
== Custom column
|
|
92
|
+
|
|
93
|
+
Pass +column:+ to use something other than +:state+:
|
|
94
|
+
|
|
95
|
+
rails_machine column: :status do
|
|
96
|
+
state :draft
|
|
97
|
+
state :published
|
|
98
|
+
transition from: :draft, to: :published
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
== Error messages
|
|
102
|
+
|
|
103
|
+
Validation failures add errors on the state column with these keys:
|
|
104
|
+
|
|
105
|
+
* +:invalid_init_state+ — record was created in a state not listed in +init_state+
|
|
106
|
+
* +:transition_not_found+ — no transition defined for the attempted state change
|
|
107
|
+
* +:guard_failed+ — a matching transition exists but its guard(s) returned falsy
|
|
108
|
+
|
|
109
|
+
Default English messages ship with the gem. Override them under +en.errors.messages+ in your own locale files.
|
|
110
|
+
|
|
111
|
+
== License
|
|
112
|
+
|
|
113
|
+
MIT.
|
|
@@ -4,7 +4,7 @@ module RailsMachine
|
|
|
4
4
|
attr_reader :states, :transitions, :init_states
|
|
5
5
|
|
|
6
6
|
def initialize
|
|
7
|
-
@states =
|
|
7
|
+
@states = {}
|
|
8
8
|
@init_states = []
|
|
9
9
|
@transitions = Hash.new{|hash,key| hash[key] = [] }
|
|
10
10
|
end
|
|
@@ -19,7 +19,8 @@ module RailsMachine
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def state(name, id: next_id)
|
|
22
|
-
|
|
22
|
+
raise ArgumentError, "State :#{name} is already defined" if @states.key?(name)
|
|
23
|
+
@states[name] = id
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def transition(from: :any, to: :any, guards: [])
|
|
@@ -30,7 +31,7 @@ module RailsMachine
|
|
|
30
31
|
if @states.empty?
|
|
31
32
|
0
|
|
32
33
|
else
|
|
33
|
-
@states.
|
|
34
|
+
@states.values.max.next
|
|
34
35
|
end
|
|
35
36
|
end
|
|
36
37
|
end
|
data/lib/rails_machine.rb
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
require 'rails_machine/configuration'
|
|
2
2
|
|
|
3
|
+
I18n.load_path << File.expand_path('../config/locales/rails_machine.en.yml', __dir__)
|
|
4
|
+
|
|
3
5
|
module RailsMachine
|
|
4
6
|
extend ActiveSupport::Concern
|
|
5
7
|
|
|
6
8
|
included do
|
|
7
|
-
cattr_accessor :transitions
|
|
8
|
-
|
|
9
|
-
validate :allowed_state, if: :state_changed?
|
|
10
|
-
private
|
|
11
|
-
cattr_accessor :init_states
|
|
12
|
-
end
|
|
9
|
+
cattr_accessor :transitions, instance_writer: false
|
|
10
|
+
cattr_accessor :init_states, instance_writer: false
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
if
|
|
16
|
-
validate_init_state
|
|
17
|
-
else
|
|
18
|
-
validate_transition
|
|
19
|
-
end
|
|
12
|
+
validate :validate_init_state, on: :create
|
|
13
|
+
validate :validate_transition, on: :update, if: :state_changed?
|
|
20
14
|
end
|
|
21
15
|
|
|
22
16
|
def validate_init_state
|
|
@@ -26,6 +20,7 @@ module RailsMachine
|
|
|
26
20
|
end
|
|
27
21
|
|
|
28
22
|
def valid_init_state
|
|
23
|
+
return true if self.state.nil?
|
|
29
24
|
init_states.empty? || init_states.include?(self.state.to_sym)
|
|
30
25
|
end
|
|
31
26
|
|
|
@@ -41,14 +36,14 @@ module RailsMachine
|
|
|
41
36
|
|
|
42
37
|
module ClassMethods
|
|
43
38
|
def rails_machine(column: :state, &blk)
|
|
44
|
-
raise ArgumentError unless block_given?
|
|
39
|
+
raise ArgumentError, "rails_machine requires a configuration block" unless block_given?
|
|
45
40
|
|
|
46
41
|
configuration = Configuration.new
|
|
47
42
|
configuration.run(&blk)
|
|
48
43
|
|
|
49
|
-
self.transitions = configuration.transitions
|
|
50
|
-
self.init_states = configuration.init_states
|
|
51
|
-
enum column
|
|
44
|
+
self.transitions = configuration.transitions.transform_values(&:freeze).freeze
|
|
45
|
+
self.init_states = configuration.init_states.freeze
|
|
46
|
+
enum column, configuration.states
|
|
52
47
|
|
|
53
48
|
validates_presence_of column
|
|
54
49
|
end
|
metadata
CHANGED
|
@@ -1,56 +1,55 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_machine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Grzegorz Łuszczek
|
|
8
8
|
- Adrian Słapa
|
|
9
|
-
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
|
-
name:
|
|
14
|
+
name: activerecord
|
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
|
17
16
|
requirements:
|
|
18
|
-
- -
|
|
17
|
+
- - ">="
|
|
19
18
|
- !ruby/object:Gem::Version
|
|
20
|
-
version: '
|
|
19
|
+
version: '7.0'
|
|
21
20
|
type: :runtime
|
|
22
21
|
prerelease: false
|
|
23
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
24
23
|
requirements:
|
|
25
|
-
- -
|
|
24
|
+
- - ">="
|
|
26
25
|
- !ruby/object:Gem::Version
|
|
27
|
-
version: '
|
|
26
|
+
version: '7.0'
|
|
28
27
|
- !ruby/object:Gem::Dependency
|
|
29
28
|
name: sqlite3
|
|
30
29
|
requirement: !ruby/object:Gem::Requirement
|
|
31
30
|
requirements:
|
|
32
|
-
- -
|
|
31
|
+
- - ">="
|
|
33
32
|
- !ruby/object:Gem::Version
|
|
34
|
-
version: '1.
|
|
33
|
+
version: '1.6'
|
|
35
34
|
type: :development
|
|
36
35
|
prerelease: false
|
|
37
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
38
37
|
requirements:
|
|
39
|
-
- -
|
|
38
|
+
- - ">="
|
|
40
39
|
- !ruby/object:Gem::Version
|
|
41
|
-
version: '1.
|
|
40
|
+
version: '1.6'
|
|
42
41
|
- !ruby/object:Gem::Dependency
|
|
43
|
-
name: rspec
|
|
42
|
+
name: rspec
|
|
44
43
|
requirement: !ruby/object:Gem::Requirement
|
|
45
44
|
requirements:
|
|
46
|
-
- -
|
|
45
|
+
- - ">="
|
|
47
46
|
- !ruby/object:Gem::Version
|
|
48
47
|
version: '3.0'
|
|
49
48
|
type: :development
|
|
50
49
|
prerelease: false
|
|
51
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
52
51
|
requirements:
|
|
53
|
-
- -
|
|
52
|
+
- - ">="
|
|
54
53
|
- !ruby/object:Gem::Version
|
|
55
54
|
version: '3.0'
|
|
56
55
|
description: Simple implementation of state machine for Rails using enums.
|
|
@@ -64,6 +63,7 @@ files:
|
|
|
64
63
|
- MIT-LICENSE
|
|
65
64
|
- README.rdoc
|
|
66
65
|
- Rakefile
|
|
66
|
+
- config/locales/rails_machine.en.yml
|
|
67
67
|
- lib/rails_machine.rb
|
|
68
68
|
- lib/rails_machine/configuration.rb
|
|
69
69
|
- lib/rails_machine/version.rb
|
|
@@ -72,25 +72,21 @@ homepage: https://github.com/grzlus/rails_machine
|
|
|
72
72
|
licenses:
|
|
73
73
|
- MIT
|
|
74
74
|
metadata: {}
|
|
75
|
-
post_install_message:
|
|
76
75
|
rdoc_options: []
|
|
77
76
|
require_paths:
|
|
78
77
|
- lib
|
|
79
78
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
80
79
|
requirements:
|
|
81
|
-
- -
|
|
80
|
+
- - ">="
|
|
82
81
|
- !ruby/object:Gem::Version
|
|
83
|
-
version: '
|
|
82
|
+
version: '3.0'
|
|
84
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
85
84
|
requirements:
|
|
86
|
-
- -
|
|
85
|
+
- - ">="
|
|
87
86
|
- !ruby/object:Gem::Version
|
|
88
87
|
version: '0'
|
|
89
88
|
requirements: []
|
|
90
|
-
|
|
91
|
-
rubygems_version: 2.2.2
|
|
92
|
-
signing_key:
|
|
89
|
+
rubygems_version: 3.6.9
|
|
93
90
|
specification_version: 4
|
|
94
91
|
summary: State machine for Rails.
|
|
95
92
|
test_files: []
|
|
96
|
-
has_rdoc:
|