aasm 5.0.6 → 5.2.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/.travis.yml +13 -13
- data/Appraisals +6 -10
- data/CHANGELOG.md +33 -0
- data/Gemfile +1 -1
- data/README.md +102 -13
- data/aasm.gemspec +1 -1
- data/gemfiles/norails.gemfile +1 -1
- data/gemfiles/rails_4.2.gemfile +1 -0
- data/gemfiles/rails_4.2_mongoid_5.gemfile +1 -0
- data/gemfiles/rails_5.0.gemfile +1 -0
- data/gemfiles/rails_5.1.gemfile +1 -0
- data/gemfiles/rails_5.2.gemfile +1 -0
- data/lib/aasm.rb +0 -2
- data/lib/aasm/base.rb +30 -11
- data/lib/aasm/configuration.rb +3 -0
- data/lib/aasm/core/event.rb +7 -2
- data/lib/aasm/core/state.rb +6 -5
- data/lib/aasm/core/transition.rb +1 -1
- data/lib/aasm/dsl_helper.rb +24 -22
- data/lib/aasm/instance_base.rb +12 -1
- data/lib/aasm/localizer.rb +13 -3
- data/lib/aasm/persistence/active_record_persistence.rb +18 -0
- data/lib/aasm/persistence/base.rb +13 -2
- data/lib/aasm/persistence/orm.rb +23 -19
- data/lib/aasm/rspec/transition_from.rb +5 -1
- data/lib/aasm/version.rb +1 -1
- data/spec/database.rb +10 -12
- data/spec/en.yml +0 -3
- data/spec/{en_deprecated_style.yml → localizer_test_model_deprecated_style.yml} +6 -3
- data/spec/localizer_test_model_new_style.yml +11 -0
- data/spec/models/active_record/active_record_callback.rb +93 -0
- data/spec/models/active_record/localizer_test_model.rb +11 -3
- data/spec/models/active_record/namespaced.rb +16 -0
- data/spec/models/active_record/timestamp_example.rb +16 -0
- data/spec/models/default_state.rb +1 -1
- data/spec/models/mongoid/timestamp_example_mongoid.rb +20 -0
- data/spec/models/simple_example.rb +6 -0
- data/spec/models/timestamps_example.rb +19 -0
- data/spec/models/timestamps_with_named_machine_example.rb +13 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/unit/api_spec.rb +4 -0
- data/spec/unit/inspection_multiple_spec.rb +9 -5
- data/spec/unit/inspection_spec.rb +7 -3
- data/spec/unit/localizer_spec.rb +49 -18
- data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +17 -0
- data/spec/unit/persistence/active_record_persistence_spec.rb +79 -0
- data/spec/unit/persistence/mongoid_persistence_spec.rb +12 -0
- data/spec/unit/rspec_matcher_spec.rb +3 -0
- data/spec/unit/simple_example_spec.rb +15 -0
- data/spec/unit/state_spec.rb +21 -5
- data/spec/unit/timestamps_spec.rb +32 -0
- metadata +26 -13
- data/callbacks.txt +0 -51
- data/gemfiles/rails_3.2.gemfile +0 -14
data/gemfiles/rails_5.2.gemfile
CHANGED
data/lib/aasm.rb
CHANGED
data/lib/aasm/base.rb
CHANGED
@@ -37,6 +37,9 @@ module AASM
|
|
37
37
|
# string for a specific lock type i.e. FOR UPDATE NOWAIT
|
38
38
|
configure :requires_lock, false
|
39
39
|
|
40
|
+
# automatically set `"#{state_name}_at" = ::Time.now` on state changes
|
41
|
+
configure :timestamps, false
|
42
|
+
|
40
43
|
# set to true to forbid direct assignment of aasm_state column (in ActiveRecord)
|
41
44
|
configure :no_direct_assignment, false
|
42
45
|
|
@@ -51,19 +54,12 @@ module AASM
|
|
51
54
|
# Configure a logger, with default being a Logger to STDERR
|
52
55
|
configure :logger, Logger.new(STDERR)
|
53
56
|
|
57
|
+
# setup timestamp-setting callback if enabled
|
58
|
+
setup_timestamps(@name)
|
59
|
+
|
54
60
|
# make sure to raise an error if no_direct_assignment is enabled
|
55
61
|
# and attribute is directly assigned though
|
56
|
-
|
57
|
-
|
58
|
-
if @state_machine.config.no_direct_assignment
|
59
|
-
@klass.send(:define_method, "#{@state_machine.config.column}=") do |state_name|
|
60
|
-
if self.class.aasm(:"#{aasm_name}").state_machine.config.no_direct_assignment
|
61
|
-
raise AASM::NoDirectAssignmentError.new('direct assignment of AASM column has been disabled (see AASM configuration for this class)')
|
62
|
-
else
|
63
|
-
super(state_name)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
62
|
+
setup_no_direct_assignment(@name)
|
67
63
|
end
|
68
64
|
|
69
65
|
# This method is both a getter and a setter
|
@@ -267,5 +263,28 @@ module AASM
|
|
267
263
|
end
|
268
264
|
end
|
269
265
|
|
266
|
+
def setup_timestamps(aasm_name)
|
267
|
+
return unless @state_machine.config.timestamps
|
268
|
+
|
269
|
+
after_all_transitions do
|
270
|
+
if self.class.aasm(:"#{aasm_name}").state_machine.config.timestamps
|
271
|
+
ts_setter = "#{aasm(aasm_name).to_state}_at="
|
272
|
+
respond_to?(ts_setter) && send(ts_setter, ::Time.now)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def setup_no_direct_assignment(aasm_name)
|
278
|
+
return unless @state_machine.config.no_direct_assignment
|
279
|
+
|
280
|
+
@klass.send(:define_method, "#{@state_machine.config.column}=") do |state_name|
|
281
|
+
if self.class.aasm(:"#{aasm_name}").state_machine.config.no_direct_assignment
|
282
|
+
raise AASM::NoDirectAssignmentError.new('direct assignment of AASM column has been disabled (see AASM configuration for this class)')
|
283
|
+
else
|
284
|
+
super(state_name)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
270
289
|
end
|
271
290
|
end
|
data/lib/aasm/configuration.rb
CHANGED
@@ -24,6 +24,9 @@ module AASM
|
|
24
24
|
# for ActiveRecord: use pessimistic locking
|
25
25
|
attr_accessor :requires_lock
|
26
26
|
|
27
|
+
# automatically set `"#{state_name}_at" = ::Time.now` on state changes
|
28
|
+
attr_accessor :timestamps
|
29
|
+
|
27
30
|
# forbid direct assignment in aasm_state column (in ActiveRecord)
|
28
31
|
attr_accessor :no_direct_assignment
|
29
32
|
|
data/lib/aasm/core/event.rb
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
module AASM::Core
|
4
4
|
class Event
|
5
|
-
include DslHelper
|
5
|
+
include AASM::DslHelper
|
6
6
|
|
7
|
-
attr_reader :name, :state_machine, :options
|
7
|
+
attr_reader :name, :state_machine, :options, :default_display_name
|
8
8
|
|
9
9
|
def initialize(name, state_machine, options = {}, &block)
|
10
10
|
@name = name
|
@@ -13,6 +13,7 @@ module AASM::Core
|
|
13
13
|
@valid_transitions = {}
|
14
14
|
@guards = Array(options[:guard] || options[:guards] || options[:if])
|
15
15
|
@unless = Array(options[:unless]) #TODO: This could use a better name
|
16
|
+
@default_display_name = name.to_s.gsub(/_/, ' ').capitalize
|
16
17
|
|
17
18
|
# from aasm4
|
18
19
|
@options = options # QUESTION: .dup ?
|
@@ -109,6 +110,10 @@ module AASM::Core
|
|
109
110
|
transitions.flat_map(&:failures)
|
110
111
|
end
|
111
112
|
|
113
|
+
def to_s
|
114
|
+
name.to_s
|
115
|
+
end
|
116
|
+
|
112
117
|
private
|
113
118
|
|
114
119
|
def attach_event_guards(definitions)
|
data/lib/aasm/core/state.rb
CHANGED
@@ -2,12 +2,13 @@
|
|
2
2
|
|
3
3
|
module AASM::Core
|
4
4
|
class State
|
5
|
-
attr_reader :name, :state_machine, :options
|
5
|
+
attr_reader :name, :state_machine, :options, :default_display_name
|
6
6
|
|
7
7
|
def initialize(name, klass, state_machine, options={})
|
8
8
|
@name = name
|
9
9
|
@klass = klass
|
10
10
|
@state_machine = state_machine
|
11
|
+
@default_display_name = name.to_s.gsub(/_/, ' ').capitalize
|
11
12
|
update(options)
|
12
13
|
end
|
13
14
|
|
@@ -54,11 +55,11 @@ module AASM::Core
|
|
54
55
|
end
|
55
56
|
|
56
57
|
def display_name
|
57
|
-
@display_name
|
58
|
+
@display_name = begin
|
58
59
|
if Module.const_defined?(:I18n)
|
59
60
|
localized_name
|
60
61
|
else
|
61
|
-
|
62
|
+
@default_display_name
|
62
63
|
end
|
63
64
|
end
|
64
65
|
end
|
@@ -75,8 +76,8 @@ module AASM::Core
|
|
75
76
|
private
|
76
77
|
|
77
78
|
def update(options = {})
|
78
|
-
if options.key?(:display)
|
79
|
-
@
|
79
|
+
if options.key?(:display)
|
80
|
+
@default_display_name = options.delete(:display)
|
80
81
|
end
|
81
82
|
@options = options
|
82
83
|
self
|
data/lib/aasm/core/transition.rb
CHANGED
data/lib/aasm/dsl_helper.rb
CHANGED
@@ -1,30 +1,32 @@
|
|
1
|
-
module
|
1
|
+
module AASM
|
2
|
+
module DslHelper
|
2
3
|
|
3
|
-
|
4
|
-
|
4
|
+
class Proxy
|
5
|
+
attr_accessor :options
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
def initialize(options, valid_keys, source)
|
8
|
+
@valid_keys = valid_keys
|
9
|
+
@source = source
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
@options = options
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
def method_missing(name, *args, &block)
|
15
|
+
if @valid_keys.include?(name)
|
16
|
+
options[name] = Array(options[name])
|
17
|
+
options[name] << block if block
|
18
|
+
options[name] += Array(args)
|
19
|
+
else
|
20
|
+
@source.send name, *args, &block
|
21
|
+
end
|
20
22
|
end
|
21
23
|
end
|
22
|
-
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
def add_options_from_dsl(options, valid_keys, &block)
|
26
|
+
proxy = Proxy.new(options, valid_keys, self)
|
27
|
+
proxy.instance_eval(&block)
|
28
|
+
proxy.options
|
29
|
+
end
|
29
30
|
|
30
|
-
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/aasm/instance_base.rb
CHANGED
@@ -28,7 +28,7 @@ module AASM
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def human_state
|
31
|
-
|
31
|
+
state_object_for_name(current_state).display_name
|
32
32
|
end
|
33
33
|
|
34
34
|
def states(options={}, *args)
|
@@ -78,6 +78,17 @@ module AASM
|
|
78
78
|
events
|
79
79
|
end
|
80
80
|
|
81
|
+
def permitted_transitions
|
82
|
+
events(permitted: true).flat_map do |event|
|
83
|
+
available_transitions = event.transitions_from_state(current_state)
|
84
|
+
allowed_transitions = available_transitions.select { |t| t.allowed?(@instance) }
|
85
|
+
|
86
|
+
allowed_transitions.map do |transition|
|
87
|
+
{ event: event.name, state: transition.to }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
81
92
|
def state_object_for_name(name)
|
82
93
|
obj = @instance.class.aasm(@name).states.find {|s| s.name == name}
|
83
94
|
raise AASM::UndefinedState, "State :#{name} doesn't exist" if obj.nil?
|
data/lib/aasm/localizer.rb
CHANGED
@@ -5,7 +5,7 @@ module AASM
|
|
5
5
|
list << :"#{i18n_scope(klass)}.events.#{i18n_klass(ancestor)}.#{event}"
|
6
6
|
list
|
7
7
|
end
|
8
|
-
translate_queue(checklist) || I18n.translate(checklist.shift, :default => event
|
8
|
+
translate_queue(checklist) || I18n.translate(checklist.shift, :default => default_display_name(event))
|
9
9
|
end
|
10
10
|
|
11
11
|
def human_state_name(klass, state)
|
@@ -14,7 +14,7 @@ module AASM
|
|
14
14
|
list << item_for(klass, state, ancestor, :old_style => true)
|
15
15
|
list
|
16
16
|
end
|
17
|
-
translate_queue(checklist) || I18n.translate(checklist.shift, :default => state
|
17
|
+
translate_queue(checklist) || I18n.translate(checklist.shift, :default => default_display_name(state))
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
@@ -46,8 +46,18 @@ module AASM
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def ancestors_list(klass)
|
49
|
+
has_active_record_base = defined?(::ActiveRecord::Base)
|
49
50
|
klass.ancestors.select do |ancestor|
|
50
|
-
|
51
|
+
not_active_record_base = has_active_record_base ? (ancestor != ::ActiveRecord::Base) : true
|
52
|
+
ancestor.respond_to?(:model_name) && not_active_record_base
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def default_display_name(object) # Can use better arguement name
|
57
|
+
if object.respond_to?(:default_display_name)
|
58
|
+
object.default_display_name
|
59
|
+
else
|
60
|
+
object.to_s.gsub(/_/, ' ').capitalize
|
51
61
|
end
|
52
62
|
end
|
53
63
|
end
|
@@ -61,6 +61,24 @@ module AASM
|
|
61
61
|
|
62
62
|
private
|
63
63
|
|
64
|
+
def aasm_execute_after_commit
|
65
|
+
begin
|
66
|
+
require 'after_commit_everywhere'
|
67
|
+
raise LoadError unless Gem::Version.new(::AfterCommitEverywhere::VERSION) >= Gem::Version.new('0.1.5')
|
68
|
+
|
69
|
+
self.extend ::AfterCommitEverywhere
|
70
|
+
after_commit do
|
71
|
+
yield
|
72
|
+
end
|
73
|
+
rescue LoadError
|
74
|
+
warn <<-MSG
|
75
|
+
[DEPRECATION] :after_commit AASM callback is not safe in terms of race conditions and redundant calls.
|
76
|
+
Please add `gem 'after_commit_everywhere', '~> 1.0'` to your Gemfile in order to fix that.
|
77
|
+
MSG
|
78
|
+
yield
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
64
82
|
def aasm_raise_invalid_record
|
65
83
|
raise ActiveRecord::RecordInvalid.new(self)
|
66
84
|
end
|
@@ -59,7 +59,9 @@ module AASM
|
|
59
59
|
# make sure to create a (named) scope for each state
|
60
60
|
def state_with_scope(*args)
|
61
61
|
names = state_without_scope(*args)
|
62
|
-
names.each
|
62
|
+
names.each do |name|
|
63
|
+
create_scopes(name)
|
64
|
+
end
|
63
65
|
end
|
64
66
|
alias_method :state_without_scope, :state
|
65
67
|
alias_method :state, :state_with_scope
|
@@ -71,7 +73,16 @@ module AASM
|
|
71
73
|
end
|
72
74
|
|
73
75
|
def create_scope(name)
|
74
|
-
@klass.aasm_create_scope(@name, name)
|
76
|
+
@klass.aasm_create_scope(@name, name) if create_scope?(name)
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_scopes(name)
|
80
|
+
if namespace?
|
81
|
+
# Create default scopes even when namespace? for backward compatibility
|
82
|
+
namepaced_name = "#{namespace}_#{name}"
|
83
|
+
create_scope(namepaced_name)
|
84
|
+
end
|
85
|
+
create_scope(name)
|
75
86
|
end
|
76
87
|
end # Base
|
77
88
|
|
data/lib/aasm/persistence/orm.rb
CHANGED
@@ -81,6 +81,10 @@ module AASM
|
|
81
81
|
true
|
82
82
|
end
|
83
83
|
|
84
|
+
def aasm_execute_after_commit
|
85
|
+
yield
|
86
|
+
end
|
87
|
+
|
84
88
|
def aasm_write_state_attribute(state, name=:default)
|
85
89
|
aasm_write_attribute(self.class.aasm(name).attribute_name, aasm_raw_attribute_value(state, name))
|
86
90
|
end
|
@@ -116,32 +120,32 @@ module AASM
|
|
116
120
|
|
117
121
|
# Returns true if event was fired successfully and transaction completed.
|
118
122
|
def aasm_fire_event(state_machine_name, name, options, *args, &block)
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
end
|
129
|
-
else
|
123
|
+
return super unless aasm_supports_transactions? && options[:persist]
|
124
|
+
|
125
|
+
event = self.class.aasm(state_machine_name).state_machine.events[name]
|
126
|
+
event.fire_callbacks(:before_transaction, self, *args)
|
127
|
+
event.fire_global_callbacks(:before_all_transactions, self, *args)
|
128
|
+
|
129
|
+
begin
|
130
|
+
success = if options[:persist] && use_transactions?(state_machine_name)
|
131
|
+
aasm_transaction(requires_new?(state_machine_name), requires_lock?(state_machine_name)) do
|
130
132
|
super
|
131
133
|
end
|
134
|
+
else
|
135
|
+
super
|
136
|
+
end
|
132
137
|
|
133
|
-
|
138
|
+
if success && !(event.options.keys & [:after_commit, :after_all_commits]).empty?
|
139
|
+
aasm_execute_after_commit do
|
134
140
|
event.fire_callbacks(:after_commit, self, *args)
|
135
141
|
event.fire_global_callbacks(:after_all_commits, self, *args)
|
136
142
|
end
|
137
|
-
|
138
|
-
success
|
139
|
-
ensure
|
140
|
-
event.fire_callbacks(:after_transaction, self, *args)
|
141
|
-
event.fire_global_callbacks(:after_all_transactions, self, *args)
|
142
143
|
end
|
143
|
-
|
144
|
-
|
144
|
+
|
145
|
+
success
|
146
|
+
ensure
|
147
|
+
event.fire_callbacks(:after_transaction, self, *args)
|
148
|
+
event.fire_global_callbacks(:after_all_transactions, self, *args)
|
145
149
|
end
|
146
150
|
end
|
147
151
|
|
@@ -2,7 +2,11 @@ RSpec::Matchers.define :transition_from do |from_state|
|
|
2
2
|
match do |obj|
|
3
3
|
@state_machine_name ||= :default
|
4
4
|
obj.aasm(@state_machine_name).current_state = from_state.to_sym
|
5
|
-
|
5
|
+
begin
|
6
|
+
obj.send(@event, *@args) && obj.aasm(@state_machine_name).current_state == @to_state.to_sym
|
7
|
+
rescue AASM::InvalidTransition
|
8
|
+
false
|
9
|
+
end
|
6
10
|
end
|
7
11
|
|
8
12
|
chain :on do |state_machine_name|
|
data/lib/aasm/version.rb
CHANGED
data/spec/database.rb
CHANGED
@@ -5,17 +5,10 @@ ActiveRecord::Migration.suppress_messages do
|
|
5
5
|
end
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
t.string "status"
|
13
|
-
end
|
14
|
-
ActiveRecord::Migration.create_table "implemented_abstract_class_dsls", :force => true do |t|
|
15
|
-
t.string "status"
|
16
|
-
end
|
17
|
-
ActiveRecord::Migration.create_table "users", :force => true do |t|
|
18
|
-
t.string "status"
|
8
|
+
%w(simple_new_dsls multiple_simple_new_dsls implemented_abstract_class_dsls users multiple_namespaceds).each do |table_name|
|
9
|
+
ActiveRecord::Migration.create_table table_name, :force => true do |t|
|
10
|
+
t.string "status"
|
11
|
+
end
|
19
12
|
end
|
20
13
|
|
21
14
|
ActiveRecord::Migration.create_table "complex_active_record_examples", :force => true do |t|
|
@@ -27,7 +20,7 @@ ActiveRecord::Migration.suppress_messages do
|
|
27
20
|
t.string "status"
|
28
21
|
end
|
29
22
|
|
30
|
-
%w(validators multiple_validators workers invalid_persistors multiple_invalid_persistors silent_persistors multiple_silent_persistors).each do |table_name|
|
23
|
+
%w(validators multiple_validators workers invalid_persistors multiple_invalid_persistors silent_persistors multiple_silent_persistors active_record_callbacks).each do |table_name|
|
31
24
|
ActiveRecord::Migration.create_table table_name, :force => true do |t|
|
32
25
|
t.string "name"
|
33
26
|
t.string "status"
|
@@ -56,4 +49,9 @@ ActiveRecord::Migration.suppress_messages do
|
|
56
49
|
t.string "state"
|
57
50
|
t.string "some_string"
|
58
51
|
end
|
52
|
+
|
53
|
+
ActiveRecord::Migration.create_table "timestamp_examples", :force => true do |t|
|
54
|
+
t.string "aasm_state"
|
55
|
+
t.datetime "opened_at"
|
56
|
+
end
|
59
57
|
end
|