rails_state_machine 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a43d6bb47ef3e0965611b3bbeff34ab781a7ca117009a94175635f4e0e4c3cb5
4
+ data.tar.gz: 4f4d100446619829d4dacad6318c97bd80a0ea3be6116036efa0d3afffd21cf2
5
+ SHA512:
6
+ metadata.gz: 510c5ac997b6481843a51f5f4a07433ac00c09ee5cd59c87e99793882fa6c3d9cc24bea5d70082336589c35fe39b6b468bc404dfae74dd5fa51c7ac85638d3d5
7
+ data.tar.gz: f8fa237d649a5ed98fc5e0a721493e4546f5d8b7f72e2d72ecb1bb291df23ff459ff398d9e6e13fdd340324a617f125d5c00c5d8ca67f1aad6b9cb9d36bce089
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ spec/support/database.yml
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.4.4
data/.travis.yml ADDED
@@ -0,0 +1,27 @@
1
+ sudo: false
2
+ language: ruby
3
+ cache: bundler
4
+
5
+ before_script:
6
+ - psql -c 'create database rails_state_machine_test;' -U postgres
7
+ - mysql -e 'create database IF NOT EXISTS rails_state_machine_test;'
8
+
9
+ notifications:
10
+ email:
11
+ - fail@makandra.de
12
+
13
+ install:
14
+ # Replace default Travis CI bundler script with a version that doesn't
15
+ # explode when lockfile doesn't match recently bumped version
16
+ - bundle install --no-deployment --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}
17
+
18
+ script: bundle exec rake current_rspec
19
+
20
+ rvm:
21
+ - 2.3.7
22
+ - 2.4.4
23
+ - 2.5.1
24
+
25
+ gemfile:
26
+ - gemfiles/Gemfile.5.1.pg
27
+ - gemfiles/Gemfile.5.2.pg
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
5
+
6
+ ## 1.0.0 2018-09-04
7
+
8
+ ### Compatible changes
9
+
10
+ - First version.
data/Gemfile ADDED
@@ -0,0 +1 @@
1
+ ./gemfiles/Gemfile.5.1.pg
data/Gemfile.lock ADDED
@@ -0,0 +1 @@
1
+ ./gemfiles/Gemfile.5.1.pg.lock
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 makandra
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 makandra
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # Rails State Machine
2
+ [![Build Status](https://travis-ci.org/makandra/rails_state_machine.svg?branch=master)](https://travis-ci.org/makandra/rails_state_machine)
3
+
4
+ Rails State Machine is a ActiveRecord-bound state machine.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'rails_state_machine'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install rails_state_machine
21
+
22
+ ## Usage
23
+
24
+ Your model needs a `state` attribute. You can then simply define your state machine as follows.
25
+
26
+ ```ruby
27
+ class YourModel < ApplicationRecord
28
+ include RailsStateMachine::Model
29
+
30
+ state_machine do
31
+ state :draft, initial: true
32
+ state :review_pending
33
+ state :approved
34
+ state :rejected
35
+
36
+ event :request_review do
37
+ transitions from: [:draft, :rejected], to: :review_pending
38
+ end
39
+
40
+ event :approve do
41
+ transitions from: :review_pending, to: :approved
42
+ end
43
+
44
+ event :reject do
45
+ transitions from: :review_pending, to: :rejected
46
+ end
47
+ end
48
+ end
49
+ ```
50
+
51
+ This will define instance methods with the names of those events, and constants like `STATE_DRAFT` on the model.
52
+ If a state is configured as `initial: true`, new instances will be assigned this state.
53
+
54
+ A model instance offers these state machine methods:
55
+
56
+ - `<state_name>?` to find out if this is the current state.
57
+ - `<event_name>` call an event and transition into a new state. The record will be `save`d, if valid.
58
+ - `<event_name>!` call an event and transition into a new state. Calls `save!` to save the record.
59
+ - `may_<event_name>?` to find out if an event transition could be taken. Note that this will not validate if the model is valid afterwards.
60
+ - `state_event=` to take a state event, but not save yet. Commonly used for forms where the controller takes a "state_event" param and saves.
61
+ - `state_event` to get the name of the event that will be called
62
+
63
+ Should you ever need to query the state machine for its states or events, it is accessible via `state_machine` class or instance methods on the model. This is mostly helpful in tests.
64
+
65
+ If you want an event to be available for a different edge in your graph, you may define multiple `transitions` per event:
66
+
67
+ ```ruby
68
+ event :request_feedback do
69
+ transitions from: :draft, to: :draft
70
+ transitions from: :review_pending, to: :review_pending
71
+ end
72
+ ```
73
+
74
+ As an alternative to using `RailsStateMachine::Model` and `state_machine do`, configure the state machine manually. This only adds the `state_machine` to your model, but no `states` or `state_events`.
75
+
76
+ ```ruby
77
+ class YourModel < ApplicationRecord
78
+ RailsStateMachine::StateMachine.new(self).configure do
79
+ state :draft, initial: true
80
+ state :review_pending
81
+ state :approved
82
+ state :rejected
83
+
84
+ event :request_review do
85
+ transitions from: [:draft, :rejected], to: :review_pending
86
+ end
87
+
88
+ event :approve do
89
+ transitions from: :review_pending, to: :approved
90
+ end
91
+
92
+ event :reject do
93
+ transitions from: :review_pending, to: :rejected
94
+ end
95
+ end
96
+
97
+ def self.states
98
+ state_machine.state_names
99
+ end
100
+
101
+ def self.state_events
102
+ state_machine.event_names
103
+ end
104
+ end
105
+ ```
106
+
107
+ ## Event callbacks
108
+
109
+ Here is a list with all the available callbacks, listed in the same order in which they will get called during the respective operations. The callbacks are chained with the existing active record callbacks on the model.
110
+
111
+ * `before_validation`
112
+ * `before_save`
113
+ * `after_save`
114
+
115
+ Example:
116
+
117
+ ```ruby
118
+ event :request_review do
119
+ transitions from: [:draft, :rejected], to: :review_pending
120
+
121
+ before_validation do
122
+ # this callback is chained with existing `before_validation` callbacks of the model
123
+ end
124
+
125
+ before_save do
126
+ # this callback is chained with existing `before_save` callbacks of the model
127
+ end
128
+
129
+ after_save do
130
+ # this callback is chained with existing `after_save` callbacks of the model
131
+ end
132
+ end
133
+ ```
134
+
135
+ ## Development
136
+
137
+ There are tests in `spec`. We only accept PRs with tests. To run tests:
138
+
139
+ - Install Ruby 2.4.4
140
+ - Copy the file `spec/support/database.sample.yml` to `spec/support/database.yml` and enter your PostgreSQL credentials. You can create the database afterwards with `createdb rails_state_machine_test`.
141
+ - Run `bin/setup` to install development dependencies.
142
+ - Run tests using `bundle exec rspec`
143
+
144
+ We recommend to test large changes against multiple versions of Ruby and multiple dependency sets. Supported combinations are configured in `.travis.yml`. We provide some rake tasks to help with this:
145
+
146
+ - Install development dependencies using `bundle matrix:install`
147
+ - Run tests using `bundle matrix:spec`
148
+
149
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
150
+
151
+ To install this gem onto your local machine, run `bundle exec rake install`.
152
+
153
+ 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
154
+
155
+ ## Contributing
156
+
157
+ If you would like to contribute:
158
+
159
+ - Fork the repository.
160
+ - Push your changes **with passing specs**.
161
+ - Send us a pull request.
162
+
163
+ We want to keep this gem leightweight and on topic. If you are unsure whether a change would make it into the gem, open an issue and discuss.
164
+
165
+ Note that we have configured Travis CI to automatically run tests in all supported Ruby versions and dependency sets after each push. We will only merge pull requests after a green Travis build.
166
+
167
+ ## License
168
+
169
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
170
+
171
+ ## Credits
172
+
173
+ Arne Hartherz and Emanuel Denzel from [makandra](https://makandra.de/).
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ begin
4
+ require 'gemika/tasks'
5
+ rescue LoadError
6
+ puts 'Run `gem install gemika` for additional tasks'
7
+ end
8
+
9
+ task :default => 'matrix:spec'
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'rails_state_machine'
5
+ require 'pry-byebug'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ gem install bundler
7
+ bundle install
8
+ bundle exec rake matrix:install
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Runtime dependencies
4
+ gem 'rails', '~>5.1.0'
5
+ gem 'pg'
6
+
7
+ # Development dependencies
8
+ gem 'rspec', '~>3.5'
9
+ gem 'rake'
10
+ gem 'pry-byebug'
11
+ gem 'gemika'
12
+ gem 'database_cleaner'
13
+
14
+ # Gem under test
15
+ gem 'rails_state_machine', :path => '..'
@@ -0,0 +1,149 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ rails_state_machine (1.0.0)
5
+ activerecord
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ actioncable (5.1.4)
11
+ actionpack (= 5.1.4)
12
+ nio4r (~> 2.0)
13
+ websocket-driver (~> 0.6.1)
14
+ actionmailer (5.1.4)
15
+ actionpack (= 5.1.4)
16
+ actionview (= 5.1.4)
17
+ activejob (= 5.1.4)
18
+ mail (~> 2.5, >= 2.5.4)
19
+ rails-dom-testing (~> 2.0)
20
+ actionpack (5.1.4)
21
+ actionview (= 5.1.4)
22
+ activesupport (= 5.1.4)
23
+ rack (~> 2.0)
24
+ rack-test (>= 0.6.3)
25
+ rails-dom-testing (~> 2.0)
26
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
27
+ actionview (5.1.4)
28
+ activesupport (= 5.1.4)
29
+ builder (~> 3.1)
30
+ erubi (~> 1.4)
31
+ rails-dom-testing (~> 2.0)
32
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
33
+ activejob (5.1.4)
34
+ activesupport (= 5.1.4)
35
+ globalid (>= 0.3.6)
36
+ activemodel (5.1.4)
37
+ activesupport (= 5.1.4)
38
+ activerecord (5.1.4)
39
+ activemodel (= 5.1.4)
40
+ activesupport (= 5.1.4)
41
+ arel (~> 8.0)
42
+ activesupport (5.1.4)
43
+ concurrent-ruby (~> 1.0, >= 1.0.2)
44
+ i18n (~> 0.7)
45
+ minitest (~> 5.1)
46
+ tzinfo (~> 1.1)
47
+ arel (8.0.0)
48
+ builder (3.2.3)
49
+ byebug (9.1.0)
50
+ coderay (1.1.2)
51
+ concurrent-ruby (1.0.5)
52
+ crass (1.0.3)
53
+ database_cleaner (1.6.2)
54
+ diff-lcs (1.3)
55
+ erubi (1.7.0)
56
+ gemika (0.3.2)
57
+ globalid (0.4.1)
58
+ activesupport (>= 4.2.0)
59
+ i18n (0.9.1)
60
+ concurrent-ruby (~> 1.0)
61
+ loofah (2.1.1)
62
+ crass (~> 1.0.2)
63
+ nokogiri (>= 1.5.9)
64
+ mail (2.7.0)
65
+ mini_mime (>= 0.1.1)
66
+ method_source (0.9.0)
67
+ mini_mime (1.0.0)
68
+ mini_portile2 (2.3.0)
69
+ minitest (5.10.3)
70
+ nio4r (2.1.0)
71
+ nokogiri (1.8.1)
72
+ mini_portile2 (~> 2.3.0)
73
+ pg (0.21.0)
74
+ pry (0.11.3)
75
+ coderay (~> 1.1.0)
76
+ method_source (~> 0.9.0)
77
+ pry-byebug (3.5.0)
78
+ byebug (~> 9.1)
79
+ pry (~> 0.10)
80
+ rack (2.0.3)
81
+ rack-test (0.7.0)
82
+ rack (>= 1.0, < 3)
83
+ rails (5.1.4)
84
+ actioncable (= 5.1.4)
85
+ actionmailer (= 5.1.4)
86
+ actionpack (= 5.1.4)
87
+ actionview (= 5.1.4)
88
+ activejob (= 5.1.4)
89
+ activemodel (= 5.1.4)
90
+ activerecord (= 5.1.4)
91
+ activesupport (= 5.1.4)
92
+ bundler (>= 1.3.0)
93
+ railties (= 5.1.4)
94
+ sprockets-rails (>= 2.0.0)
95
+ rails-dom-testing (2.0.3)
96
+ activesupport (>= 4.2.0)
97
+ nokogiri (>= 1.6)
98
+ rails-html-sanitizer (1.0.3)
99
+ loofah (~> 2.0)
100
+ railties (5.1.4)
101
+ actionpack (= 5.1.4)
102
+ activesupport (= 5.1.4)
103
+ method_source
104
+ rake (>= 0.8.7)
105
+ thor (>= 0.18.1, < 2.0)
106
+ rake (12.2.1)
107
+ rspec (3.7.0)
108
+ rspec-core (~> 3.7.0)
109
+ rspec-expectations (~> 3.7.0)
110
+ rspec-mocks (~> 3.7.0)
111
+ rspec-core (3.7.0)
112
+ rspec-support (~> 3.7.0)
113
+ rspec-expectations (3.7.0)
114
+ diff-lcs (>= 1.2.0, < 2.0)
115
+ rspec-support (~> 3.7.0)
116
+ rspec-mocks (3.7.0)
117
+ diff-lcs (>= 1.2.0, < 2.0)
118
+ rspec-support (~> 3.7.0)
119
+ rspec-support (3.7.0)
120
+ sprockets (3.7.1)
121
+ concurrent-ruby (~> 1.0)
122
+ rack (> 1, < 3)
123
+ sprockets-rails (3.2.1)
124
+ actionpack (>= 4.0)
125
+ activesupport (>= 4.0)
126
+ sprockets (>= 3.0.0)
127
+ thor (0.20.0)
128
+ thread_safe (0.3.6)
129
+ tzinfo (1.2.4)
130
+ thread_safe (~> 0.1)
131
+ websocket-driver (0.6.5)
132
+ websocket-extensions (>= 0.1.0)
133
+ websocket-extensions (0.1.3)
134
+
135
+ PLATFORMS
136
+ ruby
137
+
138
+ DEPENDENCIES
139
+ database_cleaner
140
+ gemika
141
+ pg
142
+ pry-byebug
143
+ rails (~> 5.1.0)
144
+ rails_state_machine!
145
+ rake
146
+ rspec (~> 3.5)
147
+
148
+ BUNDLED WITH
149
+ 1.16.4
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Runtime dependencies
4
+ gem 'rails', '~>5.2.0'
5
+ gem 'pg'
6
+
7
+ # Development dependencies
8
+ gem 'rspec', '~>3.5'
9
+ gem 'rake'
10
+ gem 'pry-byebug'
11
+ gem 'gemika'
12
+ gem 'database_cleaner'
13
+
14
+ # Gem under test
15
+ gem 'rails_state_machine', :path => '..'
@@ -0,0 +1,157 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ rails_state_machine (0.1.0)
5
+ activerecord
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ actioncable (5.2.1)
11
+ actionpack (= 5.2.1)
12
+ nio4r (~> 2.0)
13
+ websocket-driver (>= 0.6.1)
14
+ actionmailer (5.2.1)
15
+ actionpack (= 5.2.1)
16
+ actionview (= 5.2.1)
17
+ activejob (= 5.2.1)
18
+ mail (~> 2.5, >= 2.5.4)
19
+ rails-dom-testing (~> 2.0)
20
+ actionpack (5.2.1)
21
+ actionview (= 5.2.1)
22
+ activesupport (= 5.2.1)
23
+ rack (~> 2.0)
24
+ rack-test (>= 0.6.3)
25
+ rails-dom-testing (~> 2.0)
26
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
27
+ actionview (5.2.1)
28
+ activesupport (= 5.2.1)
29
+ builder (~> 3.1)
30
+ erubi (~> 1.4)
31
+ rails-dom-testing (~> 2.0)
32
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
33
+ activejob (5.2.1)
34
+ activesupport (= 5.2.1)
35
+ globalid (>= 0.3.6)
36
+ activemodel (5.2.1)
37
+ activesupport (= 5.2.1)
38
+ activerecord (5.2.1)
39
+ activemodel (= 5.2.1)
40
+ activesupport (= 5.2.1)
41
+ arel (>= 9.0)
42
+ activestorage (5.2.1)
43
+ actionpack (= 5.2.1)
44
+ activerecord (= 5.2.1)
45
+ marcel (~> 0.3.1)
46
+ activesupport (5.2.1)
47
+ concurrent-ruby (~> 1.0, >= 1.0.2)
48
+ i18n (>= 0.7, < 2)
49
+ minitest (~> 5.1)
50
+ tzinfo (~> 1.1)
51
+ arel (9.0.0)
52
+ builder (3.2.3)
53
+ byebug (10.0.2)
54
+ coderay (1.1.2)
55
+ concurrent-ruby (1.0.5)
56
+ crass (1.0.4)
57
+ database_cleaner (1.7.0)
58
+ diff-lcs (1.3)
59
+ erubi (1.7.1)
60
+ gemika (0.3.4)
61
+ globalid (0.4.1)
62
+ activesupport (>= 4.2.0)
63
+ i18n (1.1.0)
64
+ concurrent-ruby (~> 1.0)
65
+ loofah (2.2.2)
66
+ crass (~> 1.0.2)
67
+ nokogiri (>= 1.5.9)
68
+ mail (2.7.0)
69
+ mini_mime (>= 0.1.1)
70
+ marcel (0.3.2)
71
+ mimemagic (~> 0.3.2)
72
+ method_source (0.9.0)
73
+ mimemagic (0.3.2)
74
+ mini_mime (1.0.1)
75
+ mini_portile2 (2.3.0)
76
+ minitest (5.11.3)
77
+ nio4r (2.3.1)
78
+ nokogiri (1.8.4)
79
+ mini_portile2 (~> 2.3.0)
80
+ pg (1.1.2)
81
+ pry (0.11.3)
82
+ coderay (~> 1.1.0)
83
+ method_source (~> 0.9.0)
84
+ pry-byebug (3.6.0)
85
+ byebug (~> 10.0)
86
+ pry (~> 0.10)
87
+ rack (2.0.5)
88
+ rack-test (1.1.0)
89
+ rack (>= 1.0, < 3)
90
+ rails (5.2.1)
91
+ actioncable (= 5.2.1)
92
+ actionmailer (= 5.2.1)
93
+ actionpack (= 5.2.1)
94
+ actionview (= 5.2.1)
95
+ activejob (= 5.2.1)
96
+ activemodel (= 5.2.1)
97
+ activerecord (= 5.2.1)
98
+ activestorage (= 5.2.1)
99
+ activesupport (= 5.2.1)
100
+ bundler (>= 1.3.0)
101
+ railties (= 5.2.1)
102
+ sprockets-rails (>= 2.0.0)
103
+ rails-dom-testing (2.0.3)
104
+ activesupport (>= 4.2.0)
105
+ nokogiri (>= 1.6)
106
+ rails-html-sanitizer (1.0.4)
107
+ loofah (~> 2.2, >= 2.2.2)
108
+ railties (5.2.1)
109
+ actionpack (= 5.2.1)
110
+ activesupport (= 5.2.1)
111
+ method_source
112
+ rake (>= 0.8.7)
113
+ thor (>= 0.19.0, < 2.0)
114
+ rake (12.3.1)
115
+ rspec (3.8.0)
116
+ rspec-core (~> 3.8.0)
117
+ rspec-expectations (~> 3.8.0)
118
+ rspec-mocks (~> 3.8.0)
119
+ rspec-core (3.8.0)
120
+ rspec-support (~> 3.8.0)
121
+ rspec-expectations (3.8.1)
122
+ diff-lcs (>= 1.2.0, < 2.0)
123
+ rspec-support (~> 3.8.0)
124
+ rspec-mocks (3.8.0)
125
+ diff-lcs (>= 1.2.0, < 2.0)
126
+ rspec-support (~> 3.8.0)
127
+ rspec-support (3.8.0)
128
+ sprockets (3.7.2)
129
+ concurrent-ruby (~> 1.0)
130
+ rack (> 1, < 3)
131
+ sprockets-rails (3.2.1)
132
+ actionpack (>= 4.0)
133
+ activesupport (>= 4.0)
134
+ sprockets (>= 3.0.0)
135
+ thor (0.20.0)
136
+ thread_safe (0.3.6)
137
+ tzinfo (1.2.5)
138
+ thread_safe (~> 0.1)
139
+ websocket-driver (0.7.0)
140
+ websocket-extensions (>= 0.1.0)
141
+ websocket-extensions (0.1.3)
142
+
143
+ PLATFORMS
144
+ ruby
145
+
146
+ DEPENDENCIES
147
+ database_cleaner
148
+ gemika
149
+ pg
150
+ pry-byebug
151
+ rails (~> 5.2.0)
152
+ rails_state_machine!
153
+ rake
154
+ rspec (~> 3.5)
155
+
156
+ BUNDLED WITH
157
+ 1.16.4
@@ -0,0 +1,104 @@
1
+ module RailsStateMachine
2
+ class Event
3
+ Transition = Struct.new(:from, :to)
4
+
5
+ UndefinedStateError = Class.new(StandardError)
6
+ TransitionNotFoundError = Class.new(StandardError)
7
+ ExistingTransitionError = Class.new(StandardError)
8
+
9
+ attr_reader :name
10
+
11
+ def initialize(name, state_machine)
12
+ @name = name
13
+ @state_machine = state_machine
14
+
15
+ @before_validation = []
16
+ @before_save = []
17
+ @after_save = []
18
+ @after_commit = []
19
+
20
+ @transitions_by_state_name = {}
21
+ end
22
+
23
+ def configure(&block)
24
+ instance_eval(&block)
25
+ end
26
+
27
+ def transitions(**options)
28
+ if options.present?
29
+ add_transitions(options)
30
+ else
31
+ @transitions_by_state_name.values
32
+ end
33
+ end
34
+
35
+ def run_before_validation(record)
36
+ @before_validation.each do |block|
37
+ record.instance_eval(&block)
38
+ end
39
+ end
40
+
41
+ def run_before_save(record)
42
+ @before_save.each do |block|
43
+ record.instance_eval(&block)
44
+ end
45
+ end
46
+
47
+ def run_after_save(record)
48
+ @after_save.each do |block|
49
+ record.instance_eval(&block)
50
+ end
51
+ end
52
+
53
+ def run_after_commit(record)
54
+ @after_commit.each do |block|
55
+ record.instance_eval(&block)
56
+ end
57
+ end
58
+
59
+ def find_transition_from(state_name)
60
+ @transitions_by_state_name[state_name&.to_sym] || raise(TransitionNotFoundError, "#{name} does not transition from #{state_name}; defined are #{transitions}")
61
+ end
62
+
63
+ def allowed_from?(state_name)
64
+ @transitions_by_state_name.key?(state_name&.to_sym)
65
+ end
66
+
67
+ def future_state_name(state_name)
68
+ find_transition_from(state_name).to
69
+ end
70
+
71
+ private
72
+
73
+ def add_transitions(from:, to:)
74
+ froms = Array(from)
75
+ froms.each { |from| add_transition(from, to) }
76
+ end
77
+
78
+ def add_transition(from, to)
79
+ if !@state_machine.has_state?(from)
80
+ raise UndefinedStateError, "#{from} is not a valid state in the state machine of #{@state_machine.model}"
81
+ elsif allowed_from?(from)
82
+ raise ExistingTransitionError, "#{name} already defines a transition from #{from} (to #{future_state_name(from)})"
83
+ else
84
+ @transitions_by_state_name[from] = Transition.new(from, to)
85
+ end
86
+ end
87
+
88
+ def before_validation(&block)
89
+ @before_validation << block
90
+ end
91
+
92
+ def before_save(&block)
93
+ @before_save << block
94
+ end
95
+
96
+ def after_save(&block)
97
+ @after_save << block
98
+ end
99
+
100
+ def after_commit(&block)
101
+ @after_commit << block
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,21 @@
1
+ module RailsStateMachine
2
+ module Model
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def state_machine(&block)
9
+ StateMachine.new(self).configure(&block)
10
+ end
11
+
12
+ def states
13
+ state_machine.state_names
14
+ end
15
+
16
+ def state_events
17
+ state_machine.event_names
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module RailsStateMachine
2
+ class State
3
+ attr_reader :name, :options
4
+
5
+ def initialize(name, **options)
6
+ @name = name
7
+ @options = options
8
+ end
9
+
10
+ def initial?
11
+ !!options[:initial]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,229 @@
1
+ module RailsStateMachine
2
+ class StateMachine
3
+ def initialize(model)
4
+ @model = model
5
+
6
+ model_constant('StateMachineMethods', Module.new)
7
+ @model.include(@model::StateMachineMethods)
8
+
9
+ @states_by_name = {}
10
+ @events_by_name = {}
11
+ end
12
+
13
+ def configure(&block)
14
+ instance_eval(&block)
15
+
16
+ define_state_methods
17
+ define_state_constants
18
+ register_initial_state
19
+
20
+ define_event_methods
21
+
22
+ register_callbacks
23
+ register_validations
24
+ register_state_machine
25
+
26
+ define_model_methods
27
+ end
28
+
29
+ def states
30
+ @states_by_name.values
31
+ end
32
+
33
+ def state_names
34
+ @states_by_name.keys
35
+ end
36
+
37
+ def events
38
+ @events_by_name.values
39
+ end
40
+
41
+ def event_names
42
+ @events_by_name.keys
43
+ end
44
+
45
+ def find_event(name)
46
+ @events_by_name.fetch(name.to_sym)
47
+ end
48
+
49
+ def has_state?(name)
50
+ @states_by_name.key?(name.to_sym)
51
+ end
52
+
53
+ private
54
+
55
+ def state(name, **options)
56
+ @states_by_name[name] = State.new(name, options)
57
+ end
58
+
59
+ def event(name, &block)
60
+ event = Event.new(name, self)
61
+ event.configure(&block)
62
+
63
+ model_methods do
64
+ define_method "#{event.name}" do |**attributes|
65
+ prepare_state_event_change(attributes.merge(state_event: event.name))
66
+ save
67
+ end
68
+
69
+ define_method "#{event.name}!" do |**attributes|
70
+ prepare_state_event_change(attributes.merge(state_event: event.name))
71
+ save!
72
+ end
73
+ end
74
+
75
+ @events_by_name[name] = event
76
+ end
77
+
78
+ def model_constant(name, value)
79
+ @model.const_set(name, value)
80
+ end
81
+
82
+ def model_methods(&block)
83
+ # Using a state machine defines several methods on the model.
84
+ # The model should be able to re-define them and `super` into the original method, if necessary.
85
+ # For that, we use a module to store all methods. The module is loaded into the model class.
86
+ @model::StateMachineMethods.module_eval(&block)
87
+ end
88
+
89
+ def define_state_methods
90
+ state_names.each do |state_name|
91
+ model_methods do
92
+ define_method "#{state_name}?" do
93
+ self.state.to_s == state_name.to_s
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ def define_state_constants
100
+ state_names.each do |state_name|
101
+ model_constant("STATE_#{state_name.upcase}", state_name)
102
+ end
103
+ end
104
+
105
+ def register_initial_state
106
+ initial_state = states.detect(&:initial?)
107
+ return unless initial_state
108
+
109
+ @model.after_initialize do
110
+ self.state ||= initial_state.name if new_record?
111
+ end
112
+ end
113
+
114
+ def define_event_methods
115
+ event_names.each do |event_name, event|
116
+ model_methods do
117
+ define_method "may_#{event_name}?" do
118
+ state_machine.find_event(event_name).allowed_from?(source_state)
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ def register_callbacks
125
+ @model.class_eval do
126
+ before_validation :run_state_event_before_validation
127
+ before_save :register_state_events_for_callbacks
128
+ before_save { flush_state_event_callbacks(:before_save) }
129
+ after_save { flush_state_event_callbacks(:after_save) }
130
+ after_commit { flush_state_event_callbacks(:after_commit) }
131
+ end
132
+ end
133
+
134
+ def register_validations
135
+ @model.class_eval do
136
+ after_validation :revert_state, if: -> { errors.any? }
137
+ end
138
+ end
139
+
140
+ def register_state_machine
141
+ @model.class_eval do
142
+ cattr_accessor :state_machine
143
+ delegate :state_machine, to: :class
144
+ end
145
+
146
+ @model.state_machine = self
147
+ end
148
+
149
+ def define_model_methods
150
+ model_methods do
151
+ def state_event=(event_name)
152
+ @next_state_machine_event = state_machine.find_event(event_name)
153
+ @state_before_state_event = source_state
154
+
155
+ # If the event can not transition from source_state, a TransitionNotFoundError will be raised
156
+ self.state = @next_state_machine_event.future_state_name(source_state).to_s
157
+ end
158
+
159
+ def state_event
160
+ @next_state_machine_event&.name
161
+ end
162
+
163
+ def source_state
164
+ if new_record?
165
+ state
166
+ else
167
+ state_in_database
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ def run_state_event_before_validation
174
+ # Since validations may be skipped, we will not register validation callbacks in @state_event_callbacks,
175
+ # but call them explicitly when before_validation callbacks are triggered.
176
+ @next_state_machine_event&.run_before_validation(self)
177
+ end
178
+
179
+ def register_state_events_for_callbacks
180
+ @state_event_callbacks ||= {
181
+ before_save: [],
182
+ after_save: [],
183
+ after_commit: []
184
+ }
185
+ @state_event_callbacks[:before_save] << @next_state_machine_event
186
+ @state_event_callbacks[:after_save] << @next_state_machine_event
187
+ @state_event_callbacks[:after_commit] << @next_state_machine_event
188
+ true
189
+ end
190
+
191
+ def flush_state_event_callbacks(name)
192
+ while (event = @state_event_callbacks[name].shift)
193
+ event.public_send("run_#{name}", self)
194
+ end
195
+ end
196
+
197
+ def unset_next_state_machine_event
198
+ @next_state_machine_event = nil
199
+ end
200
+
201
+ def revert_state
202
+ self.state = @state_before_state_event
203
+ end
204
+
205
+ def prepare_state_event_change(attributes)
206
+ if saved_changes?
207
+ # After calling `save`, ActiveRecord will flag the changes that it just stored as saved.
208
+ # https://github.com/rails/rails/blob/v5.1.4/activerecord/lib/active_record/attribute_methods/dirty.rb#L33-L46
209
+ #
210
+ # When taking multiple state events (e.g. a second event called inside an `after_save` callback) and thus
211
+ # saving after other changes were just saved, we need to mimic that behavior. Otherwise, ActiveRecord will
212
+ # print deprecation warnings like these:
213
+ #
214
+ # DEPRECATION WARNING: The behavior of `attribute_was` inside of after callbacks will be changing in the
215
+ # next version of Rails. The new return value will reflect the behavior of calling the method after
216
+ # `save` returned (e.g. the opposite of what it returns now). To maintain the current behavior, use
217
+ # `attribute_before_last_save` instead.
218
+ #
219
+ # These actually originate from ActiveRecord internals which try to determine the changes that should be
220
+ # stored for the second save. It is probably a shortcoming of ActiveRecord 5.1.x that will be fixed, but
221
+ # since the current/previous save was already successful, the right action is to just call `changes_applied`.
222
+ changes_applied
223
+ end
224
+ self.attributes = attributes
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,3 @@
1
+ module RailsStateMachine
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,5 @@
1
+ require 'rails_state_machine/version'
2
+ require 'rails_state_machine/state'
3
+ require 'rails_state_machine/event'
4
+ require 'rails_state_machine/state_machine'
5
+ require 'rails_state_machine/model'
@@ -0,0 +1,29 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'rails_state_machine/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'rails_state_machine'
7
+ spec.version = RailsStateMachine::VERSION
8
+ spec.authors = ['Arne Hartherz', 'Emanuel Denzel']
9
+ spec.email = ['arne.hartherz@makandra.de']
10
+
11
+ spec.summary = %q{ActiveRecord-bound state machine}
12
+ spec.description = spec.summary
13
+ spec.homepage = 'https://github.com/makandra/rails_state_machine'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_dependency 'activerecord'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.16'
27
+ spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'pry-byebug', '~> 3.5'
29
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_state_machine
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Arne Hartherz
8
+ - Emanuel Denzel
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2018-09-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: bundler
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.16'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.16'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '10.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '10.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: pry-byebug
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.5'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.5'
70
+ description: ActiveRecord-bound state machine
71
+ email:
72
+ - arne.hartherz@makandra.de
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".ruby-version"
80
+ - ".travis.yml"
81
+ - CHANGELOG.md
82
+ - Gemfile
83
+ - Gemfile.lock
84
+ - LICENSE
85
+ - LICENSE.txt
86
+ - README.md
87
+ - Rakefile
88
+ - bin/console
89
+ - bin/setup
90
+ - gemfiles/Gemfile.5.1.pg
91
+ - gemfiles/Gemfile.5.1.pg.lock
92
+ - gemfiles/Gemfile.5.2.pg
93
+ - gemfiles/Gemfile.5.2.pg.lock
94
+ - lib/rails_state_machine.rb
95
+ - lib/rails_state_machine/event.rb
96
+ - lib/rails_state_machine/model.rb
97
+ - lib/rails_state_machine/state.rb
98
+ - lib/rails_state_machine/state_machine.rb
99
+ - lib/rails_state_machine/version.rb
100
+ - rails_state_machine.gemspec
101
+ homepage: https://github.com/makandra/rails_state_machine
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.7.6
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: ActiveRecord-bound state machine
125
+ test_files: []