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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4af0035757028b9a274654be5553733c35b3b86c
4
- data.tar.gz: 902c57516ac9603ead3293150fe5bdbf6439b3a6
2
+ SHA256:
3
+ metadata.gz: d7039049332c0e432df8f5b0607349b0bdc798201fb87c97dfa2c5e2bf95e0c0
4
+ data.tar.gz: c76c94732fc8d6ebdbe440a9cfd683d2da66fef62a096f94c3c58947d9628fd5
5
5
  SHA512:
6
- metadata.gz: 15e6ab9e4f8f566e3f22a6f0ca6dc408084a6d2853446d59a5bdc7f9290569c04fba8e8e328690a5eca22c67de107a0a6600295ae417807c5531930684933f67
7
- data.tar.gz: 689a28349e74c30d1d4130e100a7e8485eee6c8f3af8558d8adf14b67910e79fab3698b3cf692ad9fbf768a51b35615662c28ed2b8e3c2477333fa9bc1937689
6
+ metadata.gz: 52e2406afb168f5d94a5814958a6ca16923986143319e313c7424f7d1126aa791662faf409e612f0be2a3184f7c2f65201e2a5913af97ea832c5086ff744e7c5
7
+ data.tar.gz: a6566d45aee104ec86c0d57390e6b4d6918884647cf2c26685e7845225bb1654d84acf62219c664cc306a4709dd2b2c5b0af874d0d936096c31ee1dd3571c0e7
data/README.rdoc CHANGED
@@ -1,3 +1,113 @@
1
1
  = RailsMachine
2
2
 
3
- This project rocks and uses MIT-LICENSE.
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.
@@ -0,0 +1,6 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ invalid_init_state: "is not a valid initial state"
5
+ transition_not_found: "transition is not allowed"
6
+ guard_failed: "transition was blocked by a guard"
@@ -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
- @states << [name, id]
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.map(&:second).max.next
34
+ @states.values.max.next
34
35
  end
35
36
  end
36
37
  end
@@ -1,3 +1,3 @@
1
1
  module RailsMachine
2
- VERSION = "0.0.6"
2
+ VERSION = "1.0.0"
3
3
  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
- def allowed_state
15
- if self.new_record?
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 => Hash[configuration.states]
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.6
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: 2014-07-24 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
- name: rails
14
+ name: activerecord
16
15
  requirement: !ruby/object:Gem::Requirement
17
16
  requirements:
18
- - - ~>
17
+ - - ">="
19
18
  - !ruby/object:Gem::Version
20
- version: '4.1'
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: '4.1'
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.3'
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.3'
40
+ version: '1.6'
42
41
  - !ruby/object:Gem::Dependency
43
- name: rspec-rails
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: '2.1'
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
- rubyforge_project:
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: