aasm 4.0.8 → 4.5.2
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/.travis.yml +7 -2
- data/CHANGELOG.md +43 -2
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +6 -3
- data/LICENSE +1 -1
- data/PLANNED_CHANGES.md +8 -4
- data/README.md +171 -14
- data/aasm.gemspec +1 -5
- data/gemfiles/rails_3.2.gemfile +3 -2
- data/gemfiles/rails_4.0.gemfile +2 -6
- data/gemfiles/rails_4.0_mongo_mapper.gemfile +14 -0
- data/gemfiles/rails_4.1.gemfile +2 -6
- data/gemfiles/rails_4.1_mongo_mapper.gemfile +14 -0
- data/gemfiles/rails_4.2.gemfile +1 -5
- data/gemfiles/rails_4.2_mongo_mapper.gemfile +14 -0
- data/gemfiles/rails_4.2_mongoid_5.gemfile +12 -0
- data/lib/aasm/aasm.rb +69 -36
- data/lib/aasm/base.rb +51 -21
- data/lib/aasm/core/event.rb +7 -6
- data/lib/aasm/core/state.rb +11 -8
- data/lib/aasm/core/transition.rb +7 -5
- data/lib/aasm/errors.rb +15 -1
- data/lib/aasm/instance_base.rb +17 -13
- data/lib/aasm/localizer.rb +1 -1
- data/lib/aasm/persistence/active_record_persistence.rb +63 -73
- data/lib/aasm/persistence/base.rb +55 -20
- data/lib/aasm/persistence/mongo_mapper_persistence.rb +157 -0
- data/lib/aasm/persistence/mongoid_persistence.rb +16 -41
- data/lib/aasm/persistence/plain_persistence.rb +8 -7
- data/lib/aasm/persistence/sequel_persistence.rb +12 -9
- data/lib/aasm/persistence.rb +2 -0
- data/lib/aasm/rspec/allow_event.rb +22 -0
- data/lib/aasm/rspec/allow_transition_to.rb +22 -0
- data/lib/aasm/rspec/have_state.rb +22 -0
- data/lib/aasm/rspec/transition_from.rb +32 -0
- data/lib/aasm/rspec.rb +5 -0
- data/lib/aasm/state_machine.rb +20 -6
- data/lib/aasm/version.rb +1 -1
- data/spec/database.rb +27 -1
- data/spec/models/active_record/basic_active_record_two_state_machines_example.rb +25 -0
- data/spec/models/active_record/complex_active_record_example.rb +33 -0
- data/spec/models/active_record/derivate_new_dsl.rb +7 -0
- data/spec/models/active_record/false_state.rb +35 -0
- data/spec/models/active_record/gate.rb +39 -0
- data/spec/models/active_record/localizer_test_model.rb +34 -0
- data/spec/models/active_record/no_direct_assignment.rb +21 -0
- data/spec/models/active_record/no_scope.rb +21 -0
- data/spec/models/active_record/persisted_state.rb +12 -0
- data/spec/models/active_record/provided_and_persisted_state.rb +24 -0
- data/spec/models/active_record/reader.rb +7 -0
- data/spec/models/active_record/readme_job.rb +21 -0
- data/spec/models/active_record/simple_new_dsl.rb +17 -0
- data/spec/models/active_record/thief.rb +29 -0
- data/spec/models/active_record/transient.rb +6 -0
- data/spec/models/active_record/with_enum.rb +39 -0
- data/spec/models/active_record/with_false_enum.rb +31 -0
- data/spec/models/active_record/with_true_enum.rb +39 -0
- data/spec/models/active_record/writer.rb +6 -0
- data/spec/models/basic_two_state_machines_example.rb +25 -0
- data/spec/models/callbacks/basic.rb +3 -0
- data/spec/models/callbacks/basic_multiple.rb +75 -0
- data/spec/models/callbacks/guard_within_block_multiple.rb +66 -0
- data/spec/models/callbacks/multiple_transitions_transition_guard_multiple.rb +65 -0
- data/spec/models/callbacks/private_method_multiple.rb +44 -0
- data/spec/models/callbacks/with_args.rb +9 -9
- data/spec/models/callbacks/with_args_multiple.rb +61 -0
- data/spec/models/callbacks/{with_state_args.rb → with_state_arg.rb} +1 -1
- data/spec/models/callbacks/with_state_arg_multiple.rb +26 -0
- data/spec/models/complex_example.rb +222 -0
- data/spec/models/conversation.rb +47 -1
- data/spec/models/default_state.rb +12 -0
- data/spec/models/foo.rb +57 -0
- data/spec/models/foo_callback_multiple.rb +45 -0
- data/spec/models/guardian.rb +10 -0
- data/spec/models/guardian_multiple.rb +48 -0
- data/spec/models/initial_state_proc.rb +31 -0
- data/spec/models/invalid_persistor.rb +15 -0
- data/spec/models/mongo_mapper/complex_mongo_mapper_example.rb +37 -0
- data/spec/models/mongo_mapper/no_scope_mongo_mapper.rb +21 -0
- data/spec/models/mongo_mapper/simple_mongo_mapper.rb +23 -0
- data/spec/models/mongo_mapper/simple_new_dsl_mongo_mapper.rb +25 -0
- data/spec/models/mongoid/complex_mongoid_example.rb +37 -0
- data/spec/models/mongoid/no_scope_mongoid.rb +11 -0
- data/spec/models/mongoid/simple_mongoid.rb +12 -0
- data/spec/models/mongoid/simple_new_dsl_mongoid.rb +14 -1
- data/spec/models/no_initial_state.rb +25 -0
- data/spec/models/parametrised_event.rb +1 -1
- data/spec/models/parametrised_event_multiple.rb +29 -0
- data/spec/models/provided_state.rb +24 -0
- data/spec/models/sequel/complex_sequel_example.rb +45 -0
- data/spec/models/sequel/sequel_multiple.rb +25 -0
- data/spec/models/sequel/sequel_simple.rb +25 -0
- data/spec/models/silencer.rb +5 -0
- data/spec/models/simple_example.rb +15 -0
- data/spec/models/simple_multiple_example.rb +30 -0
- data/spec/models/state_machine_with_failed_event.rb +12 -0
- data/spec/models/sub_class.rb +7 -0
- data/spec/models/sub_class_with_more_states.rb +18 -0
- data/spec/models/super_class.rb +46 -0
- data/spec/models/transactor.rb +27 -0
- data/spec/models/valid_state_name.rb +23 -0
- data/spec/models/validator.rb +47 -0
- data/spec/spec_helper.rb +4 -4
- data/spec/unit/api_spec.rb +6 -1
- data/spec/unit/basic_two_state_machines_example_spec.rb +10 -0
- data/spec/unit/callback_multiple_spec.rb +295 -0
- data/spec/unit/callbacks_spec.rb +3 -2
- data/spec/unit/complex_example_spec.rb +2 -2
- data/spec/unit/complex_multiple_example_spec.rb +99 -0
- data/spec/unit/edge_cases_spec.rb +16 -0
- data/spec/unit/event_multiple_spec.rb +73 -0
- data/spec/unit/event_naming_spec.rb +2 -15
- data/spec/unit/event_spec.rb +11 -6
- data/spec/unit/guard_multiple_spec.rb +60 -0
- data/spec/unit/guard_spec.rb +12 -0
- data/spec/unit/initial_state_multiple_spec.rb +15 -0
- data/spec/unit/initial_state_spec.rb +3 -18
- data/spec/unit/inspection_multiple_spec.rb +201 -0
- data/spec/unit/inspection_spec.rb +12 -7
- data/spec/unit/localizer_spec.rb +0 -38
- data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +573 -0
- data/spec/unit/persistence/active_record_persistence_spec.rb +70 -13
- data/spec/unit/persistence/mongo_mapper_persistence_multiple_spec.rb +146 -0
- data/spec/unit/persistence/mongo_mapper_persistence_spec.rb +93 -0
- data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +127 -0
- data/spec/unit/persistence/mongoid_persistence_spec.rb +79 -0
- data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +153 -0
- data/spec/unit/persistence/sequel_persistence_spec.rb +20 -23
- data/spec/unit/readme_spec.rb +42 -0
- data/spec/unit/reloading_spec.rb +1 -1
- data/spec/unit/rspec_matcher_spec.rb +79 -0
- data/spec/unit/simple_example_spec.rb +26 -42
- data/spec/unit/simple_multiple_example_spec.rb +63 -0
- data/spec/unit/state_spec.rb +3 -1
- data/spec/unit/subclassing_multiple_spec.rb +39 -0
- data/spec/unit/subclassing_spec.rb +10 -10
- data/spec/unit/transition_spec.rb +39 -25
- metadata +152 -23
- data/spec/models/active_record/api.rb +0 -75
- data/spec/models/argument.rb +0 -11
- data/spec/models/auth_machine.rb +0 -88
- data/spec/models/bar.rb +0 -15
- data/spec/models/father.rb +0 -21
- data/spec/models/persistence.rb +0 -164
- data/spec/models/son.rb +0 -3
- data/spec/unit/persistence/mongoid_persistance_spec.rb +0 -152
|
@@ -10,7 +10,7 @@ module AASM
|
|
|
10
10
|
#
|
|
11
11
|
# Adds
|
|
12
12
|
#
|
|
13
|
-
#
|
|
13
|
+
# after_initialize :aasm_ensure_initial_state
|
|
14
14
|
#
|
|
15
15
|
# As a result, it doesn't matter when you define your methods - the following 2 are equivalent
|
|
16
16
|
#
|
|
@@ -30,7 +30,6 @@ module AASM
|
|
|
30
30
|
#
|
|
31
31
|
def self.included(base)
|
|
32
32
|
base.send(:include, AASM::Persistence::Base)
|
|
33
|
-
base.extend AASM::Persistence::ActiveRecordPersistence::ClassMethods
|
|
34
33
|
base.send(:include, AASM::Persistence::ActiveRecordPersistence::InstanceMethods)
|
|
35
34
|
|
|
36
35
|
base.after_initialize do
|
|
@@ -41,34 +40,6 @@ module AASM
|
|
|
41
40
|
base.validate :aasm_validate_states
|
|
42
41
|
end
|
|
43
42
|
|
|
44
|
-
module ClassMethods
|
|
45
|
-
|
|
46
|
-
def find_in_state(number, state, *args)
|
|
47
|
-
with_state_scope state do
|
|
48
|
-
find(number, *args)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def count_in_state(state, *args)
|
|
53
|
-
with_state_scope state do
|
|
54
|
-
count(*args)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def calculate_in_state(state, *args)
|
|
59
|
-
with_state_scope state do
|
|
60
|
-
calculate(*args)
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
protected
|
|
65
|
-
def with_state_scope(state)
|
|
66
|
-
with_scope :find => {:conditions => ["#{table_name}.#{aasm_column} = ?", state.to_s]} do
|
|
67
|
-
yield if block_given?
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
43
|
module InstanceMethods
|
|
73
44
|
|
|
74
45
|
# Writes <tt>state</tt> to the state column and persists it to the database
|
|
@@ -80,22 +51,18 @@ module AASM
|
|
|
80
51
|
# Foo.find(1).aasm.current_state # => :closed
|
|
81
52
|
#
|
|
82
53
|
# NOTE: intended to be called from an event
|
|
83
|
-
def aasm_write_state(state)
|
|
84
|
-
old_value = read_attribute(self.class.aasm.attribute_name)
|
|
85
|
-
aasm_write_attribute state
|
|
54
|
+
def aasm_write_state(state, name=:default)
|
|
55
|
+
old_value = read_attribute(self.class.aasm(name).attribute_name)
|
|
56
|
+
aasm_write_attribute state, name
|
|
86
57
|
|
|
87
|
-
success = if aasm_skipping_validations
|
|
88
|
-
value = aasm_raw_attribute_value
|
|
89
|
-
|
|
58
|
+
success = if aasm_skipping_validations(name)
|
|
59
|
+
value = aasm_raw_attribute_value(state, name)
|
|
60
|
+
aasm_update_column(name, value)
|
|
90
61
|
else
|
|
91
62
|
self.save
|
|
92
63
|
end
|
|
93
|
-
unless success
|
|
94
|
-
write_attribute(self.class.aasm.attribute_name, old_value)
|
|
95
|
-
return false
|
|
96
|
-
end
|
|
97
64
|
|
|
98
|
-
true
|
|
65
|
+
success ? true : aasm_rollback(name, old_value)
|
|
99
66
|
end
|
|
100
67
|
|
|
101
68
|
# Writes <tt>state</tt> to the state column, but does not persist it to the database
|
|
@@ -110,46 +77,56 @@ module AASM
|
|
|
110
77
|
# Foo.find(1).aasm.current_state # => :closed
|
|
111
78
|
#
|
|
112
79
|
# NOTE: intended to be called from an event
|
|
113
|
-
def aasm_write_state_without_persistence(state)
|
|
114
|
-
aasm_write_attribute
|
|
80
|
+
def aasm_write_state_without_persistence(state, name=:default)
|
|
81
|
+
aasm_write_attribute(state, name)
|
|
115
82
|
end
|
|
116
83
|
|
|
117
84
|
private
|
|
118
|
-
|
|
119
|
-
|
|
85
|
+
|
|
86
|
+
def aasm_update_column(name, value)
|
|
87
|
+
self.class.where(self.class.primary_key => self.id).update_all(self.class.aasm(name).attribute_name => value) == 1
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def aasm_rollback(name, old_value)
|
|
91
|
+
write_attribute(self.class.aasm(name).attribute_name, old_value)
|
|
92
|
+
false
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def aasm_enum(name=:default)
|
|
96
|
+
case AASM::StateMachine[self.class][name].config.enum
|
|
120
97
|
when false then nil
|
|
121
|
-
when true then aasm_guess_enum_method
|
|
122
|
-
when nil then aasm_guess_enum_method if aasm_column_looks_like_enum
|
|
123
|
-
else AASM::StateMachine[self.class].config.enum
|
|
98
|
+
when true then aasm_guess_enum_method(name)
|
|
99
|
+
when nil then aasm_guess_enum_method(name) if aasm_column_looks_like_enum(name)
|
|
100
|
+
else AASM::StateMachine[self.class][name].config.enum
|
|
124
101
|
end
|
|
125
102
|
end
|
|
126
103
|
|
|
127
|
-
def aasm_column_looks_like_enum
|
|
128
|
-
self.class.columns_hash[self.class.aasm.attribute_name.to_s].type == :integer
|
|
104
|
+
def aasm_column_looks_like_enum(name=:default)
|
|
105
|
+
self.class.columns_hash[self.class.aasm(name).attribute_name.to_s].type == :integer
|
|
129
106
|
end
|
|
130
107
|
|
|
131
|
-
def aasm_guess_enum_method
|
|
132
|
-
self.class.aasm.attribute_name.to_s.pluralize.to_sym
|
|
108
|
+
def aasm_guess_enum_method(name=:default)
|
|
109
|
+
self.class.aasm(name).attribute_name.to_s.pluralize.to_sym
|
|
133
110
|
end
|
|
134
111
|
|
|
135
|
-
def aasm_skipping_validations
|
|
136
|
-
AASM::StateMachine[self.class].config.skip_validation_on_save
|
|
112
|
+
def aasm_skipping_validations(state_machine_name)
|
|
113
|
+
AASM::StateMachine[self.class][state_machine_name].config.skip_validation_on_save
|
|
137
114
|
end
|
|
138
115
|
|
|
139
|
-
def aasm_write_attribute(state)
|
|
140
|
-
write_attribute
|
|
116
|
+
def aasm_write_attribute(state, name=:default)
|
|
117
|
+
write_attribute(self.class.aasm(name).attribute_name, aasm_raw_attribute_value(state, name))
|
|
141
118
|
end
|
|
142
119
|
|
|
143
|
-
def aasm_raw_attribute_value(state)
|
|
144
|
-
if aasm_enum
|
|
145
|
-
self.class.send(aasm_enum)[state]
|
|
120
|
+
def aasm_raw_attribute_value(state, name=:default)
|
|
121
|
+
if aasm_enum(name)
|
|
122
|
+
self.class.send(aasm_enum(name))[state]
|
|
146
123
|
else
|
|
147
124
|
state.to_s
|
|
148
125
|
end
|
|
149
126
|
end
|
|
150
127
|
|
|
151
128
|
# Ensures that if the aasm_state column is nil and the record is new
|
|
152
|
-
#
|
|
129
|
+
# then the initial state gets populated before validation on create
|
|
153
130
|
#
|
|
154
131
|
# foo = Foo.new
|
|
155
132
|
# foo.aasm_state # => nil
|
|
@@ -164,35 +141,48 @@ module AASM
|
|
|
164
141
|
# foo.aasm_state # => nil
|
|
165
142
|
#
|
|
166
143
|
def aasm_ensure_initial_state
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
144
|
+
AASM::StateMachine[self.class].keys.each do |state_machine_name|
|
|
145
|
+
# checking via respond_to? does not work in Rails <= 3
|
|
146
|
+
# if respond_to?(self.class.aasm(state_machine_name).attribute_name) && send(self.class.aasm(state_machine_name).attribute_name).blank? # Rails 4
|
|
147
|
+
if aasm_column_is_blank?(state_machine_name)
|
|
148
|
+
aasm(state_machine_name).enter_initial_state
|
|
149
|
+
end
|
|
171
150
|
end
|
|
172
151
|
end
|
|
173
152
|
|
|
174
|
-
def
|
|
175
|
-
|
|
153
|
+
def aasm_column_is_blank?(state_machine_name)
|
|
154
|
+
attribute_name = self.class.aasm(state_machine_name).attribute_name
|
|
155
|
+
attribute_names.include?(attribute_name.to_s) && send(attribute_name).blank?
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def aasm_fire_event(state_machine_name, name, options, *args, &block)
|
|
159
|
+
success = options[:persist] ? self.class.transaction(:requires_new => requires_new?(state_machine_name)) { super } : super
|
|
176
160
|
|
|
177
161
|
if success && options[:persist]
|
|
178
|
-
event = self.class.aasm.state_machine.events[name]
|
|
179
|
-
event.fire_callbacks(:after_commit, self)
|
|
162
|
+
event = self.class.aasm(state_machine_name).state_machine.events[name]
|
|
163
|
+
event.fire_callbacks(:after_commit, self, *args)
|
|
180
164
|
end
|
|
181
165
|
|
|
182
166
|
success
|
|
183
167
|
end
|
|
184
168
|
|
|
185
|
-
def requires_new?
|
|
186
|
-
AASM::StateMachine[self.class].config.requires_new_transaction
|
|
169
|
+
def requires_new?(state_machine_name)
|
|
170
|
+
AASM::StateMachine[self.class][state_machine_name].config.requires_new_transaction
|
|
187
171
|
end
|
|
188
172
|
|
|
189
173
|
def aasm_validate_states
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
174
|
+
AASM::StateMachine[self.class].keys.each do |state_machine_name|
|
|
175
|
+
unless aasm_skipping_validations(state_machine_name)
|
|
176
|
+
if aasm_invalid_state?(state_machine_name)
|
|
177
|
+
self.errors.add(AASM::StateMachine[self.class][state_machine_name].config.column , "is invalid")
|
|
178
|
+
end
|
|
193
179
|
end
|
|
194
180
|
end
|
|
195
181
|
end
|
|
182
|
+
|
|
183
|
+
def aasm_invalid_state?(state_machine_name)
|
|
184
|
+
aasm(state_machine_name).current_state && !aasm(state_machine_name).states.include?(aasm(state_machine_name).current_state)
|
|
185
|
+
end
|
|
196
186
|
end # InstanceMethods
|
|
197
187
|
|
|
198
188
|
end
|
|
@@ -32,10 +32,10 @@ module AASM
|
|
|
32
32
|
# NOTE: intended to be called from an event
|
|
33
33
|
#
|
|
34
34
|
# This allows for nil aasm states - be sure to add validation to your model
|
|
35
|
-
def aasm_read_state
|
|
36
|
-
state = send(self.class.aasm.attribute_name)
|
|
35
|
+
def aasm_read_state(name=:default)
|
|
36
|
+
state = send(self.class.aasm(name).attribute_name)
|
|
37
37
|
if new_record?
|
|
38
|
-
state.blank? ? aasm.determine_state_name(self.class.aasm.initial_state) : state.to_sym
|
|
38
|
+
state.blank? ? aasm(name).determine_state_name(self.class.aasm(name).initial_state) : state.to_sym
|
|
39
39
|
else
|
|
40
40
|
state.blank? ? nil : state.to_sym
|
|
41
41
|
end
|
|
@@ -55,27 +55,62 @@ module AASM
|
|
|
55
55
|
# make sure to create a (named) scope for each state
|
|
56
56
|
def state_with_scope(name, *args)
|
|
57
57
|
state_without_scope(name, *args)
|
|
58
|
-
if
|
|
59
|
-
|
|
58
|
+
create_scope(name) if create_scope?(name)
|
|
59
|
+
end
|
|
60
|
+
alias_method :state_without_scope, :state
|
|
61
|
+
alias_method :state, :state_with_scope
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def create_scope?(name)
|
|
66
|
+
@state_machine.config.create_scopes && !@klass.respond_to?(name)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def create_scope(name)
|
|
70
|
+
if ancestors_include?("ActiveRecord::Base")
|
|
71
|
+
create_for_active_record(name)
|
|
72
|
+
elsif ancestors_include?("Mongoid::Document")
|
|
73
|
+
create_for_mongoid(name)
|
|
74
|
+
elsif ancestors_include?("MongoMapper::Document")
|
|
75
|
+
create_for_mongomapper(name)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
60
78
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
def ancestors_include?(class_name)
|
|
80
|
+
@klass.ancestors.map { |klass| klass.to_s }.include?(class_name)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def create_for_active_record(name)
|
|
84
|
+
conditions = {
|
|
85
|
+
"#{@klass.table_name}.#{@klass.aasm(@name).attribute_name}" => name.to_s
|
|
86
|
+
}
|
|
87
|
+
if ActiveRecord::VERSION::MAJOR >= 3
|
|
88
|
+
@klass.class_eval do
|
|
89
|
+
scope name, lambda { where(conditions) }
|
|
90
|
+
end
|
|
91
|
+
else
|
|
92
|
+
@klass.class_eval do
|
|
93
|
+
named_scope name, :conditions => conditions
|
|
74
94
|
end
|
|
75
95
|
end
|
|
76
96
|
end
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
|
|
98
|
+
def create_for_mongoid(name)
|
|
99
|
+
klass = @klass
|
|
100
|
+
state_machine_name = @name
|
|
101
|
+
scope_options = lambda {
|
|
102
|
+
klass.send(
|
|
103
|
+
:where,
|
|
104
|
+
{ klass.aasm(state_machine_name).attribute_name.to_sym => name.to_s }
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
@klass.send(:scope, name, scope_options)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def create_for_mongomapper(name)
|
|
111
|
+
conditions = { @klass.aasm(@name).attribute_name.to_sym => name.to_s }
|
|
112
|
+
@klass.scope(name, lambda { @klass.where(conditions) })
|
|
113
|
+
end
|
|
79
114
|
end # Base
|
|
80
115
|
|
|
81
116
|
end # AASM
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module AASM
|
|
4
|
+
module Persistence
|
|
5
|
+
module MongoMapperPersistence
|
|
6
|
+
# This method:
|
|
7
|
+
#
|
|
8
|
+
# * extends the model with ClassMethods
|
|
9
|
+
# * includes InstanceMethods
|
|
10
|
+
#
|
|
11
|
+
# Adds
|
|
12
|
+
#
|
|
13
|
+
# before_validation :aasm_ensure_initial_state, :on => :create
|
|
14
|
+
#
|
|
15
|
+
# As a result, it doesn't matter when you define your methods - the following 2 are equivalent
|
|
16
|
+
#
|
|
17
|
+
# class Foo
|
|
18
|
+
# include MongoMapper::Document
|
|
19
|
+
# def aasm_write_state(state)
|
|
20
|
+
# "bar"
|
|
21
|
+
# end
|
|
22
|
+
# include AASM
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# class Foo < ActiveRecord::Base
|
|
26
|
+
# include MongoMapper::Document
|
|
27
|
+
# include AASM
|
|
28
|
+
# def aasm_write_state(state)
|
|
29
|
+
# "bar"
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
def self.included(base)
|
|
34
|
+
base.send(:include, AASM::Persistence::Base)
|
|
35
|
+
base.send(:include, AASM::Persistence::MongoMapperPersistence::InstanceMethods)
|
|
36
|
+
|
|
37
|
+
base.before_create :aasm_ensure_initial_state
|
|
38
|
+
|
|
39
|
+
# ensure state is in the list of states
|
|
40
|
+
base.validate :aasm_validate_states
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
module InstanceMethods
|
|
44
|
+
|
|
45
|
+
# Writes <tt>state</tt> to the state column and persists it to the database
|
|
46
|
+
#
|
|
47
|
+
# foo = Foo.find(1)
|
|
48
|
+
# foo.aasm.current_state # => :opened
|
|
49
|
+
# foo.close!
|
|
50
|
+
# foo.aasm.current_state # => :closed
|
|
51
|
+
# Foo.find(1).aasm.current_state # => :closed
|
|
52
|
+
#
|
|
53
|
+
# NOTE: intended to be called from an event
|
|
54
|
+
def aasm_write_state(state, name=:default)
|
|
55
|
+
old_value = read_attribute(self.class.aasm(name).attribute_name)
|
|
56
|
+
write_attribute(self.class.aasm(name).attribute_name, state)
|
|
57
|
+
|
|
58
|
+
success = if aasm_skipping_validations(name)
|
|
59
|
+
value = aasm_raw_attribute_value(state, name)
|
|
60
|
+
self.class.where(self.class.primary_key => self.id).update_all(self.class.aasm(name).attribute_name => value) == 1
|
|
61
|
+
else
|
|
62
|
+
self.save
|
|
63
|
+
end
|
|
64
|
+
unless success
|
|
65
|
+
write_attribute(self.class.aasm(name).attribute_name, old_value)
|
|
66
|
+
return false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Writes <tt>state</tt> to the state column, but does not persist it to the database
|
|
73
|
+
#
|
|
74
|
+
# foo = Foo.find(1)
|
|
75
|
+
# foo.aasm.current_state # => :opened
|
|
76
|
+
# foo.close
|
|
77
|
+
# foo.aasm.current_state # => :closed
|
|
78
|
+
# Foo.find(1).aasm.current_state # => :opened
|
|
79
|
+
# foo.save
|
|
80
|
+
# foo.aasm.current_state # => :closed
|
|
81
|
+
# Foo.find(1).aasm.current_state # => :closed
|
|
82
|
+
#
|
|
83
|
+
# NOTE: intended to be called from an event
|
|
84
|
+
def aasm_write_state_without_persistence(state, name=:default)
|
|
85
|
+
aasm_write_attribute(state, name)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
def aasm_enum(name=:default)
|
|
90
|
+
case AASM::StateMachine[self.class][name].config.enum
|
|
91
|
+
when false then nil
|
|
92
|
+
when true then aasm_guess_enum_method(name)
|
|
93
|
+
when nil then aasm_guess_enum_method(name) if aasm_column_looks_like_enum(name)
|
|
94
|
+
else AASM::StateMachine[self.class][name].config.enum
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def aasm_column_looks_like_enum(name)
|
|
99
|
+
self.class.keys[self.class.aasm(name).attribute_name.to_s].type == Integer
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def aasm_guess_enum_method(name)
|
|
103
|
+
self.class.aasm(name).attribute_name.to_s.pluralize.to_sym
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def aasm_skipping_validations(state_machine_name)
|
|
107
|
+
AASM::StateMachine[self.class][state_machine_name].config.skip_validation_on_save
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def aasm_write_attribute(state, name=:default)
|
|
111
|
+
write_attribute self.class.aasm(name).attribute_name, aasm_raw_attribute_value(state, name)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def aasm_raw_attribute_value(state, name=:default)
|
|
115
|
+
if aasm_enum(name)
|
|
116
|
+
self.class.send(aasm_enum(name))[state]
|
|
117
|
+
else
|
|
118
|
+
state.to_s
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Ensures that if the aasm_state column is nil and the record is new
|
|
123
|
+
# that the initial state gets populated before validation on create
|
|
124
|
+
#
|
|
125
|
+
# foo = Foo.new
|
|
126
|
+
# foo.aasm_state # => nil
|
|
127
|
+
# foo.valid?
|
|
128
|
+
# foo.aasm_state # => "open" (where :open is the initial state)
|
|
129
|
+
#
|
|
130
|
+
#
|
|
131
|
+
# foo = Foo.find(:first)
|
|
132
|
+
# foo.aasm_state # => 1
|
|
133
|
+
# foo.aasm_state = nil
|
|
134
|
+
# foo.valid?
|
|
135
|
+
# foo.aasm_state # => nil
|
|
136
|
+
#
|
|
137
|
+
def aasm_ensure_initial_state
|
|
138
|
+
AASM::StateMachine[self.class].keys.each do |state_machine_name|
|
|
139
|
+
send("#{self.class.aasm(state_machine_name).attribute_name}=", aasm(state_machine_name).enter_initial_state.to_s) if send(self.class.aasm(state_machine_name).attribute_name).blank?
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def aasm_validate_states
|
|
144
|
+
AASM::StateMachine[self.class].keys.each do |state_machine_name|
|
|
145
|
+
send("#{self.class.aasm(state_machine_name).attribute_name}=", aasm(state_machine_name).enter_initial_state.to_s) if send(self.class.aasm(state_machine_name).attribute_name).blank?
|
|
146
|
+
unless AASM::StateMachine[self.class][state_machine_name].config.skip_validation_on_save
|
|
147
|
+
if aasm(state_machine_name).current_state && !aasm(state_machine_name).states.include?(aasm(state_machine_name).current_state)
|
|
148
|
+
self.errors.add(AASM::StateMachine[self.class][state_machine_name].config.column , "is invalid")
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end # InstanceMethods
|
|
154
|
+
|
|
155
|
+
end
|
|
156
|
+
end # Persistence
|
|
157
|
+
end # AASM
|
|
@@ -32,36 +32,9 @@ module AASM
|
|
|
32
32
|
#
|
|
33
33
|
def self.included(base)
|
|
34
34
|
base.send(:include, AASM::Persistence::Base)
|
|
35
|
-
base.extend AASM::Persistence::MongoidPersistence::ClassMethods
|
|
36
35
|
base.send(:include, AASM::Persistence::MongoidPersistence::InstanceMethods)
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
# base.before_validation_on_create :aasm_ensure_initial_state
|
|
40
|
-
base.before_validation :aasm_ensure_initial_state
|
|
41
|
-
# ensure initial aasm state even when validations are skipped
|
|
42
|
-
base.before_create :aasm_ensure_initial_state
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
module ClassMethods
|
|
46
|
-
|
|
47
|
-
def find_in_state(number, state, *args)
|
|
48
|
-
with_state_scope state do
|
|
49
|
-
find(number, *args)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def count_in_state(state, *args)
|
|
54
|
-
with_state_scope state do
|
|
55
|
-
count(*args)
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def with_state_scope(state)
|
|
60
|
-
with_scope where(aasm.attribute_name.to_sym => state.to_s) do
|
|
61
|
-
yield if block_given?
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
37
|
+
base.after_initialize :aasm_ensure_initial_state
|
|
65
38
|
end
|
|
66
39
|
|
|
67
40
|
module InstanceMethods
|
|
@@ -76,12 +49,12 @@ module AASM
|
|
|
76
49
|
# Foo.find(1).aasm.current_state # => :closed
|
|
77
50
|
#
|
|
78
51
|
# NOTE: intended to be called from an event
|
|
79
|
-
def aasm_write_state(state)
|
|
80
|
-
old_value = read_attribute(self.class.aasm.attribute_name)
|
|
81
|
-
write_attribute(self.class.aasm.attribute_name, state.to_s)
|
|
52
|
+
def aasm_write_state(state, name=:default)
|
|
53
|
+
old_value = read_attribute(self.class.aasm(name).attribute_name)
|
|
54
|
+
write_attribute(self.class.aasm(name).attribute_name, state.to_s)
|
|
82
55
|
|
|
83
56
|
unless self.save(:validate => false)
|
|
84
|
-
write_attribute(self.class.aasm.attribute_name, old_value)
|
|
57
|
+
write_attribute(self.class.aasm(name).attribute_name, old_value)
|
|
85
58
|
return false
|
|
86
59
|
end
|
|
87
60
|
|
|
@@ -100,8 +73,8 @@ module AASM
|
|
|
100
73
|
# Foo.find(1).aasm.current_state # => :closed
|
|
101
74
|
#
|
|
102
75
|
# NOTE: intended to be called from an event
|
|
103
|
-
def aasm_write_state_without_persistence(state)
|
|
104
|
-
write_attribute(self.class.aasm.attribute_name, state.to_s)
|
|
76
|
+
def aasm_write_state_without_persistence(state, name=:default)
|
|
77
|
+
write_attribute(self.class.aasm(name).attribute_name, state.to_s)
|
|
105
78
|
end
|
|
106
79
|
|
|
107
80
|
private
|
|
@@ -122,16 +95,18 @@ module AASM
|
|
|
122
95
|
# foo.aasm_state # => nil
|
|
123
96
|
#
|
|
124
97
|
def aasm_ensure_initial_state
|
|
125
|
-
|
|
98
|
+
AASM::StateMachine[self.class].keys.each do |state_machine_name|
|
|
99
|
+
send("#{self.class.aasm(state_machine_name).attribute_name}=", aasm(state_machine_name).enter_initial_state.to_s) if send(self.class.aasm(state_machine_name).attribute_name).blank?
|
|
100
|
+
end
|
|
126
101
|
end
|
|
127
102
|
end # InstanceMethods
|
|
128
103
|
|
|
129
|
-
module NamedScopeMethods
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
end
|
|
104
|
+
# module NamedScopeMethods
|
|
105
|
+
# def aasm_state_with_named_scope name, options = {}
|
|
106
|
+
# aasm_state_without_named_scope name, options
|
|
107
|
+
# self.named_scope name, :conditions => { "#{table_name}.#{self.aasm.attribute_name}" => name.to_s} unless self.respond_to?(name)
|
|
108
|
+
# end
|
|
109
|
+
# end
|
|
135
110
|
end
|
|
136
111
|
end # Persistence
|
|
137
112
|
end # AASM
|
|
@@ -2,21 +2,22 @@ module AASM
|
|
|
2
2
|
module Persistence
|
|
3
3
|
module PlainPersistence
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
# may be overwritten by persistence mixins
|
|
6
|
+
def aasm_read_state(name=:default)
|
|
7
|
+
# all the following lines behave like @current_state ||= aasm(name).enter_initial_state
|
|
8
|
+
current = aasm(name).instance_variable_get("@current_state_#{name}")
|
|
8
9
|
return current if current
|
|
9
|
-
aasm.instance_variable_set("@
|
|
10
|
+
aasm(name).instance_variable_set("@current_state_#{name}", aasm(name).enter_initial_state)
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
# may be overwritten by persistence mixins
|
|
13
|
-
def aasm_write_state(new_state)
|
|
14
|
+
def aasm_write_state(new_state, name=:default)
|
|
14
15
|
true
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
# may be overwritten by persistence mixins
|
|
18
|
-
def aasm_write_state_without_persistence(new_state)
|
|
19
|
-
|
|
19
|
+
def aasm_write_state_without_persistence(new_state, name=:default)
|
|
20
|
+
aasm(name).instance_variable_set("@current_state_#{name}", new_state)
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
end
|
|
@@ -44,10 +44,10 @@ module AASM
|
|
|
44
44
|
# NOTE: intended to be called from an event
|
|
45
45
|
#
|
|
46
46
|
# This allows for nil aasm states - be sure to add validation to your model
|
|
47
|
-
def aasm_read_state
|
|
48
|
-
state = send(self.class.aasm.attribute_name)
|
|
47
|
+
def aasm_read_state(name=:default)
|
|
48
|
+
state = send(self.class.aasm(name).attribute_name)
|
|
49
49
|
if new? && state.to_s.strip.empty?
|
|
50
|
-
aasm.determine_state_name(self.class.aasm.initial_state)
|
|
50
|
+
aasm(name).determine_state_name(self.class.aasm(name).initial_state)
|
|
51
51
|
elsif state.nil?
|
|
52
52
|
nil
|
|
53
53
|
else
|
|
@@ -71,8 +71,11 @@ module AASM
|
|
|
71
71
|
# foo.aasm_state # => nil
|
|
72
72
|
#
|
|
73
73
|
def aasm_ensure_initial_state
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
AASM::StateMachine[self.class].keys.each do |state_machine_name|
|
|
75
|
+
aasm(state_machine_name).enter_initial_state if
|
|
76
|
+
(new? || values.key?(self.class.aasm(state_machine_name).attribute_name)) &&
|
|
77
|
+
send(self.class.aasm(state_machine_name).attribute_name).to_s.strip.empty?
|
|
78
|
+
end
|
|
76
79
|
end
|
|
77
80
|
|
|
78
81
|
# Writes <tt>state</tt> to the state column and persists it to the database
|
|
@@ -84,8 +87,8 @@ module AASM
|
|
|
84
87
|
# Foo[1].aasm.current_state # => :closed
|
|
85
88
|
#
|
|
86
89
|
# NOTE: intended to be called from an event
|
|
87
|
-
def aasm_write_state state
|
|
88
|
-
aasm_column = self.class.aasm.attribute_name
|
|
90
|
+
def aasm_write_state state, name=:default
|
|
91
|
+
aasm_column = self.class.aasm(name).attribute_name
|
|
89
92
|
update_only({aasm_column => state.to_s}, aasm_column)
|
|
90
93
|
end
|
|
91
94
|
|
|
@@ -101,8 +104,8 @@ module AASM
|
|
|
101
104
|
# Foo[1].aasm.current_state # => :closed
|
|
102
105
|
#
|
|
103
106
|
# NOTE: intended to be called from an event
|
|
104
|
-
def aasm_write_state_without_persistence state
|
|
105
|
-
send("#{self.class.aasm.attribute_name}=", state.to_s)
|
|
107
|
+
def aasm_write_state_without_persistence state, name=:default
|
|
108
|
+
send("#{self.class.aasm(name).attribute_name}=", state.to_s)
|
|
106
109
|
end
|
|
107
110
|
end
|
|
108
111
|
end
|
data/lib/aasm/persistence.rb
CHANGED
|
@@ -10,6 +10,8 @@ module AASM
|
|
|
10
10
|
include_persistence base, :active_record
|
|
11
11
|
elsif hierarchy.include?("Mongoid::Document")
|
|
12
12
|
include_persistence base, :mongoid
|
|
13
|
+
elsif hierarchy.include?("MongoMapper::Document")
|
|
14
|
+
include_persistence base, :mongo_mapper
|
|
13
15
|
elsif hierarchy.include?("Sequel::Model")
|
|
14
16
|
include_persistence base, :sequel
|
|
15
17
|
else
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
RSpec::Matchers.define :allow_event do |event|
|
|
2
|
+
match do |obj|
|
|
3
|
+
@state_machine_name ||= :default
|
|
4
|
+
obj.aasm(@state_machine_name).may_fire_event?(event)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
chain :on do |state_machine_name|
|
|
8
|
+
@state_machine_name = state_machine_name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
description do
|
|
12
|
+
"allow event #{expected} (on :#{@state_machine_name})"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
failure_message do |obj|
|
|
16
|
+
"expected that the event :#{expected} would be allowed (on :#{@state_machine_name})"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
failure_message_when_negated do |obj|
|
|
20
|
+
"expected that the event :#{expected} would not be allowed (on :#{@state_machine_name})"
|
|
21
|
+
end
|
|
22
|
+
end
|