transitions 1.0.0 → 1.0.1
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 +4 -4
- data/.rubocop.yml +18 -1
- data/.travis.yml +5 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +7 -6
- data/LICENSE.txt +21 -0
- data/README.md +9 -4
- data/Rakefile +9 -4
- data/bin/console +7 -0
- data/lib/active_model/transitions.rb +19 -12
- data/lib/transitions.rb +15 -15
- data/lib/transitions/event.rb +19 -15
- data/lib/transitions/machine.rb +23 -22
- data/lib/transitions/presenter.rb +2 -6
- data/lib/transitions/state.rb +10 -7
- data/lib/transitions/state_transition.rb +37 -6
- data/lib/transitions/version.rb +1 -1
- data/transitions.gemspec +23 -24
- metadata +39 -45
- data/.ruby-gemset +0 -1
- data/MIT-LICENSE.txt +0 -21
- data/test/active_record/test_active_record.rb +0 -326
- data/test/active_record/test_active_record_scopes.rb +0 -64
- data/test/active_record/test_active_record_timestamps.rb +0 -132
- data/test/active_record/test_custom_select.rb +0 -33
- data/test/event/test_event.rb +0 -72
- data/test/event/test_event_arguments.rb +0 -29
- data/test/event/test_event_being_fired.rb +0 -26
- data/test/event/test_event_checks.rb +0 -33
- data/test/helper.rb +0 -18
- data/test/machine/machine_template.rb +0 -27
- data/test/machine/test_available_states_listing.rb +0 -24
- data/test/machine/test_fire_event_machine.rb +0 -29
- data/test/machine/test_machine.rb +0 -66
- data/test/state/test_state.rb +0 -71
- data/test/state/test_state_predicate_method.rb +0 -32
- data/test/state_transition/test_state_transition.rb +0 -45
- data/test/state_transition/test_state_transition_event_failed_callback.rb +0 -36
- data/test/state_transition/test_state_transition_event_fired_callback.rb +0 -44
- data/test/state_transition/test_state_transition_guard_check.rb +0 -66
- data/test/state_transition/test_state_transition_on_transition_callback.rb +0 -48
- data/test/state_transition/test_state_transition_success_callback.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e0eecbbaa7a8c858df9799e370497da5c55691a
|
4
|
+
data.tar.gz: 2aeaec6cb95f64080a9a2a03a19ac1d9885db568
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14bdc0e63e4cad7f5cf626022738509b61878964b1fd5c86eb8c87b6550ad45e8381af5d15333ca021a5c043edf521e8b4a2360d4d3f82d6587bc2537e756818
|
7
|
+
data.tar.gz: 8e5142a6a0448e8cb29b2b4bdd08cb8ac5bd8776ff9c2f16ae7bf900775cb4768fe70e7bbdf5d82f72ed0a006a95288350c3ba9910c4436d5702b585dbfe4c0e
|
data/.rubocop.yml
CHANGED
@@ -1,4 +1,21 @@
|
|
1
1
|
Metrics/LineLength:
|
2
2
|
Max: 120
|
3
|
-
|
3
|
+
Documentation:
|
4
4
|
Enabled: false
|
5
|
+
Lint/AssignmentInCondition:
|
6
|
+
Enabled: false
|
7
|
+
# For whatever reasons disabling the cops below via source comment does **not** work.
|
8
|
+
# This happens only for event.rb, disabling those cops in other files via comment works well.
|
9
|
+
Metrics/MethodLength:
|
10
|
+
Exclude:
|
11
|
+
- 'lib/transitions/event.rb'
|
12
|
+
Metrics/AbcSize:
|
13
|
+
Exclude:
|
14
|
+
- 'lib/transitions/event.rb'
|
15
|
+
Metrics/CyclomaticComplexity:
|
16
|
+
Exclude:
|
17
|
+
- 'lib/transitions/event.rb'
|
18
|
+
Style/ConditionalAssignment:
|
19
|
+
Exclude:
|
20
|
+
- 'lib/transitions/event.rb'
|
21
|
+
- 'lib/transitions/state.rb'
|
data/.travis.yml
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
sudo: false
|
2
2
|
cache: bundler
|
3
3
|
language: ruby
|
4
|
+
bundler_args: --without local_development
|
5
|
+
script:
|
6
|
+
- bundle exec rake
|
4
7
|
rvm:
|
5
8
|
- 2.1
|
6
9
|
- 2.2
|
7
10
|
- rbx-2
|
8
11
|
- jruby
|
9
12
|
matrix:
|
13
|
+
allow_failures:
|
14
|
+
- rvm: rbx-2
|
10
15
|
fast_finish: true
|
11
16
|
notifications:
|
12
17
|
email:
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
@@ -2,11 +2,12 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
-
|
6
|
-
gem 'activerecord-jdbcsqlite3-adapter', platforms: :jruby
|
7
|
-
|
8
|
-
platforms :ruby do
|
9
|
-
gem 'sqlite3'
|
5
|
+
group :local_development do
|
10
6
|
gem 'pry'
|
11
|
-
gem '
|
7
|
+
gem 'sqlite3'
|
8
|
+
|
9
|
+
platforms :mri do
|
10
|
+
gem 'pry-byebug'
|
11
|
+
gem 'pry-stack_explorer'
|
12
|
+
end
|
12
13
|
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Timo Rößner
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
@@ -57,7 +57,7 @@ class Product
|
|
57
57
|
end
|
58
58
|
```
|
59
59
|
In this example we assume that you are in a rails project using Bundler, which
|
60
|
-
would automatically require `transitions`. If this is not the case
|
60
|
+
would automatically require `transitions`. If this is not the case then you
|
61
61
|
have to add
|
62
62
|
```ruby
|
63
63
|
require 'transitions'
|
@@ -69,7 +69,7 @@ wherever you load your dependencies in your application.
|
|
69
69
|
|
70
70
|
* You can only use one state machine per model. While in theory you can
|
71
71
|
define two or more, this won't work as you would expect. Not supporting
|
72
|
-
this was intentional, if you're interested in the
|
72
|
+
this was intentional, if you're interested in the rational look up version
|
73
73
|
0.1.0 in the CHANGELOG.
|
74
74
|
|
75
75
|
* Use symbols, not strings for declaring the state machine. Using strings is
|
@@ -106,13 +106,13 @@ the bang(!)-version will call `save!`. The `can_discontinue?` method will not
|
|
106
106
|
modify state but instead returns a boolean letting you know if a given
|
107
107
|
transition is possible.
|
108
108
|
|
109
|
-
In addition, a `can_transition?` method is added to the object that expects one or more event names as arguments. This semi-verbose method name is used to avoid
|
109
|
+
In addition, a `can_transition?` method is added to the object that expects one or more event names as arguments. This semi-verbose method name is used to avoid collisions with [https://github.com/ryanb/cancan](the authorization gem CanCan).
|
110
110
|
```ruby
|
111
111
|
>> Product.new.can_transition? :out_of_stock
|
112
112
|
=> true
|
113
113
|
```
|
114
114
|
|
115
|
-
If you need to get all available transitions for current state you can simply call:
|
115
|
+
If you need to get all available transitions for the current state you can simply call:
|
116
116
|
```ruby
|
117
117
|
>> Product.new.available_transitions
|
118
118
|
=> [:discontinued, :out_of_stock]
|
@@ -289,6 +289,11 @@ end
|
|
289
289
|
Any arguments passed to the event method will be passed on to the `guard`
|
290
290
|
predicate.
|
291
291
|
|
292
|
+
Note that guards will **not** raise on failure on their own. This means that if you want to
|
293
|
+
treat the failure of a guard exceptional you'll need to raise an exception yourself explicitly
|
294
|
+
in that guard (see [here](https://github.com/troessner/transitions/issues/149) for the
|
295
|
+
corresponding discussion).
|
296
|
+
|
292
297
|
|
293
298
|
#### Timestamps
|
294
299
|
|
data/Rakefile
CHANGED
@@ -1,14 +1,19 @@
|
|
1
|
-
require 'bundler'
|
2
|
-
Bundler::GemHelper.install_tasks
|
3
|
-
Bundler.setup
|
1
|
+
require 'bundler/gem_tasks'
|
4
2
|
|
5
3
|
require 'appraisal'
|
6
|
-
|
7
4
|
require 'rake/testtask'
|
5
|
+
require 'rubocop/rake_task'
|
6
|
+
|
8
7
|
Rake::TestTask.new(:test) do |test|
|
9
8
|
test.libs = %w(lib test)
|
10
9
|
test.pattern = 'test/**/test_*.rb'
|
11
10
|
test.verbose = true
|
12
11
|
end
|
13
12
|
|
13
|
+
RuboCop::RakeTask.new do |task|
|
14
|
+
task.options << '--display-cop-names'
|
15
|
+
task.patterns = ['lib/**/*.rb']
|
16
|
+
end
|
17
|
+
|
14
18
|
task default: :test
|
19
|
+
task default: :rubocop
|
data/bin/console
ADDED
@@ -3,7 +3,7 @@ module ActiveModel
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
|
6
|
+
::Transitions::Machine.class_eval do
|
7
7
|
unless method_defined?(:new_transitions_initialize) || method_defined?(:new_transitions_update)
|
8
8
|
attr_reader :attribute_name
|
9
9
|
alias_method :old_transitions_initialize, :initialize
|
@@ -64,28 +64,35 @@ module ActiveModel
|
|
64
64
|
self[transitions_state_column_name] && self[transitions_state_column_name].to_sym
|
65
65
|
end
|
66
66
|
|
67
|
+
#
|
68
|
+
# rubocop:disable Metrics/AbcSize
|
69
|
+
#
|
67
70
|
def set_initial_state
|
68
|
-
# In case we use a query with a custom select that excludes our state attribute
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
71
|
+
# In case we use a query with a custom select that excludes our state attribute
|
72
|
+
# name we need to skip the initialization below.
|
73
|
+
return unless attribute_names.include?(transitions_state_column_name.to_s) && state_not_set?
|
74
|
+
self[transitions_state_column_name] = self.class.get_state_machine.initial_state.to_s
|
75
|
+
self.class.get_state_machine.state_index[self[transitions_state_column_name].to_sym].call_action(:enter, self)
|
73
76
|
end
|
74
77
|
|
75
78
|
def state_presence
|
76
|
-
|
77
|
-
|
78
|
-
end
|
79
|
+
return if self[transitions_state_column_name].present?
|
80
|
+
errors.add(transitions_state_column_name, :presence)
|
79
81
|
end
|
80
82
|
|
81
83
|
def state_inclusion
|
82
|
-
|
83
|
-
|
84
|
-
end
|
84
|
+
return if state_included?
|
85
|
+
errors.add(transitions_state_column_name, :inclusion, value: self[transitions_state_column_name])
|
85
86
|
end
|
86
87
|
|
87
88
|
def state_not_set?
|
88
89
|
self[transitions_state_column_name].nil?
|
89
90
|
end
|
91
|
+
|
92
|
+
def state_included?
|
93
|
+
self.class.get_state_machine.states
|
94
|
+
.map { |s| s.name.to_s }
|
95
|
+
.include?(self[transitions_state_column_name].to_s)
|
96
|
+
end
|
90
97
|
end
|
91
98
|
end
|
data/lib/transitions.rb
CHANGED
@@ -6,11 +6,13 @@ require 'transitions/state_transition'
|
|
6
6
|
require 'transitions/version'
|
7
7
|
|
8
8
|
module Transitions
|
9
|
-
class InvalidTransition
|
9
|
+
class InvalidTransition < StandardError; end
|
10
10
|
class InvalidMethodOverride < StandardError; end
|
11
11
|
include Presenter
|
12
12
|
|
13
13
|
module ClassMethods
|
14
|
+
include Presenter
|
15
|
+
|
14
16
|
def inherited(klass)
|
15
17
|
super # Make sure we call other callbacks possibly defined upstream the ancestor chain.
|
16
18
|
klass.state_machine = state_machine
|
@@ -26,37 +28,34 @@ module Transitions
|
|
26
28
|
block ? @state_machine.update(options, &block) : @state_machine
|
27
29
|
end
|
28
30
|
|
31
|
+
# rubocop:disable Style/AccessorMethodName
|
29
32
|
def get_state_machine
|
30
33
|
@state_machine
|
31
34
|
end
|
32
|
-
|
33
|
-
def available_states
|
34
|
-
@state_machine.states.map(&:name).sort_by(&:to_s)
|
35
|
-
end
|
36
|
-
|
37
|
-
def available_events
|
38
|
-
@state_machine.events.keys.sort
|
39
|
-
end
|
40
35
|
end
|
41
36
|
|
42
37
|
def self.included(base)
|
43
38
|
base.extend(ClassMethods)
|
44
39
|
end
|
45
40
|
|
41
|
+
def get_state_machine
|
42
|
+
self.class.get_state_machine
|
43
|
+
end
|
44
|
+
|
46
45
|
def update_current_state(new_state, persist = false)
|
47
|
-
|
48
|
-
ivar = sm.current_state_variable
|
46
|
+
ivar = get_state_machine.current_state_variable
|
49
47
|
|
50
48
|
if Transitions.active_model_descendant?(self.class)
|
51
49
|
write_state(new_state) if persist
|
52
|
-
|
50
|
+
# TODO: This seems like a duplicate, `write_new` already calls `write_state_without_persistence`.
|
51
|
+
write_state_without_persistence(new_state)
|
53
52
|
end
|
54
53
|
|
55
54
|
instance_variable_set(ivar, new_state)
|
56
55
|
end
|
57
56
|
|
58
57
|
def available_transitions
|
59
|
-
|
58
|
+
get_state_machine.events_for(current_state)
|
60
59
|
end
|
61
60
|
|
62
61
|
def can_transition?(*events)
|
@@ -70,7 +69,7 @@ module Transitions
|
|
70
69
|
end
|
71
70
|
|
72
71
|
def current_state
|
73
|
-
sm =
|
72
|
+
sm = get_state_machine
|
74
73
|
ivar = sm.current_state_variable
|
75
74
|
|
76
75
|
value = instance_variable_get(ivar)
|
@@ -84,6 +83,7 @@ module Transitions
|
|
84
83
|
end
|
85
84
|
|
86
85
|
def self.active_model_descendant?(klazz)
|
87
|
-
|
86
|
+
# Checking directly for "ActiveModel" wouldn't work so we use some arbitrary module close to it.
|
87
|
+
defined?(ActiveModel) && klazz.included_modules.include?(ActiveModel::Dirty)
|
88
88
|
end
|
89
89
|
end
|
data/lib/transitions/event.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
module Transitions
|
2
|
+
# rubocop:disable Metrics/ClassLength
|
2
3
|
class Event
|
3
4
|
attr_reader :name, :success, :timestamp
|
4
5
|
|
5
6
|
def initialize(machine, name, options = {}, &block)
|
6
|
-
@machine
|
7
|
+
@machine = machine
|
8
|
+
@name = name
|
9
|
+
@transitions = []
|
7
10
|
if machine
|
8
11
|
machine.klass.send(:define_method, "#{name}!") do |*args|
|
9
12
|
machine.fire_event(name, self, true, *args)
|
@@ -34,12 +37,12 @@ module Transitions
|
|
34
37
|
next_state = nil
|
35
38
|
transitions.each do |transition|
|
36
39
|
next if to_state && !Array(transition.to).include?(to_state)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
next unless transition.executable?(obj, *args)
|
41
|
+
|
42
|
+
next_state = to_state || Array(transition.to).first
|
43
|
+
transition.execute(obj, *args)
|
44
|
+
update_event_timestamp(obj, next_state) if timestamp_defined?
|
45
|
+
break
|
43
46
|
end
|
44
47
|
# Update timestamps on obj if a timestamp has been defined
|
45
48
|
next_state
|
@@ -53,11 +56,11 @@ module Transitions
|
|
53
56
|
@transitions.select { |t| t.from? state }.any? { |t| t.executable?(obj, *args) }
|
54
57
|
end
|
55
58
|
|
56
|
-
def ==(
|
57
|
-
if
|
58
|
-
name ==
|
59
|
+
def ==(other)
|
60
|
+
if other.is_a? Symbol
|
61
|
+
name == other
|
59
62
|
else
|
60
|
-
name ==
|
63
|
+
name == other.name
|
61
64
|
end
|
62
65
|
end
|
63
66
|
|
@@ -100,8 +103,8 @@ module Transitions
|
|
100
103
|
|
101
104
|
# If @timestamp is true, try a default timestamp name
|
102
105
|
def default_timestamp_name(obj, next_state)
|
103
|
-
at_name =
|
104
|
-
on_name =
|
106
|
+
at_name = "#{next_state}_at"
|
107
|
+
on_name = "#{next_state}_on"
|
105
108
|
case
|
106
109
|
when obj.respond_to?(at_name) then at_name
|
107
110
|
when obj.respond_to?(on_name) then on_name
|
@@ -128,12 +131,13 @@ module Transitions
|
|
128
131
|
when Proc
|
129
132
|
callback_names
|
130
133
|
when Symbol
|
131
|
-
|
134
|
+
->(record) { record.send(callback_names) }
|
132
135
|
end
|
133
136
|
end
|
134
137
|
|
135
138
|
def error_message_for_invalid_transitions(obj)
|
136
|
-
"Can't fire event `#{name}` in current state `#{obj.current_state}` for `#{obj.class.name}`
|
139
|
+
"Can't fire event `#{name}` in current state `#{obj.current_state}` for `#{obj.class.name}`"\
|
140
|
+
" #{obj.class < ActiveRecord::Base && obj.persisted? ? "with ID #{obj.id} " : nil}"
|
137
141
|
end
|
138
142
|
end
|
139
143
|
end
|
data/lib/transitions/machine.rb
CHANGED
@@ -5,7 +5,10 @@ module Transitions
|
|
5
5
|
attr_reader :klass, :auto_scopes
|
6
6
|
|
7
7
|
def initialize(klass, options = {}, &block)
|
8
|
-
@klass
|
8
|
+
@klass = klass
|
9
|
+
@states = []
|
10
|
+
@state_index = {}
|
11
|
+
@events = {}
|
9
12
|
update(options, &block)
|
10
13
|
end
|
11
14
|
|
@@ -21,7 +24,9 @@ module Transitions
|
|
21
24
|
self
|
22
25
|
end
|
23
26
|
|
24
|
-
#
|
27
|
+
#
|
28
|
+
# rubocop:disable Metrics/MethodLength
|
29
|
+
#
|
25
30
|
def fire_event(event, record, persist, *args)
|
26
31
|
handle_state_exit_callback record
|
27
32
|
if new_state = transition_to_new_state(record, event, *args)
|
@@ -35,12 +40,9 @@ module Transitions
|
|
35
40
|
return false
|
36
41
|
end
|
37
42
|
rescue => e
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
else
|
42
|
-
raise e
|
43
|
-
end
|
43
|
+
raise e unless record.respond_to?(:event_failed)
|
44
|
+
record.send(:event_failed, event)
|
45
|
+
return false
|
44
46
|
end
|
45
47
|
|
46
48
|
def events_for(state)
|
@@ -49,7 +51,7 @@ module Transitions
|
|
49
51
|
end
|
50
52
|
|
51
53
|
def current_state_variable
|
52
|
-
# TODO Refactor me away.
|
54
|
+
# TODO: Refactor me away.
|
53
55
|
:@current_state
|
54
56
|
end
|
55
57
|
|
@@ -68,9 +70,8 @@ module Transitions
|
|
68
70
|
end
|
69
71
|
|
70
72
|
def handle_event_fired_callback(record, new_state, event)
|
71
|
-
|
72
|
-
|
73
|
-
end
|
73
|
+
return unless record.respond_to?(:event_fired, true)
|
74
|
+
record.send(:event_fired, record.current_state, new_state, event)
|
74
75
|
end
|
75
76
|
|
76
77
|
def handle_event_success_callback(record, event)
|
@@ -78,18 +79,16 @@ module Transitions
|
|
78
79
|
end
|
79
80
|
|
80
81
|
def handle_event_failed_callback(record, event)
|
81
|
-
|
82
|
-
|
83
|
-
end
|
82
|
+
return unless record.respond_to?(:event_failed, true)
|
83
|
+
record.send(:event_failed, event)
|
84
84
|
end
|
85
85
|
|
86
86
|
def state(name, options = {})
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
end
|
87
|
+
return if @state_index.key?(name) # Just ignore duplicates
|
88
|
+
state = State.new(name, machine: self)
|
89
|
+
state.update options
|
90
|
+
@state_index[name] = state
|
91
|
+
@states << state
|
93
92
|
end
|
94
93
|
|
95
94
|
def event(name, options = {}, &block)
|
@@ -100,7 +99,9 @@ module Transitions
|
|
100
99
|
@states.each do |state|
|
101
100
|
state_name = state.name.to_s
|
102
101
|
if @klass.respond_to?(state_name)
|
103
|
-
fail InvalidMethodOverride,
|
102
|
+
fail InvalidMethodOverride,
|
103
|
+
"Transitions: Can not define scope `#{state_name}` because there is already"\
|
104
|
+
'an equally named method defined - either rename the existing method or the state.'
|
104
105
|
end
|
105
106
|
@klass.scope state_name, -> { @klass.where(@klass.state_machine.attribute_name => state_name) }
|
106
107
|
end
|