1
0
Fork 0
mirror of https://github.com/aasm/aasm synced 2023-03-27 23:22:41 -04:00

Merge remote-tracking branch 'upstream/master'

Conflicts:
	lib/aasm/instance_base.rb
This commit is contained in:
Tsyren Ochirov 2015-09-11 16:08:32 +02:00
commit 5e59c5cba5
95 changed files with 3761 additions and 547 deletions

View file

@ -1,6 +1,9 @@
# CHANGELOG
## 4.3.0 (not yet released)
## 4.3.0
* add support for multiple state machines per class (see [issue #158](https://github.com/aasm/aasm/issues/158) and [issue #240](https://github.com/aasm/aasm/issues/240) for details)
* special thanks to [@evadne](https://github.com/evadne) for testing this feature, and providing comments and patches (see [issue #245](https://github.com/aasm/aasm/issues/245) for details)
## 4.2.0

View file

@ -9,7 +9,7 @@ gem 'mongoid', '~>4.0' if Gem::Version.create(RUBY_VERSION.dup) >= Gem::Version.
gem 'sequel'
# Since mongoid V4 requires incompatible bson V2, cannot have mongoid (V4 or greater)
# and mongo_mapper ( or mongo ) in the same application
#gem 'mongo_mapper', '~> 0.13'
#gem 'bson_ext', :platforms => :ruby
# gem 'mongo_mapper', '~> 0.13'
# gem 'bson_ext', :platforms => :ruby
gemspec

View file

@ -1,7 +1,27 @@
# Planned changes for AASM 4.1
# Planned changes
* remove support for `:on_transition` callback
## later
# Planned changes for AASM 4.0
* drop support for aasm_column ?
* nothing left
# Currently working on
# Changes so far
## version 4.3
* add support for multiple state machines per class
* class- and instance-level `aasm` methods accept a state machine selector
(aka the state machine _name_)
* if no selector/name is provided, `:default` will be used
* duplicate definitions of states and events will issue warnings
* check all tests
* _ActiveRecord_
* _Mongoid_
* _MongoMapper_
* _Sequel_
* what happen's if someone accesses `aasm`, but has defined a
state machine for `aasm(:my_name)`?
* documentation
* drop support for find_in_state, count_in_state, calculate_in_state, with_state_scope

View file

@ -312,6 +312,76 @@ job.stage1_completed
job.aasm.current_state # stage3
```
### Multiple state machines per class
Multiple state machines per class are supported. Be aware though that _AASM_ has been
built with one state machine per class in mind. Nonetheless, here's how to do it:
```ruby
class SimpleMultipleExample
include AASM
aasm(:move) do
state :standing, :initial => true
state :walking
state :running
event :walk do
transitions :from => :standing, :to => :walking
end
event :run do
transitions :from => [:standing, :walking], :to => :running
end
event :hold do
transitions :from => [:walking, :running], :to => :standing
end
end
aasm(:work) do
state :sleeping, :initial => true
state :processing
event :start do
transitions :from => :sleeping, :to => :processing
end
event :stop do
transitions :from => :processing, :to => :sleeping
end
end
end
simple = SimpleMultipleExample.new
simple.aasm(:move).current_state
# => :standing
simple.aasm(:work).current
# => :sleeping
simple.start
simple.aasm(:move).current_state
# => :standing
simple.aasm(:work).current
# => :processing
```
_AASM_ doesn't prohibit to define the same event in both state machines. The
latest definition "wins" and overrides previous definitions. A warning is issued:
`SimpleMultipleExample: The event name run is already used!`.
All _AASM_ class- and instance-level `aasm` methods accept a state machine selector.
So, for example, to use inspection on a class level, you have to use
```ruby
SimpleMultipleExample.aasm(:work).states
# => [:standing, :walking, :running]
```
*Final note*: Support for multiple state machines per class is a pretty new feature
(since version `4.3`), so please bear with us in case it doesn't as expected.
### ActiveRecord
AASM comes with support for ActiveRecord and allows automatic persisting of the object's
@ -347,7 +417,7 @@ job.run! # saved
Saving includes running all validations on the `Job` class. If you want make sure
the state gets saved without running validations (and thereby maybe persisting an
invalid object state), simply tell AASM to skip the validations. Be aware, that
invalid object state), simply tell AASM to skip the validations. Be aware that
when skipping validations, only the state column will be updated in the database
(just like ActiveRecord `change_column` is working).

View file

@ -8,36 +8,50 @@ module AASM
# do not overwrite existing state machines, which could have been created by
# inheritance, see class method inherited
AASM::StateMachine[base] ||= AASM::StateMachine.new
AASM::StateMachine[base] ||= {}
AASM::Persistence.load_persistence(base)
super
end
module ClassMethods
# make sure inheritance (aka subclassing) works with AASM
def inherited(base)
AASM::StateMachine[base] = AASM::StateMachine[self].clone
AASM::StateMachine[base] = {}
AASM::StateMachine[self].keys.each do |state_machine_name|
AASM::StateMachine[base][state_machine_name] = AASM::StateMachine[self][state_machine_name].clone
end
super
end
# this is the entry point for all state and event definitions
def aasm(options={}, &block)
@aasm ||= AASM::Base.new(self, options)
@aasm.instance_eval(&block) if block # new DSL
@aasm
end
def aasm(*args, &block)
if args[0].is_a?(Symbol) || args[0].is_a?(String)
# using custom name
state_machine_name = args[0].to_sym
options = args[1] || {}
else
# using the default state_machine_name
state_machine_name = :default
options = args[0] || {}
end
# deprecated, remove in version 4.1
def aasm_human_event_name(event) # event_name?
warn '[DEPRECATION] AASM: aasm_human_event_name is deprecated, use aasm.human_event_name instead'
aasm.human_event_name(event)
AASM::StateMachine[self][state_machine_name] ||= AASM::StateMachine.new(state_machine_name)
@aasm ||= {}
@aasm[state_machine_name] ||= AASM::Base.new(self, state_machine_name, AASM::StateMachine[self][state_machine_name], options)
@aasm[state_machine_name].instance_eval(&block) if block # new DSL
@aasm[state_machine_name]
end
end # ClassMethods
def aasm
@aasm ||= AASM::InstanceBase.new(self)
# this is the entry point for all instance-level access to AASM
def aasm(name=:default)
unless AASM::StateMachine[self.class][name.to_sym]
raise AASM::UnknownStateMachineError.new("There is no state machine with the name '#{name}' defined in #{self.class.name}!")
end
@aasm ||= {}
@aasm[name.to_sym] ||= AASM::InstanceBase.new(self, name.to_sym)
end
private
@ -56,72 +70,72 @@ private
return args
end
def aasm_fire_event(event_name, options, *args, &block)
event = self.class.aasm.state_machine.events[event_name]
def aasm_fire_event(state_machine_name, event_name, options, *args, &block)
event = self.class.aasm(state_machine_name).state_machine.events[event_name]
begin
old_state = aasm.state_object_for_name(aasm.current_state)
old_state = aasm(state_machine_name).state_object_for_name(aasm(state_machine_name).current_state)
# new event before callback
event.fire_callbacks(
:before,
self,
*process_args(event, aasm.current_state, *args)
*process_args(event, aasm(state_machine_name).current_state, *args)
)
if may_fire_to = event.may_fire?(self, *args)
old_state.fire_callbacks(:before_exit, self,
*process_args(event, aasm.current_state, *args))
*process_args(event, aasm(state_machine_name).current_state, *args))
old_state.fire_callbacks(:exit, self,
*process_args(event, aasm.current_state, *args)) # TODO: remove for AASM 4?
*process_args(event, aasm(state_machine_name).current_state, *args))
if new_state_name = event.fire(self, {:may_fire => may_fire_to}, *args)
aasm_fired(event, old_state, new_state_name, options, *args, &block)
aasm_fired(state_machine_name, event, old_state, new_state_name, options, *args, &block)
else
aasm_failed(event_name, old_state)
aasm_failed(state_machine_name, event_name, old_state)
end
else
aasm_failed(event_name, old_state)
aasm_failed(state_machine_name, event_name, old_state)
end
rescue StandardError => e
event.fire_callbacks(:error, self, e, *process_args(event, aasm.current_state, *args)) || raise(e)
event.fire_callbacks(:error, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) || raise(e)
end
end
def aasm_fired(event, old_state, new_state_name, options, *args)
def aasm_fired(state_machine_name, event, old_state, new_state_name, options, *args)
persist = options[:persist]
new_state = aasm.state_object_for_name(new_state_name)
new_state = aasm(state_machine_name).state_object_for_name(new_state_name)
new_state.fire_callbacks(:before_enter, self,
*process_args(event, aasm.current_state, *args))
*process_args(event, aasm(state_machine_name).current_state, *args))
new_state.fire_callbacks(:enter, self,
*process_args(event, aasm.current_state, *args)) # TODO: remove for AASM 4?
*process_args(event, aasm(state_machine_name).current_state, *args)) # TODO: remove for AASM 4?
persist_successful = true
if persist
persist_successful = aasm.set_current_state_with_persistence(new_state_name)
persist_successful = aasm(state_machine_name).set_current_state_with_persistence(new_state_name)
if persist_successful
yield if block_given?
event.fire_callbacks(:success, self)
end
else
aasm.current_state = new_state_name
aasm(state_machine_name).current_state = new_state_name
yield if block_given?
end
if persist_successful
old_state.fire_callbacks(:after_exit, self,
*process_args(event, aasm.current_state, *args))
*process_args(event, aasm(state_machine_name).current_state, *args))
new_state.fire_callbacks(:after_enter, self,
*process_args(event, aasm.current_state, *args))
*process_args(event, aasm(state_machine_name).current_state, *args))
event.fire_callbacks(
:after,
self,
*process_args(event, old_state.name, *args)
)
self.aasm_event_fired(event.name, old_state.name, aasm.current_state) if self.respond_to?(:aasm_event_fired)
self.aasm_event_fired(event.name, old_state.name, aasm(state_machine_name).current_state) if self.respond_to?(:aasm_event_fired)
else
self.aasm_event_failed(event.name, old_state.name) if self.respond_to?(:aasm_event_failed)
end
@ -129,13 +143,13 @@ private
persist_successful
end
def aasm_failed(event_name, old_state)
def aasm_failed(state_machine_name, event_name, old_state)
if self.respond_to?(:aasm_event_failed)
self.aasm_event_failed(event_name, old_state.name)
end
if AASM::StateMachine[self.class].config.whiny_transitions
raise AASM::InvalidTransition.new(self, event_name)
if AASM::StateMachine[self.class][state_machine_name].config.whiny_transitions
raise AASM::InvalidTransition.new(self, event_name, state_machine_name)
else
false
end

View file

@ -3,10 +3,12 @@ module AASM
attr_reader :state_machine
def initialize(klass, options={}, &block)
def initialize(klass, name, state_machine, options={}, &block)
@klass = klass
@state_machine = AASM::StateMachine[@klass]
@state_machine.config.column ||= (options[:column] || :aasm_state).to_sym # aasm4
@name = name
# @state_machine = @klass.aasm(@name).state_machine
@state_machine = state_machine
@state_machine.config.column ||= (options[:column] || default_column).to_sym
# @state_machine.config.column = options[:column].to_sym if options[:column] # master
@options = options
@ -31,7 +33,7 @@ module AASM
# and attribute is directly assigned though
@klass.class_eval %Q(
def #{@state_machine.config.column}=(state_name)
if self.class.aasm.state_machine.config.no_direct_assignment
if self.class.aasm(:#{@name}).state_machine.config.no_direct_assignment
raise AASM::NoDirectAssignmentError.new(
'direct assignment of AASM column has been disabled (see AASM configuration for this class)'
)
@ -64,10 +66,16 @@ module AASM
def state(name, options={})
@state_machine.add_state(name, @klass, options)
@klass.send(:define_method, "#{name}?") do
aasm.current_state == name
if @klass.instance_methods.include?("#{name}?")
warn "#{@klass.name}: The state name #{name} is already used!"
end
@klass.class_eval <<-EORUBY, __FILE__, __LINE__ + 1
def #{name}?
aasm(:#{@name}).current_state == :#{name}
end
EORUBY
unless @klass.const_defined?("STATE_#{name.upcase}")
@klass.const_set("STATE_#{name.upcase}", name)
end
@ -75,24 +83,30 @@ module AASM
# define an event
def event(name, options={}, &block)
@state_machine.events[name] = AASM::Core::Event.new(name, options, &block)
@state_machine.add_event(name, options, &block)
if @klass.instance_methods.include?("may_#{name}?".to_sym)
warn "#{@klass.name}: The event name #{name} is already used!"
end
# an addition over standard aasm so that, before firing an event, you can ask
# may_event? and get back a boolean that tells you whether the guard method
# on the transition will let this happen.
@klass.send(:define_method, "may_#{name}?") do |*args|
aasm.may_fire_event?(name, *args)
end
@klass.class_eval <<-EORUBY, __FILE__, __LINE__ + 1
def may_#{name}?(*args)
aasm(:#{@name}).may_fire_event?(:#{name}, *args)
end
@klass.send(:define_method, "#{name}!") do |*args, &block|
aasm.current_event = "#{name}!".to_sym
aasm_fire_event(name, {:persist => true}, *args, &block)
end
def #{name}!(*args, &block)
aasm(:#{@name}).current_event = :#{name}!
aasm_fire_event(:#{@name}, :#{name}, {:persist => true}, *args, &block)
end
@klass.send(:define_method, "#{name}") do |*args, &block|
aasm.current_event = name.to_sym
aasm_fire_event(name, {:persist => false}, *args, &block)
end
def #{name}(*args, &block)
aasm(:#{@name}).current_event = :#{name}
aasm_fire_event(:#{@name}, :#{name}, {:persist => false}, *args, &block)
end
EORUBY
end
def states
@ -122,6 +136,10 @@ module AASM
private
def default_column
@name.to_sym == :default ? :aasm_state : @name.to_sym
end
def configure(key, default_value)
if @options.key?(key)
@state_machine.config.send("#{key}=", @options[key])

View file

@ -2,10 +2,11 @@ module AASM::Core
class Event
include DslHelper
attr_reader :name, :options
attr_reader :name, :state_machine, :options
def initialize(name, options = {}, &block)
def initialize(name, state_machine, options = {}, &block)
@name = name
@state_machine = state_machine
@transitions = []
@guards = Array(options[:guard] || options[:guards] || options[:if])
@unless = Array(options[:unless]) #TODO: This could use a better name
@ -61,11 +62,11 @@ module AASM::Core
if definitions # define new transitions
# Create a separate transition for each from-state to the given state
Array(definitions[:from]).each do |s|
@transitions << AASM::Core::Transition.new(attach_event_guards(definitions.merge(:from => s.to_sym)), &block)
@transitions << AASM::Core::Transition.new(self, attach_event_guards(definitions.merge(:from => s.to_sym)), &block)
end
# Create a transition if :to is specified without :from (transitions from ANY state)
if @transitions.empty? && definitions[:to]
@transitions << AASM::Core::Transition.new(attach_event_guards(definitions), &block)
@transitions << AASM::Core::Transition.new(self, attach_event_guards(definitions), &block)
end
end
@transitions
@ -89,7 +90,7 @@ module AASM::Core
def _fire(obj, options={}, to_state=nil, *args)
result = options[:test_only] ? false : nil
if @transitions.map(&:from).any?
transitions = @transitions.select { |t| t.from == obj.aasm.current_state }
transitions = @transitions.select { |t| t.from == obj.aasm(state_machine.name).current_state }
return result if transitions.size == 0
else
transitions = @transitions

View file

@ -1,10 +1,11 @@
module AASM::Core
class State
attr_reader :name, :options
attr_reader :name, :state_machine, :options
def initialize(name, klass, options={})
def initialize(name, klass, state_machine, options={})
@name = name
@klass = klass
@state_machine = state_machine
update(options)
end

View file

@ -2,12 +2,13 @@ module AASM::Core
class Transition
include DslHelper
attr_reader :from, :to, :opts
attr_reader :from, :to, :event, :opts
alias_method :options, :opts
def initialize(opts, &block)
def initialize(event, opts, &block)
add_options_from_dsl(opts, [:on_transition, :guard, :after], &block) if block
@event = event
@from = opts[:from]
@to = opts[:to]
@guards = Array(opts[:guards]) + Array(opts[:guard]) + Array(opts[:if])
@ -44,8 +45,8 @@ module AASM::Core
def invoke_callbacks_compatible_with_guard(code, record, args, options={})
if record.respond_to?(:aasm)
record.aasm.from_state = @from if record.aasm.respond_to?(:from_state=)
record.aasm.to_state = @to if record.aasm.respond_to?(:to_state=)
record.aasm(event.state_machine.name).from_state = @from if record.aasm(event.state_machine.name).respond_to?(:from_state=)
record.aasm(event.state_machine.name).to_state = @to if record.aasm(event.state_machine.name).respond_to?(:to_state=)
end
case code

View file

@ -1,13 +1,16 @@
module AASM
class UnknownStateMachineError < RuntimeError; end
class InvalidTransition < RuntimeError
attr_reader :object, :event_name
def initialize(object, event_name)
@object, @event_name = object, event_name
attr_reader :object, :event_name, :state_machine_name
def initialize(object, event_name, state_machine_name)
@object, @event_name, @state_machine_name = object, event_name, state_machine_name
end
def message
"Event '#{event_name}' cannot transition from '#{object.aasm.current_state}'"
"Event '#{event_name}' cannot transition from '#{object.aasm(state_machine_name).current_state}'"
end
end

View file

@ -3,21 +3,22 @@ module AASM
attr_accessor :from_state, :to_state, :current_event
def initialize(instance)
def initialize(instance, name=:default) # instance of the class including AASM, name of the state machine
@instance = instance
@name = name
end
def current_state
@instance.aasm_read_state
@instance.aasm_read_state(@name)
end
def current_state=(state)
@instance.aasm_write_state_without_persistence(state)
@current_state = state
@instance.aasm_write_state_without_persistence(state, @name)
# @current_state = state
end
def enter_initial_state
state_name = determine_state_name(@instance.class.aasm.initial_state)
state_name = determine_state_name(@instance.class.aasm(@name).initial_state)
state_object = state_object_for_name(state_name)
state_object.fire_callbacks(:before_enter, @instance)
@ -29,24 +30,24 @@ module AASM
end
def human_state
AASM::Localizer.new.human_state_name(@instance.class, current_state)
AASM::Localizer.new.human_state_name(@instance.class, state_object_for_name(current_state))
end
def states(options={})
if options[:permitted]
# ugliness level 1000
permitted_event_names = events(:permitted => true).map(&:name)
transitions = @instance.class.aasm.state_machine.events.values_at(*permitted_event_names).compact.map {|e| e.transitions_from_state(current_state) }
transitions = @instance.class.aasm(@name).state_machine.events.values_at(*permitted_event_names).compact.map {|e| e.transitions_from_state(current_state) }
tos = transitions.map {|t| t[0] ? t[0].to : nil}.flatten.compact.map(&:to_sym).uniq
@instance.class.aasm.states.select {|s| tos.include?(s.name.to_sym)}
@instance.class.aasm(@name).states.select {|s| tos.include?(s.name.to_sym)}
else
@instance.class.aasm.states
@instance.class.aasm(@name).states
end
end
def events(options={})
state = options[:state] || current_state
events = @instance.class.aasm.events.select {|e| e.transitions_from_state?(state) }
events = @instance.class.aasm(@name).events.select {|e| e.transitions_from_state?(state) }
if options[:permitted]
# filters the results of events_for_current_state so that only those that
@ -58,7 +59,7 @@ module AASM
end
def state_object_for_name(name)
obj = @instance.class.aasm.states.find {|s| s.name == name}
obj = @instance.class.aasm(@name).states.find {|s| s.name == name}
raise AASM::UndefinedState, "State :#{name} doesn't exist" if obj.nil?
obj
end
@ -75,7 +76,7 @@ module AASM
end
def may_fire_event?(name, *args)
if event = @instance.class.aasm.state_machine.events[name]
if event = @instance.class.aasm(@name).state_machine.events[name]
!!event.may_fire?(@instance, *args)
else
false # unknown event
@ -83,7 +84,7 @@ module AASM
end
def set_current_state_with_persistence(state)
save_success = @instance.aasm_write_state(state)
save_success = @instance.aasm_write_state(state, @name)
self.current_state = state if save_success
save_success
end

View file

@ -21,7 +21,7 @@ module AASM
def item_for(klass, state, ancestor, options={})
separator = options[:old_style] ? '.' : '/'
:"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm.attribute_name}#{separator}#{state}"
:"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm(state.state_machine.name).attribute_name}#{separator}#{state}"
end
def translate_queue(checklist)

View file

@ -30,7 +30,6 @@ module AASM
#
def self.included(base)
base.send(:include, AASM::Persistence::Base)
base.extend AASM::Persistence::ActiveRecordPersistence::ClassMethods
base.send(:include, AASM::Persistence::ActiveRecordPersistence::InstanceMethods)
base.after_initialize do
@ -41,34 +40,6 @@ module AASM
base.validate :aasm_validate_states
end
module ClassMethods
def find_in_state(number, state, *args)
with_state_scope state do
find(number, *args)
end
end
def count_in_state(state, *args)
with_state_scope state do
count(*args)
end
end
def calculate_in_state(state, *args)
with_state_scope state do
calculate(*args)
end
end
protected
def with_state_scope(state)
with_scope :find => {:conditions => ["#{table_name}.#{aasm_column} = ?", state.to_s]} do
yield if block_given?
end
end
end
module InstanceMethods
# Writes <tt>state</tt> to the state column and persists it to the database
@ -80,18 +51,18 @@ module AASM
# Foo.find(1).aasm.current_state # => :closed
#
# NOTE: intended to be called from an event
def aasm_write_state(state)
old_value = read_attribute(self.class.aasm.attribute_name)
aasm_write_attribute state
def aasm_write_state(state, name=:default)
old_value = read_attribute(self.class.aasm(name).attribute_name)
aasm_write_attribute state, name
success = if aasm_skipping_validations
value = aasm_raw_attribute_value state
self.class.where(self.class.primary_key => self.id).update_all(self.class.aasm.attribute_name => value) == 1
success = if aasm_skipping_validations(name)
value = aasm_raw_attribute_value(state, name)
self.class.where(self.class.primary_key => self.id).update_all(self.class.aasm(name).attribute_name => value) == 1
else
self.save
end
unless success
write_attribute(self.class.aasm.attribute_name, old_value)
write_attribute(self.class.aasm(name).attribute_name, old_value)
return false
end
@ -110,39 +81,39 @@ module AASM
# Foo.find(1).aasm.current_state # => :closed
#
# NOTE: intended to be called from an event
def aasm_write_state_without_persistence(state)
aasm_write_attribute state
def aasm_write_state_without_persistence(state, name=:default)
aasm_write_attribute(state, name)
end
private
def aasm_enum
case AASM::StateMachine[self.class].config.enum
def aasm_enum(name=:default)
case AASM::StateMachine[self.class][name].config.enum
when false then nil
when true then aasm_guess_enum_method
when nil then aasm_guess_enum_method if aasm_column_looks_like_enum
else AASM::StateMachine[self.class].config.enum
when true then aasm_guess_enum_method(name)
when nil then aasm_guess_enum_method(name) if aasm_column_looks_like_enum(name)
else AASM::StateMachine[self.class][name].config.enum
end
end
def aasm_column_looks_like_enum
self.class.columns_hash[self.class.aasm.attribute_name.to_s].type == :integer
def aasm_column_looks_like_enum(name=:default)
self.class.columns_hash[self.class.aasm(name).attribute_name.to_s].type == :integer
end
def aasm_guess_enum_method
self.class.aasm.attribute_name.to_s.pluralize.to_sym
def aasm_guess_enum_method(name=:default)
self.class.aasm(name).attribute_name.to_s.pluralize.to_sym
end
def aasm_skipping_validations
AASM::StateMachine[self.class].config.skip_validation_on_save
def aasm_skipping_validations(state_machine_name)
AASM::StateMachine[self.class][state_machine_name].config.skip_validation_on_save
end
def aasm_write_attribute(state)
write_attribute self.class.aasm.attribute_name, aasm_raw_attribute_value(state)
def aasm_write_attribute(state, name=:default)
write_attribute(self.class.aasm(name).attribute_name, aasm_raw_attribute_value(state, name))
end
def aasm_raw_attribute_value(state)
if aasm_enum
self.class.send(aasm_enum)[state]
def aasm_raw_attribute_value(state, name=:default)
if aasm_enum(name)
self.class.send(aasm_enum(name))[state]
else
state.to_s
end
@ -164,32 +135,36 @@ module AASM
# foo.aasm_state # => nil
#
def aasm_ensure_initial_state
# checking via respond_to? does not work in Rails <= 3
# if respond_to?(self.class.aasm.attribute_name) && send(self.class.aasm.attribute_name).blank? # Rails 4
if attribute_names.include?(self.class.aasm.attribute_name.to_s) && send(self.class.aasm.attribute_name).blank?
aasm.enter_initial_state
AASM::StateMachine[self.class].keys.each do |state_machine_name|
# checking via respond_to? does not work in Rails <= 3
# if respond_to?(self.class.aasm(state_machine_name).attribute_name) && send(self.class.aasm(state_machine_name).attribute_name).blank? # Rails 4
if attribute_names.include?(self.class.aasm(state_machine_name).attribute_name.to_s) && send(self.class.aasm(state_machine_name).attribute_name).blank?
aasm(state_machine_name).enter_initial_state
end
end
end
def aasm_fire_event(name, options, *args, &block)
success = options[:persist] ? self.class.transaction(:requires_new => requires_new?) { super } : super
def aasm_fire_event(state_machine_name, name, options, *args, &block)
success = options[:persist] ? self.class.transaction(:requires_new => requires_new?(state_machine_name)) { super } : super
if success && options[:persist]
event = self.class.aasm.state_machine.events[name]
event = self.class.aasm(state_machine_name).state_machine.events[name]
event.fire_callbacks(:after_commit, self, *args)
end
success
end
def requires_new?
AASM::StateMachine[self.class].config.requires_new_transaction
def requires_new?(state_machine_name)
AASM::StateMachine[self.class][state_machine_name].config.requires_new_transaction
end
def aasm_validate_states
unless aasm_skipping_validations
if aasm.current_state && !aasm.states.include?(aasm.current_state)
self.errors.add(AASM::StateMachine[self.class].config.column , "is invalid")
AASM::StateMachine[self.class].keys.each do |state_machine_name|
unless aasm_skipping_validations(state_machine_name)
if aasm(state_machine_name).current_state && !aasm(state_machine_name).states.include?(aasm(state_machine_name).current_state)
self.errors.add(AASM::StateMachine[self.class][state_machine_name].config.column , "is invalid")
end
end
end
end

View file

@ -32,10 +32,10 @@ module AASM
# NOTE: intended to be called from an event
#
# This allows for nil aasm states - be sure to add validation to your model
def aasm_read_state
state = send(self.class.aasm.attribute_name)
def aasm_read_state(name=:default)
state = send(self.class.aasm(name).attribute_name)
if new_record?
state.blank? ? aasm.determine_state_name(self.class.aasm.initial_state) : state.to_sym
state.blank? ? aasm(name).determine_state_name(self.class.aasm(name).initial_state) : state.to_sym
else
state.blank? ? nil : state.to_sym
end
@ -55,10 +55,10 @@ module AASM
# make sure to create a (named) scope for each state
def state_with_scope(name, *args)
state_without_scope(name, *args)
if AASM::StateMachine[@klass].config.create_scopes && !@klass.respond_to?(name)
if @state_machine.config.create_scopes && !@klass.respond_to?(name)
if @klass.ancestors.map {|klass| klass.to_s}.include?("ActiveRecord::Base")
conditions = {"#{@klass.table_name}.#{@klass.aasm.attribute_name}" => name.to_s}
conditions = {"#{@klass.table_name}.#{@klass.aasm(@name).attribute_name}" => name.to_s}
if ActiveRecord::VERSION::MAJOR >= 3
@klass.class_eval do
scope name, lambda { where(conditions) }
@ -69,10 +69,10 @@ module AASM
end
end
elsif @klass.ancestors.map {|klass| klass.to_s}.include?("Mongoid::Document")
scope_options = lambda { @klass.send(:where, {@klass.aasm.attribute_name.to_sym => name.to_s}) }
scope_options = lambda { @klass.send(:where, {@klass.aasm(@name).attribute_name.to_sym => name.to_s}) }
@klass.send(:scope, name, scope_options)
elsif @klass.ancestors.map {|klass| klass.to_s}.include?("MongoMapper::Document")
conditions = { @klass.aasm.attribute_name.to_sym => name.to_s }
conditions = { @klass.aasm(@name).attribute_name.to_sym => name.to_s }
@klass.scope(name, lambda { @klass.where(conditions) })
end
end

View file

@ -32,7 +32,6 @@ module AASM
#
def self.included(base)
base.send(:include, AASM::Persistence::Base)
base.extend AASM::Persistence::MongoMapperPersistence::ClassMethods
base.send(:include, AASM::Persistence::MongoMapperPersistence::InstanceMethods)
base.before_create :aasm_ensure_initial_state
@ -41,26 +40,6 @@ module AASM
base.validate :aasm_validate_states
end
module ClassMethods
def find_in_state(number, state, *args)
with_state_scope(state).find!(number, *args)
end
def count_in_state(state, *args)
with_state_scope(state).count(*args)
end
def calculate_in_state(state, *args)
with_state_scope(state).calculate(*args)
end
protected
def with_state_scope(state)
where(aasm.attribute_name.to_sym => state.to_s)
end
end
module InstanceMethods
# Writes <tt>state</tt> to the state column and persists it to the database
@ -72,18 +51,18 @@ module AASM
# Foo.find(1).aasm.current_state # => :closed
#
# NOTE: intended to be called from an event
def aasm_write_state(state)
old_value = read_attribute(self.class.aasm.attribute_name)
aasm_write_attribute state
def aasm_write_state(state, name=:default)
old_value = read_attribute(self.class.aasm(name).attribute_name)
write_attribute(self.class.aasm(name).attribute_name, state)
success = if aasm_skipping_validations
value = aasm_raw_attribute_value state
self.class.where(self.class.primary_key => self.id).update_all(self.class.aasm.attribute_name => value) == 1
success = if aasm_skipping_validations(name)
value = aasm_raw_attribute_value(state, name)
self.class.where(self.class.primary_key => self.id).update_all(self.class.aasm(name).attribute_name => value) == 1
else
self.save
end
unless success
write_attribute(self.class.aasm.attribute_name, old_value)
write_attribute(self.class.aasm(name).attribute_name, old_value)
return false
end
@ -102,39 +81,39 @@ module AASM
# Foo.find(1).aasm.current_state # => :closed
#
# NOTE: intended to be called from an event
def aasm_write_state_without_persistence(state)
aasm_write_attribute state
def aasm_write_state_without_persistence(state, name=:default)
aasm_write_attribute(state, name)
end
private
def aasm_enum
case AASM::StateMachine[self.class].config.enum
def aasm_enum(name=:default)
case AASM::StateMachine[self.class][name].config.enum
when false then nil
when true then aasm_guess_enum_method
when nil then aasm_guess_enum_method if aasm_column_looks_like_enum
else AASM::StateMachine[self.class].config.enum
when true then aasm_guess_enum_method(name)
when nil then aasm_guess_enum_method(name) if aasm_column_looks_like_enum(name)
else AASM::StateMachine[self.class][name].config.enum
end
end
def aasm_column_looks_like_enum
self.class.keys[self.class.aasm.attribute_name.to_s].type == Integer
def aasm_column_looks_like_enum(name)
self.class.keys[self.class.aasm(name).attribute_name.to_s].type == Integer
end
def aasm_guess_enum_method
self.class.aasm.attribute_name.to_s.pluralize.to_sym
def aasm_guess_enum_method(name)
self.class.aasm(name).attribute_name.to_s.pluralize.to_sym
end
def aasm_skipping_validations
AASM::StateMachine[self.class].config.skip_validation_on_save
def aasm_skipping_validations(state_machine_name)
AASM::StateMachine[self.class][state_machine_name].config.skip_validation_on_save
end
def aasm_write_attribute(state)
write_attribute self.class.aasm.attribute_name, aasm_raw_attribute_value(state)
def aasm_write_attribute(state, name=:default)
write_attribute self.class.aasm(name).attribute_name, aasm_raw_attribute_value(state, name)
end
def aasm_raw_attribute_value(state)
if aasm_enum
self.class.send(aasm_enum)[state]
def aasm_raw_attribute_value(state, name=:default)
if aasm_enum(name)
self.class.send(aasm_enum(name))[state]
else
state.to_s
end
@ -156,14 +135,18 @@ module AASM
# foo.aasm_state # => nil
#
def aasm_ensure_initial_state
return send("#{self.class.aasm.attribute_name}=", aasm.enter_initial_state.to_s) if send(self.class.aasm.attribute_name).blank?
AASM::StateMachine[self.class].keys.each do |state_machine_name|
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?
end
end
def aasm_validate_states
send("#{self.class.aasm.attribute_name}=", aasm.enter_initial_state.to_s) if send(self.class.aasm.attribute_name).blank?
unless AASM::StateMachine[self.class].config.skip_validation_on_save
if aasm.current_state && !aasm.states.include?(aasm.current_state)
self.errors.add(AASM::StateMachine[self.class].config.column , "is invalid")
AASM::StateMachine[self.class].keys.each do |state_machine_name|
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?
unless AASM::StateMachine[self.class][state_machine_name].config.skip_validation_on_save
if aasm(state_machine_name).current_state && !aasm(state_machine_name).states.include?(aasm(state_machine_name).current_state)
self.errors.add(AASM::StateMachine[self.class][state_machine_name].config.column , "is invalid")
end
end
end
end

View file

@ -32,34 +32,11 @@ module AASM
#
def self.included(base)
base.send(:include, AASM::Persistence::Base)
base.extend AASM::Persistence::MongoidPersistence::ClassMethods
base.send(:include, AASM::Persistence::MongoidPersistence::InstanceMethods)
base.after_initialize :aasm_ensure_initial_state
end
module ClassMethods
def find_in_state(number, state, *args)
with_state_scope state do
find(number, *args)
end
end
def count_in_state(state, *args)
with_state_scope state do
count(*args)
end
end
def with_state_scope(state)
with_scope where(aasm.attribute_name.to_sym => state.to_s) do
yield if block_given?
end
end
end
module InstanceMethods
# Writes <tt>state</tt> to the state column and persists it to the database
@ -72,12 +49,12 @@ module AASM
# Foo.find(1).aasm.current_state # => :closed
#
# NOTE: intended to be called from an event
def aasm_write_state(state)
old_value = read_attribute(self.class.aasm.attribute_name)
write_attribute(self.class.aasm.attribute_name, state.to_s)
def aasm_write_state(state, name=:default)
old_value = read_attribute(self.class.aasm(name).attribute_name)
write_attribute(self.class.aasm(name).attribute_name, state.to_s)
unless self.save(:validate => false)
write_attribute(self.class.aasm.attribute_name, old_value)
write_attribute(self.class.aasm(name).attribute_name, old_value)
return false
end
@ -96,8 +73,8 @@ module AASM
# Foo.find(1).aasm.current_state # => :closed
#
# NOTE: intended to be called from an event
def aasm_write_state_without_persistence(state)
write_attribute(self.class.aasm.attribute_name, state.to_s)
def aasm_write_state_without_persistence(state, name=:default)
write_attribute(self.class.aasm(name).attribute_name, state.to_s)
end
private
@ -118,16 +95,18 @@ module AASM
# foo.aasm_state # => nil
#
def aasm_ensure_initial_state
send("#{self.class.aasm.attribute_name}=", aasm.enter_initial_state.to_s) if send(self.class.aasm.attribute_name).blank?
AASM::StateMachine[self.class].keys.each do |state_machine_name|
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?
end
end
end # InstanceMethods
module NamedScopeMethods
def aasm_state_with_named_scope name, options = {}
aasm_state_without_named_scope name, options
self.named_scope name, :conditions => { "#{table_name}.#{self.aasm.attribute_name}" => name.to_s} unless self.respond_to?(name)
end
end
# module NamedScopeMethods
# def aasm_state_with_named_scope name, options = {}
# aasm_state_without_named_scope name, options
# self.named_scope name, :conditions => { "#{table_name}.#{self.aasm.attribute_name}" => name.to_s} unless self.respond_to?(name)
# end
# end
end
end # Persistence
end # AASM

View file

@ -2,21 +2,22 @@ module AASM
module Persistence
module PlainPersistence
def aasm_read_state
# all the following lines behave like @current_state ||= aasm.enter_initial_state
current = aasm.instance_variable_get("@current_state")
# may be overwritten by persistence mixins
def aasm_read_state(name=:default)
# all the following lines behave like @current_state ||= aasm(name).enter_initial_state
current = aasm(name).instance_variable_get("@current_state_#{name}")
return current if current
aasm.instance_variable_set("@current_state", aasm.enter_initial_state)
aasm(name).instance_variable_set("@current_state_#{name}", aasm(name).enter_initial_state)
end
# may be overwritten by persistence mixins
def aasm_write_state(new_state)
def aasm_write_state(new_state, name=:default)
true
end
# may be overwritten by persistence mixins
def aasm_write_state_without_persistence(new_state)
true
def aasm_write_state_without_persistence(new_state, name=:default)
aasm(name).instance_variable_set("@current_state_#{name}", new_state)
end
end

View file

@ -44,10 +44,10 @@ module AASM
# NOTE: intended to be called from an event
#
# This allows for nil aasm states - be sure to add validation to your model
def aasm_read_state
state = send(self.class.aasm.attribute_name)
def aasm_read_state(name=:default)
state = send(self.class.aasm(name).attribute_name)
if new? && state.to_s.strip.empty?
aasm.determine_state_name(self.class.aasm.initial_state)
aasm(name).determine_state_name(self.class.aasm(name).initial_state)
elsif state.nil?
nil
else
@ -71,9 +71,11 @@ module AASM
# foo.aasm_state # => nil
#
def aasm_ensure_initial_state
aasm.enter_initial_state if
(new? || values.key?(self.class.aasm.attribute_name)) &&
send(self.class.aasm.attribute_name).to_s.strip.empty?
AASM::StateMachine[self.class].keys.each do |state_machine_name|
aasm(state_machine_name).enter_initial_state if
(new? || values.key?(self.class.aasm(state_machine_name).attribute_name)) &&
send(self.class.aasm(state_machine_name).attribute_name).to_s.strip.empty?
end
end
# Writes <tt>state</tt> to the state column and persists it to the database
@ -85,8 +87,8 @@ module AASM
# Foo[1].aasm.current_state # => :closed
#
# NOTE: intended to be called from an event
def aasm_write_state state
aasm_column = self.class.aasm.attribute_name
def aasm_write_state state, name=:default
aasm_column = self.class.aasm(name).attribute_name
update_only({aasm_column => state.to_s}, aasm_column)
end
@ -102,8 +104,8 @@ module AASM
# Foo[1].aasm.current_state # => :closed
#
# NOTE: intended to be called from an event
def aasm_write_state_without_persistence state
send("#{self.class.aasm.attribute_name}=", state.to_s)
def aasm_write_state_without_persistence state, name=:default
send("#{self.class.aasm(name).attribute_name}=", state.to_s)
end
end
end

View file

@ -10,13 +10,14 @@ module AASM
(@machines ||= {})[klass.to_s] = machine
end
attr_accessor :states, :events, :initial_state, :config
attr_accessor :states, :events, :initial_state, :config, :name
def initialize
def initialize(name)
@initial_state = nil
@states = []
@events = {}
@config = AASM::Configuration.new
@name = name
end
# called internally by Ruby 1.9 after clone()
@ -26,13 +27,17 @@ module AASM
@events = @events.dup
end
def add_state(name, klass, options)
set_initial_state(name, options)
def add_state(state_name, klass, options)
set_initial_state(state_name, options)
# allow reloading, extending or redefining a state
@states.delete(name) if @states.include?(name)
@states.delete(state_name) if @states.include?(state_name)
@states << AASM::Core::State.new(name, klass, options)
@states << AASM::Core::State.new(state_name, klass, self, options)
end
def add_event(name, options, &block)
@events[name] = AASM::Core::Event.new(name, self, options, &block)
end
private

View file

@ -1,3 +1,3 @@
module AASM
VERSION = "4.2.0"
VERSION = "4.3.0"
end

View file

@ -1,5 +1,5 @@
ActiveRecord::Migration.suppress_messages do
%w{gates readers writers transients simples no_scopes no_direct_assignments thieves localizer_test_models persisted_states provided_and_persisted_states with_enums with_true_enums with_false_enums false_states}.each do |table_name|
%w{gates multiple_gates readers writers transients simples no_scopes multiple_no_scopes no_direct_assignments multiple_no_direct_assignments thieves multiple_thieves localizer_test_models persisted_states provided_and_persisted_states with_enums with_true_enums with_false_enums false_states multiple_with_enums multiple_with_true_enums multiple_with_false_enums multiple_false_states}.each do |table_name|
ActiveRecord::Migration.create_table table_name, :force => true do |t|
t.string "aasm_state"
end
@ -8,17 +8,34 @@ ActiveRecord::Migration.suppress_messages do
ActiveRecord::Migration.create_table "simple_new_dsls", :force => true do |t|
t.string "status"
end
ActiveRecord::Migration.create_table "multiple_simple_new_dsls", :force => true do |t|
t.string "status"
end
ActiveRecord::Migration.create_table "complex_active_record_examples", :force => true do |t|
t.string "left"
t.string "right"
end
ActiveRecord::Migration.create_table "validators", :force => true do |t|
t.string "name"
t.string "status"
end
ActiveRecord::Migration.create_table "multiple_validators", :force => true do |t|
t.string "name"
t.string "status"
end
ActiveRecord::Migration.create_table "transactors", :force => true do |t|
t.string "name"
t.string "status"
t.integer "worker_id"
end
ActiveRecord::Migration.create_table "multiple_transactors", :force => true do |t|
t.string "name"
t.string "status"
t.integer "worker_id"
end
ActiveRecord::Migration.create_table "workers", :force => true do |t|
t.string "name"
@ -29,9 +46,18 @@ ActiveRecord::Migration.suppress_messages do
t.string "name"
t.string "status"
end
ActiveRecord::Migration.create_table "multiple_invalid_persistors", :force => true do |t|
t.string "name"
t.string "status"
end
ActiveRecord::Migration.create_table "fathers", :force => true do |t|
t.string "aasm_state"
t.string "type"
end
ActiveRecord::Migration.create_table "basic_active_record_two_state_machines_examples", :force => true do |t|
t.string "search"
t.string "sync"
end
end

View file

@ -0,0 +1,25 @@
class BasicActiveRecordTwoStateMachinesExample < ActiveRecord::Base
include AASM
aasm :search do
state :initialised, :initial => true
state :queried
state :requested
event :query do
transitions :from => [:initialised, :requested], :to => :queried
end
event :request do
transitions :from => :queried, :to => :requested
end
end
aasm :sync do
state :unsynced, :initial => true
state :synced
event :synchronise do
transitions :from => :unsynced, :to => :synced
end
end
end

View file

@ -0,0 +1,33 @@
class ComplexActiveRecordExample < ActiveRecord::Base
include AASM
aasm :left, :column => 'left' do
state :one, :initial => true
state :two
state :three
event :increment do
transitions :from => :one, :to => :two
transitions :from => :two, :to => :three
end
event :reset do
transitions :from => :three, :to => :one
end
end
aasm :right, :column => 'right' do
state :alpha, :initial => true
state :beta
state :gamma
event :level_up do
transitions :from => :alpha, :to => :beta
transitions :from => :beta, :to => :gamma
end
event :level_down do
transitions :from => :gamma, :to => :beta
transitions :from => :beta, :to => :alpha
end
end
end

View file

@ -1,3 +1,7 @@
require_relative 'simple_new_dsl'
class DerivateNewDsl < SimpleNewDsl
end
class MultipleDerivateNewDsl < MultipleSimpleNewDsl
end

View file

@ -15,3 +15,21 @@ class FalseState < ActiveRecord::Base
end
end
end
class MultipleFalseState < ActiveRecord::Base
include AASM
def initialize(*args)
super
self.aasm_state = false
end
aasm :left do
state :opened
state :closed
event :view do
transitions :to => :read, :from => [:needs_attention]
end
end
end

View file

@ -17,3 +17,23 @@ class Gate < ActiveRecord::Base
end
end
end
class MultipleGate < ActiveRecord::Base
include AASM
# Fake this column for testing purposes
# attr_accessor :aasm_state
def value
'value'
end
aasm :left, :column => :aasm_state do
state :opened
state :closed
event :view do
transitions :to => :read, :from => [:needs_attention]
end
end
end

View file

@ -8,3 +8,14 @@ class NoDirectAssignment < ActiveRecord::Base
end
end
end
class MultipleNoDirectAssignment < ActiveRecord::Base
include AASM
aasm :left, :column => :aasm_state, :no_direct_assignment => true do
state :pending, :initial => true
state :running
event :run do
transitions :from => :pending, :to => :running
end
end
end

View file

@ -8,3 +8,14 @@ class NoScope < ActiveRecord::Base
end
end
end
class MultipleNoScope < ActiveRecord::Base
include AASM
aasm :left, :column => :aasm_state, :create_scopes => false do
state :pending, :initial => true
state :running
event :run do
transitions :from => :pending, :to => :running
end
end
end

View file

@ -10,15 +10,15 @@ class ProvidedAndPersistedState < ActiveRecord::Base
end
end
def aasm_read_state
def aasm_read_state(*args)
:gamma
end
def aasm_write_state(new_state)
def aasm_write_state(new_state, *args)
@persisted_store = new_state
end
def aasm_write_state_without_persistence(new_state)
def aasm_write_state_without_persistence(new_state, *args)
@transient_store = new_state
end
end

View file

@ -6,3 +6,12 @@ class SimpleNewDsl < ActiveRecord::Base
state :new
end
end
class MultipleSimpleNewDsl < ActiveRecord::Base
include AASM
aasm :left, :column => :status
aasm :left do
state :unknown_scope
state :new
end
end

View file

@ -12,3 +12,18 @@ class Thief < ActiveRecord::Base
end
attr_accessor :skilled, :aasm_state
end
class MultipleThief < ActiveRecord::Base
if ActiveRecord::VERSION::MAJOR >= 3
self.table_name = 'multiple_thieves'
else
set_table_name "multiple_thieves"
end
include AASM
aasm :left, :column => :aasm_state do
state :rich
state :jailed
initial_state Proc.new {|thief| thief.skilled ? :rich : :jailed }
end
attr_accessor :skilled, :aasm_state
end

View file

@ -17,3 +17,23 @@ class WithEnum < ActiveRecord::Base
end
end
end
class MultipleWithEnum < ActiveRecord::Base
include AASM
# Fake this column for testing purposes
attr_accessor :aasm_state
def self.test
{}
end
aasm :left, :enum => :test do
state :opened
state :closed
event :view do
transitions :to => :read, :from => [:needs_attention]
end
end
end

View file

@ -13,3 +13,19 @@ class WithFalseEnum < ActiveRecord::Base
end
end
end
class MultipleWithFalseEnum < ActiveRecord::Base
include AASM
# Fake this column for testing purposes
attr_accessor :aasm_state
aasm :left, :enum => false do
state :opened
state :closed
event :view do
transitions :to => :read, :from => [:needs_attention]
end
end
end

View file

@ -17,3 +17,23 @@ class WithTrueEnum < ActiveRecord::Base
end
end
end
class MultipleWithTrueEnum < ActiveRecord::Base
include AASM
# Fake this column for testing purposes
attr_accessor :aasm_state
def value
'value'
end
aasm :left, :enum => true do
state :opened
state :closed
event :view do
transitions :to => :read, :from => [:needs_attention]
end
end
end

View file

@ -0,0 +1,25 @@
class BasicTwoStateMachinesExample
include AASM
aasm :search do
state :initialised, :initial => true
state :queried
state :requested
event :query do
transitions :from => [:initialised, :requested], :to => :queried
end
event :request do
transitions :from => :queried, :to => :requested
end
end
aasm :sync do
state :unsynced, :initial => true
state :synced
event :sync do
transitions :from => :unsynced, :to => :synced
end
end
end

View file

@ -0,0 +1,75 @@
module Callbacks
class BasicMultiple
include AASM
def initialize(options={})
@fail_event_guard = options[:fail_event_guard]
@fail_transition_guard = options[:fail_transition_guard]
@log = options[:log]
reset_data
end
def reset_data
@data = []
end
def data
@data.join(' ')
end
aasm(:left) do
state :open, :initial => true,
:before_enter => :before_enter_open,
:enter => :enter_open,
:after_enter => :after_enter_open,
:before_exit => :before_exit_open,
:exit => :exit_open,
:after_exit => :after_exit_open
state :closed,
:before_enter => :before_enter_closed,
:enter => :enter_closed,
:after_enter => :after_enter_closed,
:before_exit => :before_exit_closed,
:exit => :exit_closed,
:after_exit => :after_exit_closed
event :left_close, :before => :before_event, :after => :after_event, :guard => :event_guard do
transitions :to => :closed, :from => [:open], :guard => :transition_guard, :after => :after_transition
end
event :left_open, :before => :before_event, :after => :after_event do
transitions :to => :open, :from => :closed
end
end
def log(text)
@data << text
puts text if @log
end
def aasm_write_state(*args); log("aasm_write_state(#{args.inspect})"); true; end
def before_enter_open; log('before_enter_open'); end
def enter_open; log('enter_open'); end
def before_exit_open; log('before_exit_open'); end
def after_enter_open; log('after_enter_open'); end
def exit_open; log('exit_open'); end
def after_exit_open; log('after_exit_open'); end
def before_enter_closed; log('before_enter_closed'); end
def enter_closed; log('enter_closed'); end
def before_exit_closed; log('before_exit_closed'); end
def exit_closed; log('exit_closed'); end
def after_enter_closed; log('after_enter_closed'); end
def after_exit_closed; log('after_exit_closed'); end
def event_guard; log('event_guard'); !@fail_event_guard; end
def transition_guard; log('transition_guard'); !@fail_transition_guard; end
def after_transition; log('after_transition'); end
def before_event; log('before_event'); end
def after_event; log('after_event'); end
end
end

View file

@ -0,0 +1,66 @@
module Callbacks
class GuardWithinBlockMultiple
include AASM
def initialize(options={})
@fail_event_guard = options[:fail_event_guard]
@fail_transition_guard = options[:fail_transition_guard]
@log = options[:log]
end
aasm(:left) do
state :open, :initial => true,
:before_enter => :before_enter_open,
:enter => :enter_open,
:after_enter => :after_enter_open,
:before_exit => :before_exit_open,
:exit => :exit_open,
:after_exit => :after_exit_open
state :closed,
:before_enter => :before_enter_closed,
:enter => :enter_closed,
:after_enter => :after_enter_closed,
:before_exit => :before_exit_closed,
:exit => :exit_closed,
:after_exit => :after_exit_closed
event :close, :before => :before, :after => :after, :guard => :event_guard do
transitions :to => :closed, :from => [:open], :after => :transitioning do
guard do
transition_guard
end
end
end
event :open, :before => :before, :after => :after do
transitions :to => :open, :from => :closed
end
end
def log(text)
puts text if @log
end
def before_enter_open; log('before_enter_open'); end
def enter_open; log('enter_open'); end
def before_exit_open; log('before_exit_open'); end
def after_enter_open; log('after_enter_open'); end
def exit_open; log('exit_open'); end
def after_exit_open; log('after_exit_open'); end
def before_enter_closed; log('before_enter_closed'); end
def enter_closed; log('enter_closed'); end
def before_exit_closed; log('before_exit_closed'); end
def exit_closed; log('exit_closed'); end
def after_enter_closed; log('after_enter_closed'); end
def after_exit_closed; log('after_exit_closed'); end
def event_guard; log('event_guard'); !@fail_event_guard; end
def transition_guard; log('transition_guard'); !@fail_transition_guard; end
def transitioning; log('transitioning'); end
def before; log('before'); end
def after; log('after'); end
end
end

View file

@ -0,0 +1,65 @@
module Callbacks
class MultipleTransitionsTransitionGuardMultiple
include AASM
def initialize(options={})
@fail_event_guard = options[:fail_event_guard]
@fail_transition_guard = options[:fail_transition_guard]
@log = options[:log]
end
aasm(:left) do
state :open, :initial => true,
:before_enter => :before_enter_open,
:enter => :enter_open,
:after_enter => :after_enter_open,
:before_exit => :before_exit_open,
:exit => :exit_open,
:after_exit => :after_exit_open
state :closed,
:before_enter => :before_enter_closed,
:enter => :enter_closed,
:after_enter => :after_enter_closed,
:before_exit => :before_exit_closed,
:exit => :exit_closed,
:after_exit => :after_exit_closed
state :failed
event :close, :before => :before, :after => :after, :guard => :event_guard do
transitions :to => :closed, :from => [:open], :guard => :transition_guard, :after => :transitioning
transitions :to => :failed, :from => [:open]
end
event :open, :before => :before, :after => :after do
transitions :to => :open, :from => :closed
end
end
def log(text)
puts text if @log
end
def before_enter_open; log('before_enter_open'); end
def enter_open; log('enter_open'); end
def before_exit_open; log('before_exit_open'); end
def after_enter_open; log('after_enter_open'); end
def exit_open; log('exit_open'); end
def after_exit_open; log('after_exit_open'); end
def before_enter_closed; log('before_enter_closed'); end
def enter_closed; log('enter_closed'); end
def before_exit_closed; log('before_exit_closed'); end
def exit_closed; log('exit_closed'); end
def after_enter_closed; log('after_enter_closed'); end
def after_exit_closed; log('after_exit_closed'); end
def event_guard; log('event_guard'); !@fail_event_guard; end
def transition_guard; log('transition_guard'); !@fail_transition_guard; end
def transitioning; log('transitioning'); end
def before; log('before'); end
def after; log('after'); end
end
end

View file

@ -0,0 +1,44 @@
module Callbacks
class PrivateMethodMultiple
include AASM
def initialize(options={})
@fail_event_guard = options[:fail_event_guard]
@fail_transition_guard = options[:fail_transition_guard]
@log = options[:log]
reset_data
end
def reset_data
@data = []
end
def data
@data.join(' ')
end
aasm(:left) do
state :open, :initial => true
state :closed
event :close, :after => :after_event do
transitions :to => :closed, :from => [:open]
end
event :open, :after => :after_event do
transitions :to => :open, :from => :closed
end
end
def log(text)
@data << text
puts text if @log
end
def aasm_write_state(*args); log('aasm_write_state'); true; end
private
def after_event; log('after_event'); end
end
end

View file

@ -0,0 +1,61 @@
module Callbacks
class WithArgsMultiple
include AASM
def initialize(options={})
@log = options[:log]
reset_data
end
def reset_data
@data = []
end
def data
@data.join(' ')
end
aasm(:left) do
state :open, :initial => true,
:before_enter => :before_enter_open,
:after_enter => :after_enter_open,
:before_exit => :before_exit_open,
:after_exit => :after_exit_open
state :closed,
:before_enter => :before_enter_closed,
:after_enter => :after_enter_closed,
:before_exit => :before_exit_closed,
:after_exit => :after_exit_closed
event :close, :before => :before, :after => :after do
transitions :to => :closed, :from => [:open], :after => :transition_proc
end
event :open, :before => :before, :after => :after do
transitions :to => :open, :from => :closed
end
end
def log(text)
@data << text
puts text if @log
end
def aasm_write_state(*args); log('aasm_write_state'); true; end
def before_enter_open(*args); log("before_enter_open(#{args.map(&:inspect).join(',')})"); end
def before_exit_open(*args); log("before_exit_open(#{args.map(&:inspect).join(',')})"); end
def after_enter_open(*args); log("after_enter_open(#{args.map(&:inspect).join(',')})"); end
def after_exit_open(*args); log("after_exit_open(#{args.map(&:inspect).join(',')})"); end
def before_enter_closed(*args); log("before_enter_closed(#{args.map(&:inspect).join(',')})"); end
def before_exit_closed(*args); log("before_enter_closed(#{args.map(&:inspect).join(',')})"); end
def after_enter_closed(*args); log("after_enter_closed(#{args.map(&:inspect).join(',')})"); end
def after_exit_closed(*args); log("after_exit_closed(#{args.map(&:inspect).join(',')})"); end
def before(arg1, *args); log("before(#{arg1.inspect},#{args.map(&:inspect).join(',')})"); end
def transition_proc(arg1, arg2); log("transition_proc(#{arg1.inspect},#{arg2.inspect})"); end
def after(*args); log("after(#{args.map(&:inspect).join(',')})"); end
end
end

View file

@ -0,0 +1,26 @@
module Callbacks
class WithStateArgMultiple
include AASM
aasm(:left) do
state :open, :inital => true
state :closed
state :out_to_lunch
event :close, :before => :before_method, :after => :after_method do
transitions :to => :closed, :from => [:open], :after => :transition_method
transitions :to => :out_to_lunch, :from => [:open], :after => :transition_method2
end
end
def before_method(arg); end
def after_method(arg); end
def transition_method(arg); end
def transition_method2(arg); end
end
end

View file

@ -86,3 +86,137 @@ class ComplexExample
phrase == :please
end
end
class ComplexExampleMultiple
include AASM
attr_accessor :activation_code, :activated_at, :deleted_at
aasm(:left) do
state :passive
state :pending, :initial => true, :before_enter => :make_activation_code
state :active, :before_enter => :do_activate
state :suspended
state :deleted, :before_enter => :do_delete#, :exit => :do_undelete
state :waiting
event :left_register do
transitions :from => :passive, :to => :pending do
guard do
can_register?
end
end
end
event :left_activate do
transitions :from => :pending, :to => :active
end
event :left_suspend do
transitions :from => [:passive, :pending, :active], :to => :suspended
end
event :left_delete do
transitions :from => [:passive, :pending, :active, :suspended], :to => :deleted
end
# a dummy event that can never happen
event :left_unpassify do
transitions :from => :passive, :to => :active, :guard => Proc.new {|u| false }
end
event :left_unsuspend do
transitions :from => :suspended, :to => :active, :guard => Proc.new { has_activated? }
transitions :from => :suspended, :to => :pending, :guard => :has_activation_code?
transitions :from => :suspended, :to => :passive
end
event :left_wait do
transitions :from => :suspended, :to => :waiting, :guard => :if_polite?
end
end
aasm(:right) do
state :passive
state :pending, :initial => true, :before_enter => :make_activation_code
state :active, :before_enter => :do_activate
state :suspended
state :deleted, :before_enter => :do_delete#, :exit => :do_undelete
state :waiting
event :right_register do
transitions :from => :passive, :to => :pending do
guard do
can_register?
end
end
end
event :right_activate do
transitions :from => :pending, :to => :active
end
event :right_suspend do
transitions :from => [:passive, :pending, :active], :to => :suspended
end
event :right_delete do
transitions :from => [:passive, :pending, :active, :suspended], :to => :deleted
end
# a dummy event that can never happen
event :right_unpassify do
transitions :from => :passive, :to => :active, :guard => Proc.new {|u| false }
end
event :right_unsuspend do
transitions :from => :suspended, :to => :active, :guard => Proc.new { has_activated? }
transitions :from => :suspended, :to => :pending, :guard => :has_activation_code?
transitions :from => :suspended, :to => :passive
end
event :right_wait do
transitions :from => :suspended, :to => :waiting, :guard => :if_polite?
end
end # right
def initialize
# the AR backend uses a before_validate_on_create :aasm_ensure_initial_state
# lets do something similar here for testing purposes.
aasm(:left).enter_initial_state
aasm(:right).enter_initial_state
end
def make_activation_code
@activation_code = @activation_code ? @activation_code + '2' : '1'
end
def do_activate
@activated_at = Time.now
@activation_code = nil
end
def do_delete
@deleted_at = Time.now
end
def do_undelete
@deleted_at = false
end
def can_register?
true
end
def has_activated?
!!@activated_at
end
def has_activation_code?
!!@activation_code
end
def if_polite?(phrase = nil)
phrase == :please
end
end

View file

@ -34,8 +34,55 @@ class Conversation
@persister = persister
end
private
def aasm_read_state
@persister.read_state
end
def aasm_write_state(state)
@persister.write_state(state)
end
end
class ConversationMultiple
include AASM
aasm(:left) do
state :needs_attention, :initial => true
state :read
state :closed
state :awaiting_response
state :junk
event :new_message do
end
event :view do
transitions :to => :read, :from => [:needs_attention]
end
event :reply do
end
event :close do
transitions :to => :closed, :from => [:read, :awaiting_response]
end
event :junk do
transitions :to => :junk, :from => [:read]
end
event :unjunk do
end
end
def initialize(persister)
@persister = persister
end
private
def aasm_read_state
@persister.read_state
end
@ -43,5 +90,4 @@ class Conversation
def aasm_write_state(state)
@persister.write_state(state)
end
end

View file

@ -33,3 +33,60 @@ class FooTwo < Foo
state :foo
end
end
class FooMultiple
include AASM
aasm(:left) do
state :open, :initial => true, :before_exit => :before_exit
state :closed, :before_enter => :before_enter
state :final
event :close, :success => :success_callback do
transitions :from => [:open], :to => [:closed]
end
event :null do
transitions :from => [:open], :to => [:closed, :final], :guard => :always_false
end
end
aasm(:right, :column => :right) do
state :green, :initial => true
state :yellow
state :red
event :green do
transitions :from => [:yellow], :to => :green
end
event :yellow do
transitions :from => [:green, :red], :to => :yellow
end
event :red do
transitions :from => [:yellow], :to => :red
end
end
def always_false
false
end
def success_callback
end
def before_enter
end
def before_exit
end
end
class FooTwoMultiple < FooMultiple
include AASM
aasm(:left) do
state :foo
end
aasm(:right) do
state :bar
end
end

View file

@ -0,0 +1,45 @@
class FooCallbackMultiple
include AASM
aasm(:left) do
state :open, :initial => true, :before_exit => :before_exit
state :closed, :before_enter => :before_enter
state :final
event :close, :success => :success_callback do
transitions :from => [:open], :to => [:closed]
end
event :null do
transitions :from => [:open], :to => [:closed, :final], :guard => :always_false
end
end
aasm(:right, :column => :right) do
state :green, :initial => true
state :yellow
state :red
event :green do
transitions :from => [:yellow], :to => :green
end
event :yellow do
transitions :from => [:green, :red], :to => :yellow
end
event :red do
transitions :from => [:yellow], :to => :red
end
end
def always_false
false
end
def success_callback
end
def before_enter
end
def before_exit
end
end

View file

@ -0,0 +1,48 @@
class GuardianMultiple
include AASM
aasm(:left) do
state :alpha, :initial => true
state :beta
event :use_one_guard_that_succeeds do
transitions :from => :alpha, :to => :beta, :guard => :succeed
end
event :use_one_guard_that_fails do
transitions :from => :alpha, :to => :beta, :guard => :fail
end
event :use_guards_that_succeed do
transitions :from => :alpha, :to => :beta, :guards => [:succeed, :another_succeed]
end
event :use_guards_where_the_first_fails do
transitions :from => :alpha, :to => :beta, :guards => [:succeed, :fail]
end
event :use_guards_where_the_second_fails do
transitions :from => :alpha, :to => :beta, :guards => [:fail, :succeed]
end
event :use_event_guards_that_succeed, :guards => [:succeed, :another_succeed] do
transitions :from => :alpha, :to => :beta
end
event :use_event_and_transition_guards_that_succeed, :guards => [:succeed, :another_succeed] do
transitions :from => :alpha, :to => :beta, :guards => [:more_succeed]
end
event :use_event_guards_where_the_first_fails, :guards => [:succeed, :fail] do
transitions :from => :alpha, :to => :beta
end
event :use_event_guards_where_the_second_fails, :guards => [:fail, :succeed] do
transitions :from => :alpha, :to => :beta, :guard => :another_succeed
end
event :use_event_and_transition_guards_where_third_fails, :guards => [:succeed, :another_succeed] do
transitions :from => :alpha, :to => :beta, :guards => [:fail]
end
end
def fail; false; end
def succeed; true; end
def another_succeed; true; end
def more_succeed; true; end
end

View file

@ -13,3 +13,19 @@ class InitialStateProc
def initialize(balance = 0); self.balance = balance; end
def rich?; self.balance >= RICH; end
end
class InitialStateProcMultiple
RICH = 1_000_000
attr_accessor :balance
include AASM
aasm(:left) do
state :retired
state :selling_bad_mortgages
initial_state Proc.new { |banker| banker.rich? ? :retired : :selling_bad_mortgages }
end
def initialize(balance = 0); self.balance = balance; end
def rich?; self.balance >= RICH; end
end

View file

@ -14,3 +14,18 @@ class InvalidPersistor < ActiveRecord::Base
end
validates_presence_of :name
end
class MultipleInvalidPersistor < ActiveRecord::Base
include AASM
aasm :left, :column => :status, :skip_validation_on_save => true do
state :sleeping, :initial => true
state :running
event :run do
transitions :to => :running, :from => :sleeping
end
event :sleep do
transitions :to => :sleeping, :from => :running
end
end
validates_presence_of :name
end

View file

@ -0,0 +1,37 @@
class ComplexMongoMapperExample
include MongoMapper::Document
include AASM
key :left, String
key :right, String
aasm :left, :column => 'left' do
state :one, :initial => true
state :two
state :three
event :increment do
transitions :from => :one, :to => :two
transitions :from => :two, :to => :three
end
event :reset do
transitions :from => :three, :to => :one
end
end
aasm :right, :column => 'right' do
state :alpha, :initial => true
state :beta
state :gamma
event :level_up do
transitions :from => :alpha, :to => :beta
transitions :from => :beta, :to => :gamma
end
event :level_down do
transitions :from => :gamma, :to => :beta
transitions :from => :beta, :to => :alpha
end
end
end

View file

@ -8,3 +8,14 @@ class NoScopeMongoMapper
state :ignored_scope
end
end
class NoScopeMongoMapperMultiple
include MongoMapper::Document
include AASM
key :status, String
aasm :left, :create_scopes => false, :column => :status do
state :ignored_scope
end
end

View file

@ -9,3 +9,15 @@ class SimpleMongoMapper
state :next
end
end
class SimpleMongoMapperMultiple
include MongoMapper::Document
include AASM
key :status, String
aasm :left, column: :status do
state :unknown_scope
state :next
end
end

View file

@ -10,3 +10,16 @@ class SimpleNewDslMongoMapper
state :next
end
end
class SimpleNewDslMongoMapperMultiple
include MongoMapper::Document
include AASM
key :status, String
aasm :left, :column => :status
aasm :left do
state :unknown_scope
state :next
end
end

View file

@ -0,0 +1,37 @@
class ComplexMongoidExample
include Mongoid::Document
include AASM
field :left, :type => String
field :right, :type => String
aasm :left, :column => 'left' do
state :one, :initial => true
state :two
state :three
event :increment do
transitions :from => :one, :to => :two
transitions :from => :two, :to => :three
end
event :reset do
transitions :from => :three, :to => :one
end
end
aasm :right, :column => 'right' do
state :alpha, :initial => true
state :beta
state :gamma
event :level_up do
transitions :from => :alpha, :to => :beta
transitions :from => :beta, :to => :gamma
end
event :level_down do
transitions :from => :gamma, :to => :beta
transitions :from => :beta, :to => :alpha
end
end
end

View file

@ -8,3 +8,14 @@ class NoScopeMongoid
state :ignored_scope
end
end
class NoScopeMongoidMultiple
include Mongoid::Document
include AASM
field :status, :type => String
aasm :left, :create_scopes => false, :column => :status do
state :ignored_scope
end
end

View file

@ -9,3 +9,15 @@ class SimpleMongoid
state :new
end
end
class SimpleMongoidMultiple
include Mongoid::Document
include AASM
field :status, :type => String
aasm :left, column: :status do
state :unknown_scope
state :new
end
end

View file

@ -10,3 +10,16 @@ class SimpleNewDslMongoid
state :new
end
end
class SimpleNewDslMongoidMultiple
include Mongoid::Document
include AASM
field :status, :type => String
aasm :left, :column => :status
aasm :left do
state :unknown_scope, :initial => true
state :new
end
end

View file

@ -10,3 +10,16 @@ class NoInitialState
end
end
end
class NoInitialStateMultiple
include AASM
aasm(:left) do
state :read
state :ended
event :foo do
transitions :to => :ended, :from => [:read]
end
end
end

View file

@ -18,7 +18,7 @@ class ParametrisedEvent
end
end
def wear_clothes(shirt_color, trouser_type)
def wear_clothes(shirt_color, trouser_type=nil)
end
def condition_hair

View file

@ -0,0 +1,29 @@
class ParametrisedEventMultiple
include AASM
aasm(:left) do
state :sleeping, :initial => true
state :showering
state :working
state :dating
state :prettying_up
event :wakeup do
transitions :from => :sleeping, :to => [:showering, :working]
end
event :dress do
transitions :from => :sleeping, :to => :working, :after => :wear_clothes
transitions :from => :showering, :to => [:working, :dating], :after => Proc.new { |*args| wear_clothes(*args) }
transitions :from => :showering, :to => :prettying_up, :after => [:condition_hair, :fix_hair]
end
end
def wear_clothes(shirt_color, trouser_type=nil)
end
def condition_hair
end
def fix_hair
end
end

View file

@ -10,15 +10,15 @@ class ProvidedState
end
end
def aasm_read_state
def aasm_read_state(*args)
:beta
end
def aasm_write_state(new_state)
def aasm_write_state(new_state, *args)
@persisted_store = new_state
end
def aasm_write_state_without_persistence(new_state)
def aasm_write_state_without_persistence(new_state, *args)
@transient_store = new_state
end
end

View file

@ -0,0 +1,45 @@
db = Sequel.connect(SEQUEL_DB)
# if you want to see the statements while running the spec enable the following line
# db.loggers << Logger.new($stderr)
db.create_table(:complex_sequel_examples) do
primary_key :id
String :left
String :right
end
class ComplexSequelExample < Sequel::Model(db)
set_dataset(:complex_sequel_examples)
include AASM
aasm :left, :column => 'left' do
state :one, :initial => true
state :two
state :three
event :increment do
transitions :from => :one, :to => :two
transitions :from => :two, :to => :three
end
event :reset do
transitions :from => :three, :to => :one
end
end
aasm :right, :column => 'right' do
state :alpha, :initial => true
state :beta
state :gamma
event :level_up do
transitions :from => :alpha, :to => :beta
transitions :from => :beta, :to => :gamma
end
event :level_down do
transitions :from => :gamma, :to => :beta
transitions :from => :beta, :to => :alpha
end
end
end

View file

@ -0,0 +1,25 @@
db = Sequel.connect(SEQUEL_DB)
# if you want to see the statements while running the spec enable the following line
# db.loggers << Logger.new($stderr)
db.create_table(:multiples) do
primary_key :id
String :status
end
class SequelMultiple < Sequel::Model(db)
set_dataset(:multiples)
include AASM
attr_accessor :default
aasm :left, :column => :status
aasm :left do
state :alpha, :initial => true
state :beta
state :gamma
event :release do
transitions :from => [:alpha, :beta, :gamma], :to => :beta
end
end
end

View file

@ -0,0 +1,25 @@
db = Sequel.connect(SEQUEL_DB)
# if you want to see the statements while running the spec enable the following line
# db.loggers << Logger.new($stderr)
db.create_table(:simples) do
primary_key :id
String :status
end
class SequelSimple < Sequel::Model(db)
set_dataset(:simples)
include AASM
attr_accessor :default
aasm :column => :status
aasm do
state :alpha, :initial => true
state :beta
state :gamma
event :release do
transitions :from => [:alpha, :beta, :gamma], :to => :beta
end
end
end

View file

@ -0,0 +1,30 @@
class SimpleMultipleExample
include AASM
aasm(:move) do
state :standing, :initial => true
state :walking
state :running
event :walk do
transitions :from => :standing, :to => :walking
end
event :run do
transitions :from => [:standing, :walking], :to => :running
end
event :hold do
transitions :from => [:walking, :running], :to => :standing
end
end
aasm(:work) do
state :sleeping, :initial => true
state :processing
event :start do
transitions :from => :sleeping, :to => :processing
end
event :stop do
transitions :from => :processing, :to => :sleeping
end
end
end

View file

@ -1,3 +1,7 @@
require_relative 'super_class'
class SubClass < SuperClass
end
class SubClassMultiple < SuperClassMultiple
end

View file

@ -1,7 +1,18 @@
require_relative 'super_class'
class SubClassWithMoreStates < SuperClass
include AASM
aasm do
state :foo
end
end
class SubClassWithMoreStatesMultiple < SuperClassMultiple
include AASM
aasm(:left) do
state :foo
end
aasm(:right) do
state :archived
end
end

View file

@ -16,3 +16,31 @@ class SuperClass
end
end
end
class SuperClassMultiple
include AASM
aasm(:left) do
state :read
state :ended
event :foo do
transitions :to => :ended, :from => [:read]
end
end
aasm(:right) do
state :opened
state :closed
event :close do
transitions :to => :closed, :from => [:opened]
end
end
def update_state
if may_foo?
foo!
end
end
end

View file

@ -1,4 +1,5 @@
require 'active_record'
class Transactor < ActiveRecord::Base
belongs_to :worker
@ -24,3 +25,29 @@ private
end
end
class MultipleTransactor < ActiveRecord::Base
belongs_to :worker
include AASM
aasm :left, :column => :status do
state :sleeping, :initial => true
state :running, :before_enter => :start_worker, :after_enter => :fail
event :run do
transitions :to => :running, :from => :sleeping
end
end
private
def start_worker
worker.update_attribute(:status, 'running')
end
def fail
raise StandardError.new('failed on purpose')
end
end

View file

@ -9,3 +9,15 @@ class ValidStateName
end
end
end
class ValidStateNameMultiple
include AASM
aasm(:left) do
state :invalid, :initial => true
state :valid
event :valid do
transitions :to => :valid, :from => [:invalid]
end
end
end

View file

@ -38,3 +38,42 @@ class Validator < ActiveRecord::Base
raise StandardError.new('failed on purpose')
end
end
class MultipleValidator < ActiveRecord::Base
include AASM
aasm :left, :column => :status do
state :sleeping, :initial => true
state :running
state :failed, :after_enter => :fail
event :run, :after_commit => :change_name! do
transitions :to => :running, :from => :sleeping
end
event :sleep do
after_commit do |name|
change_name_on_sleep name
end
transitions :to => :sleeping, :from => :running
end
event :fail do
transitions :to => :failed, :from => [:sleeping, :running]
end
end
validates_presence_of :name
def change_name!
self.name = "name changed"
save!
end
def change_name_on_sleep name
self.name = name
save!
end
def fail
raise StandardError.new('failed on purpose')
end
end

View file

@ -0,0 +1,10 @@
require 'spec_helper'
describe 'on initialization' do
let(:example) { BasicTwoStateMachinesExample.new }
it 'should be in the initial state' do
expect(example.aasm(:search).current_state).to eql :initialised
expect(example.aasm(:sync).current_state).to eql :unsynced
end
end

View file

@ -0,0 +1,295 @@
require 'spec_helper'
Dir[File.dirname(__FILE__) + "/../models/callbacks/*.rb"].sort.each { |f| require File.expand_path(f) }
describe 'callbacks for the new DSL' do
it "be called in order" do
show_debug_log = false
callback = Callbacks::BasicMultiple.new(:log => show_debug_log)
callback.aasm(:left).current_state
unless show_debug_log
expect(callback).to receive(:before_event).once.ordered
expect(callback).to receive(:event_guard).once.ordered.and_return(true)
expect(callback).to receive(:transition_guard).once.ordered.and_return(true)
expect(callback).to receive(:before_exit_open).once.ordered # these should be before the state changes
expect(callback).to receive(:exit_open).once.ordered
# expect(callback).to receive(:event_guard).once.ordered.and_return(true)
# expect(callback).to receive(:transition_guard).once.ordered.and_return(true)
expect(callback).to receive(:after_transition).once.ordered
expect(callback).to receive(:before_enter_closed).once.ordered
expect(callback).to receive(:enter_closed).once.ordered
expect(callback).to receive(:aasm_write_state).with(:closed, :left).once.ordered.and_return(true) # this is when the state changes
expect(callback).to receive(:after_exit_open).once.ordered # these should be after the state changes
expect(callback).to receive(:after_enter_closed).once.ordered
expect(callback).to receive(:after_event).once.ordered
end
# puts "------- close!"
callback.left_close!
end
it "does not run any state callback if the event guard fails" do
callback = Callbacks::BasicMultiple.new(:log => false)
callback.aasm(:left).current_state
expect(callback).to receive(:before_event).once.ordered
expect(callback).to receive(:event_guard).once.ordered.and_return(false)
expect(callback).to_not receive(:transition_guard)
expect(callback).to_not receive(:before_exit_open)
expect(callback).to_not receive(:exit_open)
expect(callback).to_not receive(:after_transition)
expect(callback).to_not receive(:before_enter_closed)
expect(callback).to_not receive(:enter_closed)
expect(callback).to_not receive(:aasm_write_state)
expect(callback).to_not receive(:after_exit_open)
expect(callback).to_not receive(:after_enter_closed)
expect(callback).to_not receive(:after_event)
expect {
callback.left_close!
}.to raise_error(AASM::InvalidTransition)
end
it "handles private callback methods as well" do
show_debug_log = false
callback = Callbacks::PrivateMethodMultiple.new(:log => show_debug_log)
callback.aasm(:left).current_state
# puts "------- close!"
expect {
callback.close!
}.to_not raise_error
end
context "if the transition guard fails" do
it "does not run any state callback if guard is defined inline" do
show_debug_log = false
callback = Callbacks::BasicMultiple.new(:log => show_debug_log, :fail_transition_guard => true)
callback.aasm(:left).current_state
unless show_debug_log
expect(callback).to receive(:before_event).once.ordered
expect(callback).to receive(:event_guard).once.ordered.and_return(true)
expect(callback).to receive(:transition_guard).once.ordered.and_return(false)
expect(callback).to_not receive(:before_exit_open)
expect(callback).to_not receive(:exit_open)
expect(callback).to_not receive(:after_transition)
expect(callback).to_not receive(:before_enter_closed)
expect(callback).to_not receive(:enter_closed)
expect(callback).to_not receive(:aasm_write_state)
expect(callback).to_not receive(:after_exit_open)
expect(callback).to_not receive(:after_enter_closed)
expect(callback).to_not receive(:after_event)
end
expect {
callback.left_close!
}.to raise_error(AASM::InvalidTransition)
end
it "does not run transition_guard twice for multiple permitted transitions" do
show_debug_log = false
callback = Callbacks::MultipleTransitionsTransitionGuardMultiple.new(:log => show_debug_log, :fail_transition_guard => true)
callback.aasm(:left).current_state
unless show_debug_log
expect(callback).to receive(:before).once.ordered
expect(callback).to receive(:event_guard).once.ordered.and_return(true)
expect(callback).to receive(:transition_guard).once.ordered.and_return(false)
expect(callback).to receive(:event_guard).once.ordered.and_return(true)
expect(callback).to receive(:before_exit_open).once.ordered
expect(callback).to receive(:exit_open).once.ordered
expect(callback).to receive(:aasm_write_state).once.ordered.and_return(true) # this is when the state changes
expect(callback).to receive(:after_exit_open).once.ordered
expect(callback).to receive(:after).once.ordered
expect(callback).to_not receive(:transitioning)
expect(callback).to_not receive(:before_enter_closed)
expect(callback).to_not receive(:enter_closed)
expect(callback).to_not receive(:after_enter_closed)
end
callback.close!
expect(callback.aasm(:left).current_state).to eql :failed
end
it "does not run any state callback if guard is defined with block" do
callback = Callbacks::GuardWithinBlockMultiple.new #(:log => true, :fail_transition_guard => true)
callback.aasm(:left).current_state
expect(callback).to receive(:before).once.ordered
expect(callback).to receive(:event_guard).once.ordered.and_return(true)
expect(callback).to receive(:transition_guard).once.ordered.and_return(false)
expect(callback).to_not receive(:before_exit_open)
expect(callback).to_not receive(:exit_open)
expect(callback).to_not receive(:transitioning)
expect(callback).to_not receive(:before_enter_closed)
expect(callback).to_not receive(:enter_closed)
expect(callback).to_not receive(:aasm_write_state)
expect(callback).to_not receive(:after_exit_open)
expect(callback).to_not receive(:after_enter_closed)
expect(callback).to_not receive(:after)
expect {
callback.close!
}.to raise_error(AASM::InvalidTransition)
end
end
it "should properly pass arguments" do
cb = Callbacks::WithArgsMultiple.new(:log => false)
cb.aasm(:left).current_state
cb.reset_data
cb.close!(:arg1, :arg2)
expect(cb.data).to eql 'before(:arg1,:arg2) before_exit_open(:arg1,:arg2) transition_proc(:arg1,:arg2) before_enter_closed(:arg1,:arg2) aasm_write_state after_exit_open(:arg1,:arg2) after_enter_closed(:arg1,:arg2) after(:arg1,:arg2)'
end
it "should call the callbacks given the to-state as argument" do
cb = Callbacks::WithStateArgMultiple.new
expect(cb).to receive(:before_method).with(:arg1).once.ordered
expect(cb).to receive(:transition_method).never
expect(cb).to receive(:transition_method2).with(:arg1).once.ordered
expect(cb).to receive(:after_method).with(:arg1).once.ordered
cb.close!(:out_to_lunch, :arg1)
cb = Callbacks::WithStateArgMultiple.new
some_object = double('some object')
expect(cb).to receive(:before_method).with(some_object).once.ordered
expect(cb).to receive(:transition_method2).with(some_object).once.ordered
expect(cb).to receive(:after_method).with(some_object).once.ordered
cb.close!(:out_to_lunch, some_object)
end
it "should call the proper methods just with arguments" do
cb = Callbacks::WithStateArgMultiple.new
expect(cb).to receive(:before_method).with(:arg1).once.ordered
expect(cb).to receive(:transition_method).with(:arg1).once.ordered
expect(cb).to receive(:transition_method).never
expect(cb).to receive(:after_method).with(:arg1).once.ordered
cb.close!(:arg1)
cb = Callbacks::WithStateArgMultiple.new
some_object = double('some object')
expect(cb).to receive(:before_method).with(some_object).once.ordered
expect(cb).to receive(:transition_method).with(some_object).once.ordered
expect(cb).to receive(:transition_method).never
expect(cb).to receive(:after_method).with(some_object).once.ordered
cb.close!(some_object)
end
end # callbacks for the new DSL
describe 'event callbacks' do
describe "with an error callback defined" do
before do
class FooCallbackMultiple
# this hack is needed to allow testing of parameters, since RSpec
# destroys a method's arity when mocked
attr_accessor :data
aasm(:left) do
event :safe_close, :success => :success_callback, :error => :error_callback do
transitions :to => :closed, :from => [:open]
end
end
end
@foo = FooCallbackMultiple.new
end
context "error_callback defined" do
it "should run error_callback if an exception is raised" do
def @foo.error_callback(e)
@data = [e]
end
allow(@foo).to receive(:before_enter).and_raise(e = StandardError.new)
@foo.safe_close!
expect(@foo.data).to eql [e]
end
it "should run error_callback without parameters if callback does not support any" do
def @foo.error_callback(e)
@data = []
end
allow(@foo).to receive(:before_enter).and_raise(e = StandardError.new)
@foo.safe_close!('arg1', 'arg2')
expect(@foo.data).to eql []
end
it "should run error_callback with parameters if callback supports them" do
def @foo.error_callback(e, arg1, arg2)
@data = [arg1, arg2]
end
allow(@foo).to receive(:before_enter).and_raise(e = StandardError.new)
@foo.safe_close!('arg1', 'arg2')
expect(@foo.data).to eql ['arg1', 'arg2']
end
end
it "should raise NoMethodError if exception is raised and error_callback is declared but not defined" do
allow(@foo).to receive(:before_enter).and_raise(StandardError)
expect{@foo.safe_close!}.to raise_error(NoMethodError)
end
it "should propagate an error if no error callback is declared" do
allow(@foo).to receive(:before_enter).and_raise("Cannot enter safe")
expect{@foo.close!}.to raise_error(StandardError, "Cannot enter safe")
end
end
describe "with aasm_event_fired defined" do
before do
@foo = FooMultiple.new
def @foo.aasm_event_fired(event, from, to); end
end
it 'should call it for successful bang fire' do
expect(@foo).to receive(:aasm_event_fired).with(:close, :open, :closed)
@foo.close!
end
it 'should call it for successful non-bang fire' do
expect(@foo).to receive(:aasm_event_fired)
@foo.close
end
it 'should not call it for failing bang fire' do
allow(@foo.aasm(:left)).to receive(:set_current_state_with_persistence).and_return(false)
expect(@foo).not_to receive(:aasm_event_fired)
@foo.close!
end
end
describe "with aasm_event_failed defined" do
before do
@foo = FooMultiple.new
def @foo.aasm_event_failed(event, from); end
end
it 'should call it when transition failed for bang fire' do
expect(@foo).to receive(:aasm_event_failed).with(:null, :open)
expect {@foo.null!}.to raise_error(AASM::InvalidTransition)
end
it 'should call it when transition failed for non-bang fire' do
expect(@foo).to receive(:aasm_event_failed).with(:null, :open)
expect {@foo.null}.to raise_error(AASM::InvalidTransition)
end
it 'should not call it if persist fails for bang fire' do
allow(@foo.aasm(:left)).to receive(:set_current_state_with_persistence).and_return(false)
expect(@foo).to receive(:aasm_event_failed)
@foo.close!
end
end
end # event callbacks

View file

@ -52,7 +52,7 @@ describe 'callbacks for the new DSL' do
}.to raise_error(AASM::InvalidTransition)
end
it "it handles private callback methods as well" do
it "handles private callback methods as well" do
show_debug_log = false
callback = Callbacks::PrivateMethod.new(:log => show_debug_log)

View file

@ -0,0 +1,99 @@
require 'spec_helper'
describe 'on initialization' do
let(:auth) {ComplexExampleMultiple.new}
it 'should be in the initial state' do
expect(auth.aasm(:left).current_state).to eq(:pending)
expect(auth.aasm(:right).current_state).to eq(:pending)
end
it 'should have an activation code' do
expect(auth.has_activation_code?).to be_truthy
expect(auth.activation_code).to eq '12'
end
end
describe 'when being unsuspended' do
let(:auth) {ComplexExampleMultiple.new}
it 'should be able to unsuspend' do
auth.left_activate!
auth.left_suspend!
expect(auth.may_left_unsuspend?).to be true
auth.right_activate!
auth.right_suspend!
expect(auth.may_right_unsuspend?).to be true
end
it 'should not be able to unsuspend into active' do
auth.left_suspend!
expect(auth.may_left_unsuspend?(:active)).not_to be true
auth.right_activate!
auth.right_suspend!
expect(auth.may_right_unsuspend?(:active)).to be true
end
it 'should be able to wait into waiting if polite' do
auth.left_suspend!
expect(auth.may_left_wait?(:waiting, :please)).to be true
auth.left_wait!(nil, :please)
auth.right_suspend!
expect(auth.may_right_wait?(:waiting)).to be false
auth.right_wait!(nil, :please)
end
it 'should not be able to be unsuspended into active if not polite' do
auth.left_suspend!
expect(auth.may_left_wait?(:waiting)).not_to be true
expect(auth.may_left_wait?(:waiting, :rude)).not_to be true
expect {auth.left_wait!(nil, :rude)}.to raise_error(AASM::InvalidTransition)
expect {auth.left_wait!}.to raise_error(AASM::InvalidTransition)
end
it 'should not be able to be unpassified' do
auth.left_activate!
auth.left_suspend!
auth.left_unsuspend!
expect(auth.may_left_unpassify?).not_to be true
expect {auth.left_unpassify!}.to raise_error(AASM::InvalidTransition)
end
it 'should be active if previously activated' do
auth.left_activate!
auth.left_suspend!
auth.left_unsuspend!
expect(auth.aasm(:left).current_state).to eq(:active)
end
it 'should be pending if not previously activated, but an activation code is present' do
auth.left_suspend!
auth.left_unsuspend!
expect(auth.aasm(:left).current_state).to eq(:pending)
end
it 'should be passive if not previously activated and there is no activation code' do
auth.activation_code = nil
auth.left_suspend!
auth.left_unsuspend!
expect(auth.aasm(:left).current_state).to eq(:passive)
end
it "should be able to fire known events" do
expect(auth.aasm(:left).may_fire_event?(:left_activate)).to be true
expect(auth.aasm(:right).may_fire_event?(:right_activate)).to be true
end
it "should not be able to fire unknown events" do
expect(auth.aasm(:left).may_fire_event?(:unknown)).to be false
expect(auth.aasm(:right).may_fire_event?(:unknown)).to be false
end
end

View file

@ -0,0 +1,16 @@
require 'spec_helper'
describe "edge cases" do
describe "for classes with multiple state machines" do
it "allows accessing a multiple state machine class without state machine name" do
# it's like starting to define a new state machine within the
# requested class
expect(SimpleMultipleExample.aasm.states.map(&:name)).to be_empty
end
it "do not know yet" do
example = ComplexExampleMultiple.new
expect { example.aasm.states.inspect }.to raise_error(AASM::UnknownStateMachineError)
end
end
end

View file

@ -0,0 +1,73 @@
require 'spec_helper'
describe 'current event' do
let(:pe) {ParametrisedEventMultiple.new}
it 'if no event has been triggered' do
expect(pe.aasm(:left).current_event).to be_nil
end
it 'if a event has been triggered' do
pe.wakeup
expect(pe.aasm(:left).current_event).to eql :wakeup
end
it 'if no event has been triggered' do
pe.wakeup!
expect(pe.aasm(:left).current_event).to eql :wakeup!
end
end
describe 'parametrised events' do
let(:pe) {ParametrisedEventMultiple.new}
it 'should transition to specified next state (sleeping to showering)' do
pe.wakeup!(:showering)
expect(pe.aasm(:left).current_state).to eq(:showering)
end
it 'should transition to specified next state (sleeping to working)' do
pe.wakeup!(:working)
expect(pe.aasm(:left).current_state).to eq(:working)
end
it 'should transition to default (first or showering) state' do
pe.wakeup!
expect(pe.aasm(:left).current_state).to eq(:showering)
end
it 'should transition to default state when :after transition invoked' do
pe.dress!(nil, 'purple', 'dressy')
expect(pe.aasm(:left).current_state).to eq(:working)
end
it 'should call :after transition method with args' do
pe.wakeup!(:showering)
expect(pe).to receive(:wear_clothes).with('blue', 'jeans')
pe.dress!(:working, 'blue', 'jeans')
end
it 'should call :after transition proc' do
pe.wakeup!(:showering)
expect(pe).to receive(:wear_clothes).with('purple', 'slacks')
pe.dress!(:dating, 'purple', 'slacks')
end
it 'should call :after transition with an array of methods' do
pe.wakeup!(:showering)
expect(pe).to receive(:condition_hair)
expect(pe).to receive(:fix_hair)
pe.dress!(:prettying_up)
end
end
describe 'event firing without persistence' do
it 'should attempt to persist if aasm_write_state is defined' do
foo = Foo.new
def foo.aasm_write_state; end
expect(foo).to be_open
expect(foo).to receive(:aasm_write_state_without_persistence)
foo.close
end
end

View file

@ -1,8 +1,9 @@
require 'spec_helper'
describe 'adding an event' do
let(:state_machine) { AASM::StateMachine.new(:name) }
let(:event) do
AASM::Core::Event.new(:close_order, {:success => :success_callback}) do
AASM::Core::Event.new(:close_order, state_machine, {:success => :success_callback}) do
before :before_callback
after :after_callback
transitions :to => :closed, :from => [:open, :received]
@ -35,8 +36,9 @@ describe 'adding an event' do
end
describe 'transition inspection' do
let(:state_machine) { AASM::StateMachine.new(:name) }
let(:event) do
AASM::Core::Event.new(:run) do
AASM::Core::Event.new(:run, state_machine) do
transitions :to => :running, :from => :sleeping
end
end
@ -59,8 +61,9 @@ describe 'transition inspection' do
end
describe 'transition inspection without from' do
let(:state_machine) { AASM::StateMachine.new(:name) }
let(:event) do
AASM::Core::Event.new(:run) do
AASM::Core::Event.new(:run, state_machine) do
transitions :to => :running
end
end
@ -76,15 +79,17 @@ describe 'transition inspection without from' do
end
describe 'firing an event' do
let(:state_machine) { AASM::StateMachine.new(:name) }
it 'should return nil if the transitions are empty' do
obj = double('object', :aasm => double('aasm', :current_state => 'open'))
event = AASM::Core::Event.new(:event)
event = AASM::Core::Event.new(:event, state_machine)
expect(event.fire(obj)).to be_nil
end
it 'should return the state of the first matching transition it finds' do
event = AASM::Core::Event.new(:event) do
event = AASM::Core::Event.new(:event, state_machine) do
transitions :to => :closed, :from => [:open, :received]
end
@ -94,7 +99,7 @@ describe 'firing an event' do
end
it 'should call the guard with the params passed in' do
event = AASM::Core::Event.new(:event) do
event = AASM::Core::Event.new(:event, state_machine) do
transitions :to => :closed, :from => [:open, :received], :guard => :guard_fn
end

View file

@ -0,0 +1,60 @@
require 'spec_helper'
describe "per-transition guards" do
let(:guardian) { GuardianMultiple.new }
it "allows the transition if the guard succeeds" do
expect { guardian.use_one_guard_that_succeeds! }.to_not raise_error
expect(guardian).to be_beta
end
it "stops the transition if the guard fails" do
expect { guardian.use_one_guard_that_fails! }.to raise_error(AASM::InvalidTransition)
expect(guardian).to be_alpha
end
it "allows the transition if all guards succeeds" do
expect { guardian.use_guards_that_succeed! }.to_not raise_error
expect(guardian).to be_beta
end
it "stops the transition if the first guard fails" do
expect { guardian.use_guards_where_the_first_fails! }.to raise_error(AASM::InvalidTransition)
expect(guardian).to be_alpha
end
it "stops the transition if the second guard fails" do
expect { guardian.use_guards_where_the_second_fails! }.to raise_error(AASM::InvalidTransition)
expect(guardian).to be_alpha
end
end
describe "event guards" do
let(:guardian) { GuardianMultiple.new }
it "allows the transition if the event guards succeed" do
expect { guardian.use_event_guards_that_succeed! }.to_not raise_error
expect(guardian).to be_beta
end
it "allows the transition if the event and transition guards succeed" do
expect { guardian.use_event_and_transition_guards_that_succeed! }.to_not raise_error
expect(guardian).to be_beta
end
it "stops the transition if the first event guard fails" do
expect { guardian.use_event_guards_where_the_first_fails! }.to raise_error(AASM::InvalidTransition)
expect(guardian).to be_alpha
end
it "stops the transition if the second event guard fails" do
expect { guardian.use_event_guards_where_the_second_fails! }.to raise_error(AASM::InvalidTransition)
expect(guardian).to be_alpha
end
it "stops the transition if the transition guard fails" do
expect { guardian.use_event_and_transition_guards_where_third_fails! }.to raise_error(AASM::InvalidTransition)
expect(guardian).to be_alpha
end
end

View file

@ -0,0 +1,15 @@
require 'spec_helper'
describe 'initial states' do
it 'should use the first state defined if no initial state is given' do
expect(NoInitialStateMultiple.new.aasm(:left).current_state).to eq(:read)
end
it 'should determine initial state from the Proc results' do
balance = InitialStateProcMultiple::RICH - 1
expect(InitialStateProcMultiple.new(balance).aasm(:left).current_state).to eq(:selling_bad_mortgages)
balance = InitialStateProcMultiple::RICH + 1
expect(InitialStateProcMultiple.new(balance).aasm(:left).current_state).to eq(:retired)
end
end

View file

@ -0,0 +1,201 @@
require 'spec_helper'
describe 'inspection for common cases' do
it 'should support the new DSL' do
# 1st state machine
expect(FooMultiple.aasm(:left)).to respond_to(:states)
expect(FooMultiple.aasm(:left).states.size).to eql 3
expect(FooMultiple.aasm(:left).states).to include(:open)
expect(FooMultiple.aasm(:left).states).to include(:closed)
expect(FooMultiple.aasm(:left).states).to include(:final)
expect(FooMultiple.aasm(:left)).to respond_to(:initial_state)
expect(FooMultiple.aasm(:left).initial_state).to eq(:open)
expect(FooMultiple.aasm(:left)).to respond_to(:events)
expect(FooMultiple.aasm(:left).events.size).to eql 2
expect(FooMultiple.aasm(:left).events).to include(:close)
expect(FooMultiple.aasm(:left).events).to include(:null)
# 2nd state machine
expect(FooMultiple.aasm(:right)).to respond_to(:states)
expect(FooMultiple.aasm(:right).states.size).to eql 3
expect(FooMultiple.aasm(:right).states).to include(:green)
expect(FooMultiple.aasm(:right).states).to include(:yellow)
expect(FooMultiple.aasm(:right).states).to include(:red)
expect(FooMultiple.aasm(:right)).to respond_to(:initial_state)
expect(FooMultiple.aasm(:right).initial_state).to eq(:green)
expect(FooMultiple.aasm(:right)).to respond_to(:events)
expect(FooMultiple.aasm(:right).events.size).to eql 3
expect(FooMultiple.aasm(:right).events).to include(:green)
expect(FooMultiple.aasm(:right).events).to include(:yellow)
expect(FooMultiple.aasm(:right).events).to include(:red)
end
context "instance level inspection" do
let(:foo) { FooMultiple.new }
let(:two) { FooTwoMultiple.new }
it "delivers all states" do
# 1st state machine
states = foo.aasm(:left).states
expect(states.size).to eql 3
expect(states).to include(:open)
expect(states).to include(:closed)
expect(states).to include(:final)
states = foo.aasm(:left).states(:permitted => true)
expect(states.size).to eql 1
expect(states).to include(:closed)
expect(states).not_to include(:open)
expect(states).not_to include(:final)
foo.close
expect(foo.aasm(:left).states(:permitted => true)).to be_empty
# 2nd state machine
states = foo.aasm(:right).states
expect(states.size).to eql 3
expect(states).to include(:green)
expect(states).to include(:yellow)
expect(states).to include(:red)
states = foo.aasm(:right).states(:permitted => true)
expect(states.size).to eql 1
expect(states).to include(:yellow)
expect(states).not_to include(:green)
expect(states).not_to include(:red)
foo.yellow
states = foo.aasm(:right).states(:permitted => true)
expect(states.size).to eql 2
expect(states).to include(:red)
expect(states).to include(:green)
expect(states).not_to include(:yellow)
end
it "delivers all states for subclasses" do
# 1st state machine
states = two.aasm(:left).states
expect(states.size).to eql 4
expect(states).to include(:open)
expect(states).to include(:closed)
expect(states).to include(:final)
expect(states).to include(:foo)
states = two.aasm(:left).states(:permitted => true)
expect(states.size).to eql 1
expect(states).to include(:closed)
expect(states).not_to include(:open)
two.close
expect(two.aasm(:left).states(:permitted => true)).to be_empty
# 2nd state machine
states = two.aasm(:right).states
expect(states.size).to eql 4
expect(states).to include(:green)
expect(states).to include(:yellow)
expect(states).to include(:red)
expect(states).to include(:bar)
states = two.aasm(:right).states(:permitted => true)
expect(states.size).to eql 1
expect(states).to include(:yellow)
expect(states).not_to include(:red)
expect(states).not_to include(:green)
expect(states).not_to include(:bar)
two.yellow
states = two.aasm(:right).states(:permitted => true)
expect(states.size).to eql 2
expect(states).to include(:green)
expect(states).to include(:red)
expect(states).not_to include(:yellow)
expect(states).not_to include(:bar)
end
it "delivers all events" do
# 1st state machine
events = foo.aasm(:left).events
expect(events.size).to eql 2
expect(events).to include(:close)
expect(events).to include(:null)
foo.close
expect(foo.aasm(:left).events).to be_empty
# 2nd state machine
events = foo.aasm(:right).events
expect(events.size).to eql 1
expect(events).to include(:yellow)
expect(events).not_to include(:green)
expect(events).not_to include(:red)
foo.yellow
events = foo.aasm(:right).events
expect(events.size).to eql 2
expect(events).to include(:green)
expect(events).to include(:red)
expect(events).not_to include(:yellow)
end
end
it 'should list states in the order they have been defined' do
expect(ConversationMultiple.aasm(:left).states).to eq([
:needs_attention, :read, :closed, :awaiting_response, :junk
])
end
end
describe "special cases" do
it "should support valid as state name" do
expect(ValidStateNameMultiple.aasm(:left).states).to include(:invalid)
expect(ValidStateNameMultiple.aasm(:left).states).to include(:valid)
argument = ValidStateNameMultiple.new
expect(argument.invalid?).to be_truthy
expect(argument.aasm(:left).current_state).to eq(:invalid)
argument.valid!
expect(argument.valid?).to be_truthy
expect(argument.aasm(:left).current_state).to eq(:valid)
end
end
describe 'aasm.states_for_select' do
it "should return a select friendly array of states" do
expect(FooMultiple.aasm(:left)).to respond_to(:states_for_select)
expect(FooMultiple.aasm(:left).states_for_select).to eq(
[['Open', 'open'], ['Closed', 'closed'], ['Final', 'final']]
)
end
end
describe 'aasm.from_states_for_state' do
it "should return all from states for a state" do
expect(ComplexExampleMultiple.aasm(:left)).to respond_to(:from_states_for_state)
froms = ComplexExampleMultiple.aasm(:left).from_states_for_state(:active)
[:pending, :passive, :suspended].each {|from| expect(froms).to include(from)}
end
it "should return from states for a state for a particular transition only" do
froms = ComplexExampleMultiple.aasm(:left).from_states_for_state(:active, :transition => :left_unsuspend)
[:suspended].each {|from| expect(froms).to include(from)}
end
end
describe 'permitted events' do
let(:foo) {FooMultiple.new}
it 'work' do
expect(foo.aasm(:left).events(:permitted => true)).to include(:close)
expect(foo.aasm(:left).events(:permitted => true)).not_to include(:null)
expect(foo.aasm(:right).events(:permitted => true)).to include(:yellow)
expect(foo.aasm(:right).events(:permitted => true)).not_to include(:green)
expect(foo.aasm(:right).events(:permitted => true)).not_to include(:red)
end
end

View file

@ -0,0 +1,560 @@
require 'active_record'
require 'spec_helper'
Dir[File.dirname(__FILE__) + "/../../models/active_record/*.rb"].sort.each do |f|
require File.expand_path(f)
end
load_schema
# if you want to see the statements while running the spec enable the following line
# require 'logger'
# ActiveRecord::Base.logger = Logger.new(STDERR)
describe "instance methods" do
let(:gate) {MultipleGate.new}
it "should respond to aasm persistence methods" do
expect(gate).to respond_to(:aasm_read_state)
expect(gate).to respond_to(:aasm_write_state)
expect(gate).to respond_to(:aasm_write_state_without_persistence)
end
describe "aasm_column_looks_like_enum" do
subject { lambda{ gate.send(:aasm_column_looks_like_enum, :left) } }
let(:column_name) { "value" }
let(:columns_hash) { Hash[column_name, column] }
before :each do
allow(gate.class.aasm(:left)).to receive(:attribute_name).and_return(column_name.to_sym)
allow(gate.class).to receive(:columns_hash).and_return(columns_hash)
end
context "when AASM column has integer type" do
let(:column) { double(Object, type: :integer) }
it "returns true" do
expect(subject.call).to be_truthy
end
end
context "when AASM column has string type" do
let(:column) { double(Object, type: :string) }
it "returns false" do
expect(subject.call).to be_falsey
end
end
end
describe "aasm_guess_enum_method" do
subject { lambda{ gate.send(:aasm_guess_enum_method, :left) } }
before :each do
allow(gate.class.aasm(:left)).to receive(:attribute_name).and_return(:value)
end
it "pluralizes AASM column name" do
expect(subject.call).to eq :values
end
end
describe "aasm_enum" do
context "when AASM enum setting contains an explicit enum method name" do
let(:with_enum) { MultipleWithEnum.new }
it "returns whatever value was set in AASM config" do
expect(with_enum.send(:aasm_enum, :left)).to eq :test
end
end
context "when AASM enum setting is simply set to true" do
let(:with_true_enum) { MultipleWithTrueEnum.new }
before :each do
allow(MultipleWithTrueEnum.aasm(:left)).to receive(:attribute_name).and_return(:value)
end
it "infers enum method name from pluralized column name" do
expect(with_true_enum.send(:aasm_enum, :left)).to eq :values
end
end
context "when AASM enum setting is explicitly disabled" do
let(:with_false_enum) { MultipleWithFalseEnum.new }
it "returns nil" do
expect(with_false_enum.send(:aasm_enum, :left)).to be_nil
end
end
context "when AASM enum setting is not enabled" do
before :each do
allow(MultipleGate.aasm(:left)).to receive(:attribute_name).and_return(:value)
end
context "when AASM column looks like enum" do
before :each do
allow(gate).to receive(:aasm_column_looks_like_enum).with(:left).and_return(true)
end
it "infers enum method name from pluralized column name" do
expect(gate.send(:aasm_enum, :left)).to eq :values
end
end
context "when AASM column doesn't look like enum'" do
before :each do
allow(gate).to receive(:aasm_column_looks_like_enum)
.and_return(false)
end
it "returns nil, as we're not using enum" do
expect(gate.send(:aasm_enum, :left)).to be_nil
end
end
end
end
context "when AASM is configured to use enum" do
let(:state_sym) { :running }
let(:state_code) { 2 }
let(:enum_name) { :states }
let(:enum) { Hash[state_sym, state_code] }
before :each do
allow(gate).to receive(:aasm_enum).and_return(enum_name)
allow(gate).to receive(:aasm_write_attribute)
allow(gate).to receive(:write_attribute)
allow(MultipleGate).to receive(enum_name).and_return(enum)
end
describe "aasm_write_state" do
context "when AASM is configured to skip validations on save" do
before :each do
allow(gate).to receive(:aasm_skipping_validations).and_return(true)
end
it "passes state code instead of state symbol to update_all" do
# stub_chain does not allow us to give expectations on call
# parameters in the middle of the chain, so we need to use
# intermediate object instead.
obj = double(Object, update_all: 1)
allow(MultipleGate).to receive(:where).and_return(obj)
gate.aasm_write_state state_sym, :left
expect(obj).to have_received(:update_all)
.with(Hash[gate.class.aasm(:left).attribute_name, state_code])
end
end
context "when AASM is not skipping validations" do
it "delegates state update to the helper method" do
# Let's pretend that validation is passed
allow(gate).to receive(:save).and_return(true)
gate.aasm_write_state state_sym, :left
expect(gate).to have_received(:aasm_write_attribute).with(state_sym, :left)
expect(gate).to_not have_received :write_attribute
end
end
end
describe "aasm_write_state_without_persistence" do
it "delegates state update to the helper method" do
gate.aasm_write_state_without_persistence state_sym, :left
expect(gate).to have_received(:aasm_write_attribute).with(state_sym, :left)
expect(gate).to_not have_received :write_attribute
end
end
describe "aasm_raw_attribute_value" do
it "converts state symbol to state code" do
expect(gate.send(:aasm_raw_attribute_value, state_sym))
.to eq state_code
end
end
end
context "when AASM is configured to use string field" do
let(:state_sym) { :running }
before :each do
allow(gate).to receive(:aasm_enum).and_return(nil)
end
describe "aasm_raw_attribute_value" do
it "converts state symbol to string" do
expect(gate.send(:aasm_raw_attribute_value, state_sym))
.to eq state_sym.to_s
end
end
end
describe "aasm_write_attribute helper method" do
let(:sym) { :sym }
let(:value) { 42 }
before :each do
allow(gate).to receive(:write_attribute)
allow(gate).to receive(:aasm_raw_attribute_value).and_return(value)
gate.send(:aasm_write_attribute, sym, :left)
end
it "generates attribute value using a helper method" do
expect(gate).to have_received(:aasm_raw_attribute_value).with(sym, :left)
end
it "writes attribute to the model" do
expect(gate).to have_received(:write_attribute).with(:aasm_state, value)
end
end
it "should return the initial state when new and the aasm field is nil" do
expect(gate.aasm(:left).current_state).to eq(:opened)
end
it "should return the aasm column when new and the aasm field is not nil" do
gate.aasm_state = "closed"
expect(gate.aasm(:left).current_state).to eq(:closed)
end
it "should return the aasm column when not new and the aasm.attribute_name is not nil" do
allow(gate).to receive(:new_record?).and_return(false)
gate.aasm_state = "state"
expect(gate.aasm(:left).current_state).to eq(:state)
end
it "should allow a nil state" do
allow(gate).to receive(:new_record?).and_return(false)
gate.aasm_state = nil
expect(gate.aasm(:left).current_state).to be_nil
end
context 'on initialization' do
it "should initialize the aasm state" do
expect(MultipleGate.new.aasm_state).to eql 'opened'
expect(MultipleGate.new.aasm(:left).current_state).to eql :opened
end
it "should not initialize the aasm state if it has not been loaded" do
# we have to create a gate in the database, for which we only want to
# load the id, and not the state
gate = MultipleGate.create!
# then we just load the gate ids
MultipleGate.select(:id).where(id: gate.id).first
end
end
end
if ActiveRecord::VERSION::MAJOR < 4 && ActiveRecord::VERSION::MINOR < 2 # won't work with Rails >= 4.2
describe "direct state column access" do
it "accepts false states" do
f = MultipleFalseState.create!
expect(f.aasm_state).to eql false
expect {
f.aasm(:left).events.map(&:name)
}.to_not raise_error
end
end
end
describe 'subclasses' do
it "should have the same states as its parent class" do
expect(MultipleDerivateNewDsl.aasm(:left).states).to eq(MultipleSimpleNewDsl.aasm(:left).states)
end
it "should have the same events as its parent class" do
expect(MultipleDerivateNewDsl.aasm(:left).events).to eq(MultipleSimpleNewDsl.aasm(:left).events)
end
it "should have the same column as its parent even for the new dsl" do
expect(MultipleSimpleNewDsl.aasm(:left).attribute_name).to eq(:status)
expect(MultipleDerivateNewDsl.aasm(:left).attribute_name).to eq(:status)
end
end
describe "named scopes with the new DSL" do
context "Does not already respond_to? the scope name" do
it "should add a scope" do
expect(MultipleSimpleNewDsl).to respond_to(:unknown_scope)
expect(MultipleSimpleNewDsl.unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
end
end
context "Already respond_to? the scope name" do
it "should not add a scope" do
expect(MultipleSimpleNewDsl).to respond_to(:new)
expect(MultipleSimpleNewDsl.new.class).to eq(MultipleSimpleNewDsl)
end
end
it "does not create scopes if requested" do
expect(MultipleNoScope).not_to respond_to(:pending)
end
end # scopes
describe "direct assignment" do
it "is allowed by default" do
obj = MultipleNoScope.create
expect(obj.aasm_state.to_sym).to eql :pending
obj.aasm_state = :running
expect(obj.aasm_state.to_sym).to eql :running
end
it "is forbidden if configured" do
obj = MultipleNoDirectAssignment.create
expect(obj.aasm_state.to_sym).to eql :pending
expect {obj.aasm_state = :running}.to raise_error(AASM::NoDirectAssignmentError)
expect(obj.aasm_state.to_sym).to eql :pending
end
it 'can be turned off and on again' do
obj = MultipleNoDirectAssignment.create
expect(obj.aasm_state.to_sym).to eql :pending
expect {obj.aasm_state = :running}.to raise_error(AASM::NoDirectAssignmentError)
expect(obj.aasm_state.to_sym).to eql :pending
# allow it temporarily
MultipleNoDirectAssignment.aasm(:left).state_machine.config.no_direct_assignment = false
obj.aasm_state = :pending
expect(obj.aasm_state.to_sym).to eql :pending
# and forbid it again
MultipleNoDirectAssignment.aasm(:left).state_machine.config.no_direct_assignment = true
expect {obj.aasm_state = :running}.to raise_error(AASM::NoDirectAssignmentError)
expect(obj.aasm_state.to_sym).to eql :pending
end
end # direct assignment
describe 'initial states' do
it 'should support conditions' do
expect(MultipleThief.new(:skilled => true).aasm(:left).current_state).to eq(:rich)
expect(MultipleThief.new(:skilled => false).aasm(:left).current_state).to eq(:jailed)
end
end
describe 'transitions with persistence' do
it "should work for valid models" do
valid_object = MultipleValidator.create(:name => 'name')
expect(valid_object).to be_sleeping
valid_object.status = :running
expect(valid_object).to be_running
end
it 'should not store states for invalid models' do
validator = MultipleValidator.create(:name => 'name')
expect(validator).to be_valid
expect(validator).to be_sleeping
validator.name = nil
expect(validator).not_to be_valid
expect(validator.run!).to be_falsey
expect(validator).to be_sleeping
validator.reload
expect(validator).not_to be_running
expect(validator).to be_sleeping
validator.name = 'another name'
expect(validator).to be_valid
expect(validator.run!).to be_truthy
expect(validator).to be_running
validator.reload
expect(validator).to be_running
expect(validator).not_to be_sleeping
end
it 'should store states for invalid models if configured' do
persistor = MultipleInvalidPersistor.create(:name => 'name')
expect(persistor).to be_valid
expect(persistor).to be_sleeping
persistor.name = nil
expect(persistor).not_to be_valid
expect(persistor.run!).to be_truthy
expect(persistor).to be_running
persistor = MultipleInvalidPersistor.find(persistor.id)
persistor.valid?
expect(persistor).to be_valid
expect(persistor).to be_running
expect(persistor).not_to be_sleeping
persistor.reload
expect(persistor).to be_running
expect(persistor).not_to be_sleeping
end
describe 'transactions' do
let(:worker) { Worker.create!(:name => 'worker', :status => 'sleeping') }
let(:transactor) { MultipleTransactor.create!(:name => 'transactor', :worker => worker) }
it 'should rollback all changes' do
expect(transactor).to be_sleeping
expect(worker.status).to eq('sleeping')
expect {transactor.run!}.to raise_error(StandardError, 'failed on purpose')
expect(transactor).to be_running
expect(worker.reload.status).to eq('sleeping')
end
context "nested transactions" do
it "should rollback all changes in nested transaction" do
expect(transactor).to be_sleeping
expect(worker.status).to eq('sleeping')
Worker.transaction do
expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
end
expect(transactor).to be_running
expect(worker.reload.status).to eq('sleeping')
end
it "should only rollback changes in the main transaction not the nested one" do
# change configuration to not require new transaction
AASM::StateMachine[MultipleTransactor][:left].config.requires_new_transaction = false
expect(transactor).to be_sleeping
expect(worker.status).to eq('sleeping')
Worker.transaction do
expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
end
expect(transactor).to be_running
expect(worker.reload.status).to eq('running')
end
end
describe "after_commit callback" do
it "should fire :after_commit if transaction was successful" do
validator = MultipleValidator.create(:name => 'name')
expect(validator).to be_sleeping
validator.run!
expect(validator).to be_running
expect(validator.name).to eq("name changed")
validator.sleep!("sleeper")
expect(validator).to be_sleeping
expect(validator.name).to eq("sleeper")
end
it "should not fire :after_commit if transaction failed" do
validator = MultipleValidator.create(:name => 'name')
expect { validator.fail! }.to raise_error(StandardError, 'failed on purpose')
expect(validator.name).to eq("name")
end
it "should not fire if not saving" do
validator = MultipleValidator.create(:name => 'name')
expect(validator).to be_sleeping
validator.run
expect(validator).to be_running
expect(validator.name).to eq("name")
end
end
context "when not persisting" do
it 'should not rollback all changes' do
expect(transactor).to be_sleeping
expect(worker.status).to eq('sleeping')
# Notice here we're calling "run" and not "run!" with a bang.
expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
expect(transactor).to be_running
expect(worker.reload.status).to eq('running')
end
it 'should not create a database transaction' do
expect(transactor.class).not_to receive(:transaction)
expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
end
end
end
end
describe "invalid states with persistence" do
it "should not store states" do
validator = MultipleValidator.create(:name => 'name')
validator.status = 'invalid_state'
expect(validator.save).to be_falsey
expect {validator.save!}.to raise_error(ActiveRecord::RecordInvalid)
validator.reload
expect(validator).to be_sleeping
end
it "should store invalid states if configured" do
persistor = MultipleInvalidPersistor.create(:name => 'name')
persistor.status = 'invalid_state'
expect(persistor.save).to be_truthy
persistor.reload
expect(persistor.status).to eq('invalid_state')
end
end
describe "complex example" do
it "works" do
record = ComplexActiveRecordExample.new
expect_aasm_states record, :one, :alpha
record.save!
expect_aasm_states record, :one, :alpha
record.reload
expect_aasm_states record, :one, :alpha
record.increment!
expect_aasm_states record, :two, :alpha
record.reload
expect_aasm_states record, :two, :alpha
record.level_up!
expect_aasm_states record, :two, :beta
record.reload
expect_aasm_states record, :two, :beta
record.increment!
expect { record.increment! }.to raise_error(AASM::InvalidTransition)
expect_aasm_states record, :three, :beta
record.reload
expect_aasm_states record, :three, :beta
record.level_up!
expect_aasm_states record, :three, :gamma
record.reload
expect_aasm_states record, :three, :gamma
record.level_down # without saving
expect_aasm_states record, :three, :beta
record.reload
expect_aasm_states record, :three, :gamma
record.level_down # without saving
expect_aasm_states record, :three, :beta
record.reset!
expect_aasm_states record, :one, :beta
end
def expect_aasm_states(record, left_state, right_state)
expect(record.aasm(:left).current_state).to eql left_state.to_sym
expect(record.left).to eql left_state.to_s
expect(record.aasm(:right).current_state).to eql right_state.to_sym
expect(record.right).to eql right_state.to_s
end
end

View file

@ -1,19 +1,15 @@
require 'active_record'
require 'spec_helper'
Dir[File.dirname(__FILE__) + "/../../models/active_record/*.rb"].sort.each { |f| require File.expand_path(f) }
Dir[File.dirname(__FILE__) + "/../../models/active_record/*.rb"].sort.each do |f|
require File.expand_path(f)
end
load_schema
# if you want to see the statements while running the spec enable the following line
# require 'logger'
# ActiveRecord::Base.logger = Logger.new(STDERR)
shared_examples_for "aasm model" do
it "should include persistence mixins" do
expect(klass.included_modules).to be_include(AASM::Persistence::ActiveRecordPersistence)
expect(klass.included_modules).to be_include(AASM::Persistence::ActiveRecordPersistence::InstanceMethods)
end
end
describe "instance methods" do
let(:gate) {Gate.new}
@ -160,7 +156,7 @@ describe "instance methods" do
gate.aasm_write_state state_sym
expect(gate).to have_received(:aasm_write_attribute).with(state_sym)
expect(gate).to have_received(:aasm_write_attribute).with(state_sym, :default)
expect(gate).to_not have_received :write_attribute
end
end
@ -170,7 +166,7 @@ describe "instance methods" do
it "delegates state update to the helper method" do
gate.aasm_write_state_without_persistence state_sym
expect(gate).to have_received(:aasm_write_attribute).with(state_sym)
expect(gate).to have_received(:aasm_write_attribute).with(state_sym, :default)
expect(gate).to_not have_received :write_attribute
end
end
@ -210,7 +206,7 @@ describe "instance methods" do
end
it "generates attribute value using a helper method" do
expect(gate).to have_received(:aasm_raw_attribute_value).with(sym)
expect(gate).to have_received(:aasm_raw_attribute_value).with(sym, :default)
end
it "writes attribute to the model" do
@ -431,7 +427,7 @@ describe 'transitions with persistence' do
it "should only rollback changes in the main transaction not the nested one" do
# change configuration to not require new transaction
AASM::StateMachine[Transactor].config.requires_new_transaction = false
AASM::StateMachine[Transactor][:default].config.requires_new_transaction = false
expect(transactor).to be_sleeping
expect(worker.status).to eq('sleeping')
@ -516,3 +512,12 @@ describe "invalid states with persistence" do
end
end
describe 'basic example with two state machines' do
let(:example) { BasicActiveRecordTwoStateMachinesExample.new }
it 'should initialise properly' do
expect(example.aasm(:search).current_state).to eql :initialised
expect(example.aasm(:sync).current_state).to eql :unsynced
end
end

View file

@ -0,0 +1,146 @@
describe 'mongo_mapper' do
begin
require 'mongo_mapper'
require 'logger'
require 'spec_helper'
Dir[File.dirname(__FILE__) + "/../../models/mongo_mapper/*.rb"].sort.each do |f|
require File.expand_path(f)
end
before(:all) do
config = {
'test' => {
'database' => "mongo_mapper_#{Process.pid}"
}
}
MongoMapper.setup(config, 'test') #, :logger => Logger.new(STDERR))
end
after do
# Clear Out all non-system Mongo collections between tests
MongoMapper.database.collections.each do |collection|
collection.drop unless collection.capped? || (collection.name =~ /\Asystem/)
end
end
describe "named scopes with the old DSL" do
context "Does not already respond_to? the scope name" do
it "should add a scope" do
expect(SimpleMongoMapperMultiple).to respond_to(:unknown_scope)
expect(SimpleMongoMapperMultiple.unknown_scope.class).to eq(MongoMapper::Plugins::Querying::DecoratedPluckyQuery)
#expect(SimpleMongoMapperMultiple.unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
end
end
context "Already respond_to? the scope name" do
it "should not add a scope" do
expect(SimpleMongoMapperMultiple).to respond_to(:next)
expect(SimpleMongoMapperMultiple.new.class).to eq(SimpleMongoMapperMultiple)
end
end
end
describe "named scopes with the new DSL" do
context "Does not already respond_to? the scope name" do
it "should add a scope" do
expect(SimpleNewDslMongoMapperMultiple).to respond_to(:unknown_scope)
expect(SimpleNewDslMongoMapperMultiple.unknown_scope.class).to eq(MongoMapper::Plugins::Querying::DecoratedPluckyQuery)
end
end
context "Already respond_to? the scope name" do
it "should not add a scope" do
expect(SimpleNewDslMongoMapperMultiple).to respond_to(:next)
expect(SimpleNewDslMongoMapperMultiple.new.class).to eq(SimpleNewDslMongoMapperMultiple)
end
end
it "does not create scopes if requested" do
expect(NoScopeMongoMapperMultiple).not_to respond_to(:ignored_scope)
end
end
describe "instance methods" do
let(:simple) {SimpleNewDslMongoMapperMultiple.new}
it "should call aasm_ensure_initial_state on validation before create" do
expect(SimpleNewDslMongoMapperMultiple.aasm(:left).initial_state).to eq(:unknown_scope)
expect(SimpleNewDslMongoMapperMultiple.aasm(:left).attribute_name).to eq(:status)
expect(simple.status).to eq(nil)
simple.valid?
expect(simple.status).to eq('unknown_scope')
end
it "should call aasm_ensure_initial_state before create, even if skipping validations" do
expect(simple.status).to eq(nil)
simple.save(:validate => false)
expect(simple.status).to eq('unknown_scope')
end
end
describe "complex example" do
it "works" do
record = ComplexMongoMapperExample.new
expect(record.aasm(:left).current_state).to eql :one
expect(record.left).to be_nil
expect(record.aasm(:right).current_state).to eql :alpha
expect(record.right).to be_nil
record.save!
expect_aasm_states record, :one, :alpha
record.reload
expect_aasm_states record, :one, :alpha
record.increment!
expect_aasm_states record, :two, :alpha
record.reload
expect_aasm_states record, :two, :alpha
record.level_up!
expect_aasm_states record, :two, :beta
record.reload
expect_aasm_states record, :two, :beta
record.increment!
expect { record.increment! }.to raise_error(AASM::InvalidTransition)
expect_aasm_states record, :three, :beta
record.reload
expect_aasm_states record, :three, :beta
record.level_up!
expect_aasm_states record, :three, :gamma
record.reload
expect_aasm_states record, :three, :gamma
record.level_down # without saving
expect_aasm_states record, :three, :beta
record.reload
expect_aasm_states record, :three, :gamma
record.level_down # without saving
expect_aasm_states record, :three, :beta
record.reset!
expect_aasm_states record, :one, :beta
end
def expect_aasm_states(record, left_state, right_state)
expect(record.aasm(:left).current_state).to eql left_state.to_sym
expect(record.left).to eql left_state.to_s
expect(record.aasm(:right).current_state).to eql right_state.to_sym
expect(record.right).to eql right_state.to_s
end
end
rescue LoadError
puts "--------------------------------------------------------------------------"
puts "Not running MongoMapper specs because mongo_mapper gem is not installed!!!"
puts "--------------------------------------------------------------------------"
end
end

View file

@ -4,9 +4,11 @@ describe 'mongo_mapper' do
require 'logger'
require 'spec_helper'
before(:all) do
Dir[File.dirname(__FILE__) + "/../../models/mongo_mapper/*.rb"].sort.each { |f| require File.expand_path(f) }
Dir[File.dirname(__FILE__) + "/../../models/mongo_mapper/*.rb"].sort.each do |f|
require File.expand_path(f)
end
before(:all) do
config = {
'test' => {
'database' => "mongo_mapper_#{Process.pid}"
@ -64,52 +66,6 @@ describe 'mongo_mapper' do
end
describe "#find_in_state" do
let!(:model) { SimpleNewDslMongoMapper.create!(:status => :unknown_scope) }
let!(:model_id) { model._id }
it "should respond to method" do
expect(SimpleNewDslMongoMapper).to respond_to(:find_in_state)
end
it "should find the model when given the correct scope and model id" do
expect(SimpleNewDslMongoMapper.find_in_state(model_id, 'unknown_scope').class).to eq(SimpleNewDslMongoMapper)
expect(SimpleNewDslMongoMapper.find_in_state(model_id, 'unknown_scope')).to eq(model)
end
it "should raise DocumentNotFound error when given incorrect scope" do
expect {SimpleNewDslMongoMapper.find_in_state(model_id, 'next')}.to raise_error MongoMapper::DocumentNotFound
end
it "should raise DocumentNotFound error when given incorrect model id" do
expect {SimpleNewDslMongoMapper.find_in_state('bad_id', 'unknown_scope')}.to raise_error MongoMapper::DocumentNotFound
end
end
describe "#count_in_state" do
before do
3.times { SimpleNewDslMongoMapper.create!(:status => :unknown_scope) }
end
it "should respond to method" do
expect(SimpleNewDslMongoMapper).to respond_to(:count_in_state)
end
it "should return n for a scope with n records persisted" do
expect(SimpleNewDslMongoMapper.count_in_state('unknown_scope').class).to eq(Fixnum)
expect(SimpleNewDslMongoMapper.count_in_state('unknown_scope')).to eq(3)
end
it "should return zero for a scope without records persisted" do
expect(SimpleNewDslMongoMapper.count_in_state('next').class).to eq(Fixnum)
expect(SimpleNewDslMongoMapper.count_in_state('next')).to eq(0)
end
end
describe "instance methods" do
let(:simple) {SimpleNewDslMongoMapper.new}
@ -130,6 +86,8 @@ describe 'mongo_mapper' do
end
rescue LoadError
puts "Not running MongoMapper specs because mongo_mapper gem is not installed!!!"
puts "--------------------------------------------------------------------------"
puts "Not running MongoMapper multiple-specs because mongo_mapper gem is not installed!!!"
puts "--------------------------------------------------------------------------"
end
end

View file

@ -1,146 +0,0 @@
describe 'mongoid' do
begin
require 'mongoid'
require 'logger'
require 'spec_helper'
before(:all) do
Dir[File.dirname(__FILE__) + "/../../models/mongoid/*.rb"].sort.each { |f| require File.expand_path(f) }
# if you want to see the statements while running the spec enable the following line
# Mongoid.logger = Logger.new(STDERR)
DATABASE_NAME = "mongoid_#{Process.pid}"
Mongoid.configure do |config|
config.connect_to DATABASE_NAME
end
end
after do
Mongoid.purge!
end
describe "named scopes with the old DSL" do
context "Does not already respond_to? the scope name" do
it "should add a scope" do
expect(SimpleMongoid).to respond_to(:unknown_scope)
expect(SimpleMongoid.unknown_scope.class).to eq(Mongoid::Criteria)
end
end
context "Already respond_to? the scope name" do
it "should not add a scope" do
expect(SimpleMongoid).to respond_to(:new)
expect(SimpleMongoid.new.class).to eq(SimpleMongoid)
end
end
end
describe "named scopes with the new DSL" do
context "Does not already respond_to? the scope name" do
it "should add a scope" do
expect(SimpleNewDslMongoid).to respond_to(:unknown_scope)
expect(SimpleNewDslMongoid.unknown_scope.class).to eq(Mongoid::Criteria)
end
end
context "Already respond_to? the scope name" do
it "should not add a scope" do
expect(SimpleNewDslMongoid).to respond_to(:new)
expect(SimpleNewDslMongoid.new.class).to eq(SimpleNewDslMongoid)
end
end
it "does not create scopes if requested" do
expect(NoScopeMongoid).not_to respond_to(:ignored_scope)
end
end
describe "#find_in_state" do
let!(:model) { SimpleNewDslMongoid.create!(:status => :unknown_scope) }
let!(:model_id) { model._id }
it "should respond to method" do
expect(SimpleNewDslMongoid).to respond_to(:find_in_state)
end
it "should find the model when given the correct scope and model id" do
expect(SimpleNewDslMongoid.find_in_state(model_id, 'unknown_scope').class).to eq(SimpleNewDslMongoid)
expect(SimpleNewDslMongoid.find_in_state(model_id, 'unknown_scope')).to eq(model)
end
it "should raise DocumentNotFound error when given incorrect scope" do
expect {SimpleNewDslMongoid.find_in_state(model_id, 'new')}.to raise_error Mongoid::Errors::DocumentNotFound
end
it "should raise DocumentNotFound error when given incorrect model id" do
expect {SimpleNewDslMongoid.find_in_state('bad_id', 'unknown_scope')}.to raise_error Mongoid::Errors::DocumentNotFound
end
end
describe "#count_in_state" do
before do
3.times { SimpleNewDslMongoid.create!(:status => :unknown_scope) }
end
it "should respond to method" do
expect(SimpleNewDslMongoid).to respond_to(:count_in_state)
end
it "should return n for a scope with n records persisted" do
expect(SimpleNewDslMongoid.count_in_state('unknown_scope').class).to eq(Fixnum)
expect(SimpleNewDslMongoid.count_in_state('unknown_scope')).to eq(3)
end
it "should return zero for a scope without records persisted" do
expect(SimpleNewDslMongoid.count_in_state('new').class).to eq(Fixnum)
expect(SimpleNewDslMongoid.count_in_state('new')).to eq(0)
end
end
describe "#with_state_scope" do
before do
3.times { SimpleNewDslMongoid.create!(:status => :unknown_scope) }
2.times { SimpleNewDslMongoid.create!(:status => :new) }
end
it "should respond to method" do
expect(SimpleNewDslMongoid).to respond_to(:with_state_scope)
end
it "should correctly process block" do
expect(SimpleNewDslMongoid.with_state_scope('unknown_scope') do
SimpleNewDslMongoid.count
end).to eq(3)
expect(SimpleNewDslMongoid.with_state_scope('new') do
SimpleNewDslMongoid.count
end).to eq(2)
end
end
describe "instance methods" do
let(:simple) {SimpleNewDslMongoid.new}
it "should initialize the aasm state on instantiation" do
expect(SimpleNewDslMongoid.new.status).to eql 'unknown_scope'
expect(SimpleNewDslMongoid.new.aasm.current_state).to eql :unknown_scope
end
end
rescue LoadError
puts "Not running Mongoid specs because mongoid gem is not installed!!!"
end
end

View file

@ -0,0 +1,127 @@
describe 'mongoid' do
begin
require 'mongoid'
require 'logger'
require 'spec_helper'
Dir[File.dirname(__FILE__) + "/../../models/mongoid/*.rb"].sort.each do |f|
require File.expand_path(f)
end
before(:all) do
# if you want to see the statements while running the spec enable the following line
# Mongoid.logger = Logger.new(STDERR)
Mongoid.configure do |config|
config.connect_to "mongoid_#{Process.pid}"
end
end
after do
Mongoid.purge!
end
describe "named scopes with the old DSL" do
context "Does not already respond_to? the scope name" do
it "should add a scope" do
expect(SimpleMongoidMultiple).to respond_to(:unknown_scope)
expect(SimpleMongoidMultiple.unknown_scope.class).to eq(Mongoid::Criteria)
end
end
context "Already respond_to? the scope name" do
it "should not add a scope" do
expect(SimpleMongoidMultiple).to respond_to(:new)
expect(SimpleMongoidMultiple.new.class).to eq(SimpleMongoidMultiple)
end
end
end
describe "named scopes with the new DSL" do
context "Does not already respond_to? the scope name" do
it "should add a scope" do
expect(SimpleNewDslMongoidMultiple).to respond_to(:unknown_scope)
expect(SimpleNewDslMongoidMultiple.unknown_scope.class).to eq(Mongoid::Criteria)
end
end
context "Already respond_to? the scope name" do
it "should not add a scope" do
expect(SimpleNewDslMongoidMultiple).to respond_to(:new)
expect(SimpleNewDslMongoidMultiple.new.class).to eq(SimpleNewDslMongoidMultiple)
end
end
it "does not create scopes if requested" do
expect(NoScopeMongoidMultiple).not_to respond_to(:ignored_scope)
end
end
describe "instance methods" do
let(:simple) {SimpleNewDslMongoidMultiple.new}
it "should initialize the aasm state on instantiation" do
expect(SimpleNewDslMongoidMultiple.new.status).to eql 'unknown_scope'
expect(SimpleNewDslMongoidMultiple.new.aasm(:left).current_state).to eql :unknown_scope
end
end
describe "complex example" do
it "works" do
record = ComplexMongoidExample.new
expect_aasm_states record, :one, :alpha
record.save!
expect_aasm_states record, :one, :alpha
record.reload
expect_aasm_states record, :one, :alpha
record.increment!
expect_aasm_states record, :two, :alpha
record.reload
expect_aasm_states record, :two, :alpha
record.level_up!
expect_aasm_states record, :two, :beta
record.reload
expect_aasm_states record, :two, :beta
record.increment!
expect { record.increment! }.to raise_error(AASM::InvalidTransition)
expect_aasm_states record, :three, :beta
record.reload
expect_aasm_states record, :three, :beta
record.level_up!
expect_aasm_states record, :three, :gamma
record.reload
expect_aasm_states record, :three, :gamma
record.level_down # without saving
expect_aasm_states record, :three, :beta
record.reload
expect_aasm_states record, :three, :gamma
record.level_down # without saving
expect_aasm_states record, :three, :beta
record.reset!
expect_aasm_states record, :one, :beta
end
def expect_aasm_states(record, left_state, right_state)
expect(record.aasm(:left).current_state).to eql left_state.to_sym
expect(record.left).to eql left_state.to_s
expect(record.aasm(:right).current_state).to eql right_state.to_sym
expect(record.right).to eql right_state.to_s
end
end
rescue LoadError
puts "--------------------------------------------------------------------------"
puts "Not running Mongoid multiple-specs because mongoid gem is not installed!!!"
puts "--------------------------------------------------------------------------"
end
end

View file

@ -0,0 +1,79 @@
describe 'mongoid' do
begin
require 'mongoid'
require 'logger'
require 'spec_helper'
Dir[File.dirname(__FILE__) + "/../../models/mongoid/*.rb"].sort.each do |f|
require File.expand_path(f)
end
before(:all) do
# if you want to see the statements while running the spec enable the following line
# Mongoid.logger = Logger.new(STDERR)
Mongoid.configure do |config|
config.connect_to "mongoid_#{Process.pid}"
end
end
after do
Mongoid.purge!
end
describe "named scopes with the old DSL" do
context "Does not already respond_to? the scope name" do
it "should add a scope" do
expect(SimpleMongoid).to respond_to(:unknown_scope)
expect(SimpleMongoid.unknown_scope.class).to eq(Mongoid::Criteria)
end
end
context "Already respond_to? the scope name" do
it "should not add a scope" do
expect(SimpleMongoid).to respond_to(:new)
expect(SimpleMongoid.new.class).to eq(SimpleMongoid)
end
end
end
describe "named scopes with the new DSL" do
context "Does not already respond_to? the scope name" do
it "should add a scope" do
expect(SimpleNewDslMongoid).to respond_to(:unknown_scope)
expect(SimpleNewDslMongoid.unknown_scope.class).to eq(Mongoid::Criteria)
end
end
context "Already respond_to? the scope name" do
it "should not add a scope" do
expect(SimpleNewDslMongoid).to respond_to(:new)
expect(SimpleNewDslMongoid.new.class).to eq(SimpleNewDslMongoid)
end
end
it "does not create scopes if requested" do
expect(NoScopeMongoid).not_to respond_to(:ignored_scope)
end
end
describe "instance methods" do
let(:simple) {SimpleNewDslMongoid.new}
it "should initialize the aasm state on instantiation" do
expect(SimpleNewDslMongoid.new.status).to eql 'unknown_scope'
expect(SimpleNewDslMongoid.new.aasm.current_state).to eql :unknown_scope
end
end
rescue LoadError
puts "--------------------------------------------------------------------------"
puts "Not running Mongoid specs because mongoid gem is not installed!!!"
puts "--------------------------------------------------------------------------"
end
end

View file

@ -0,0 +1,153 @@
describe 'sequel' do
begin
require 'sequel'
require 'logger'
require 'spec_helper'
Dir[File.dirname(__FILE__) + "/../../models/sequel/*.rb"].sort.each do |f|
require File.expand_path(f)
end
before(:all) do
@model = SequelMultiple
end
describe "instance methods" do
let(:model) {@model.new}
it "should respond to aasm persistence methods" do
expect(model).to respond_to(:aasm_read_state)
expect(model).to respond_to(:aasm_write_state)
expect(model).to respond_to(:aasm_write_state_without_persistence)
end
it "should return the initial state when new and the aasm field is nil" do
expect(model.aasm(:left).current_state).to eq(:alpha)
end
it "should save the initial state" do
model.save
expect(model.status).to eq("alpha")
end
it "should return the aasm column when new and the aasm field is not nil" do
model.status = "beta"
expect(model.aasm(:left).current_state).to eq(:beta)
end
it "should return the aasm column when not new and the aasm_column is not nil" do
allow(model).to receive(:new?).and_return(false)
model.status = "gamma"
expect(model.aasm(:left).current_state).to eq(:gamma)
end
it "should allow a nil state" do
allow(model).to receive(:new?).and_return(false)
model.status = nil
expect(model.aasm(:left).current_state).to be_nil
end
it "should not change the state if state is not loaded" do
model.release
model.save
model.class.select(:id).first.save
model.reload
expect(model.aasm(:left).current_state).to eq(:beta)
end
it "should call aasm_ensure_initial_state on validation before create" do
expect(model).to receive(:aasm_ensure_initial_state).and_return(true)
model.valid?
end
it "should call aasm_ensure_initial_state before create, even if skipping validations" do
expect(model).to receive(:aasm_ensure_initial_state).and_return(true)
model.save(:validate => false)
end
end
describe 'subclasses' do
it "should have the same states as its parent class" do
expect(Class.new(@model).aasm(:left).states).to eq(@model.aasm(:left).states)
end
it "should have the same events as its parent class" do
expect(Class.new(@model).aasm(:left).events).to eq(@model.aasm(:left).events)
end
it "should have the same column as its parent even for the new dsl" do
expect(@model.aasm(:left).attribute_name).to eq(:status)
expect(Class.new(@model).aasm(:left).attribute_name).to eq(:status)
end
end
describe 'initial states' do
it 'should support conditions' do
@model.aasm(:left) do
initial_state lambda{ |m| m.default }
end
expect(@model.new(:default => :beta).aasm(:left).current_state).to eq(:beta)
expect(@model.new(:default => :gamma).aasm(:left).current_state).to eq(:gamma)
end
end
describe "complex example" do
it "works" do
record = ComplexSequelExample.new
expect(record.aasm(:left).current_state).to eql :one
expect(record.left).to be_nil
expect(record.aasm(:right).current_state).to eql :alpha
expect(record.right).to be_nil
record.save
expect_aasm_states record, :one, :alpha
record.reload
expect_aasm_states record, :one, :alpha
record.increment!
expect_aasm_states record, :two, :alpha
record.reload
expect_aasm_states record, :two, :alpha
record.level_up!
expect_aasm_states record, :two, :beta
record.reload
expect_aasm_states record, :two, :beta
record.increment!
expect { record.increment! }.to raise_error(AASM::InvalidTransition)
expect_aasm_states record, :three, :beta
record.reload
expect_aasm_states record, :three, :beta
record.level_up!
expect_aasm_states record, :three, :gamma
record.reload
expect_aasm_states record, :three, :gamma
record.level_down # without saving
expect_aasm_states record, :three, :beta
record.reload
expect_aasm_states record, :three, :gamma
record.level_down # without saving
expect_aasm_states record, :three, :beta
record.reset!
expect_aasm_states record, :one, :beta
end
def expect_aasm_states(record, left_state, right_state)
expect(record.aasm(:left).current_state).to eql left_state.to_sym
expect(record.left).to eql left_state.to_s
expect(record.aasm(:right).current_state).to eql right_state.to_sym
expect(record.right).to eql right_state.to_s
end
end
rescue LoadError
puts "------------------------------------------------------------------------"
puts "Not running Sequel multiple-specs because sequel gem is not installed!!!"
puts "------------------------------------------------------------------------"
end
end

View file

@ -1,34 +1,15 @@
describe 'sequel' do
begin
require 'sequel'
require 'logger'
require 'spec_helper'
Dir[File.dirname(__FILE__) + "/../../models/sequel/*.rb"].sort.each do |f|
require File.expand_path(f)
end
before(:all) do
db = Sequel.connect(SEQUEL_DB)
# if you want to see the statements while running the spec enable the following line
# db.loggers << Logger.new($stderr)
db.create_table(:models) do
primary_key :id
String :status
end
@model = Class.new(Sequel::Model(db)) do
set_dataset(:models)
attr_accessor :default
include AASM
aasm :column => :status
aasm do
state :alpha, :initial => true
state :beta
state :gamma
event :release do
transitions :from => [:alpha, :beta, :gamma], :to => :beta
end
end
end
@model = SequelSimple
end
describe "instance methods" do
@ -112,6 +93,8 @@ describe 'sequel' do
end
rescue LoadError
puts "------------------------------------------------------------------------"
puts "Not running Sequel specs because sequel gem is not installed!!!"
puts "------------------------------------------------------------------------"
end
end

View file

@ -1,6 +1,6 @@
require 'spec_helper'
describe 'callbacks for the new DSL' do
describe 'when redefining states' do
let(:definer) { DoubleDefiner.new }
it "allows extending states" do

View file

@ -0,0 +1,63 @@
require 'spec_helper'
describe 'state machine' do
let(:simple) { SimpleMultipleExample.new }
it 'starts with an initial state' do
expect(simple.aasm(:move).current_state).to eq(:standing)
expect(simple).to respond_to(:standing?)
expect(simple).to be_standing
expect(simple.aasm(:work).current_state).to eq(:sleeping)
expect(simple).to respond_to(:sleeping?)
expect(simple).to be_sleeping
end
it 'allows transitions to other states' do
expect(simple).to respond_to(:walk)
expect(simple).to respond_to(:walk!)
simple.walk!
expect(simple).to respond_to(:walking?)
expect(simple).to be_walking
expect(simple).to respond_to(:run)
expect(simple).to respond_to(:run!)
simple.run
expect(simple).to respond_to(:running?)
expect(simple).to be_running
expect(simple).to respond_to(:start)
expect(simple).to respond_to(:start!)
simple.start
expect(simple).to respond_to(:processing?)
expect(simple).to be_processing
end
it 'denies transitions to other states' do
expect {simple.hold}.to raise_error(AASM::InvalidTransition)
expect {simple.hold!}.to raise_error(AASM::InvalidTransition)
simple.walk
expect {simple.walk}.to raise_error(AASM::InvalidTransition)
expect {simple.walk!}.to raise_error(AASM::InvalidTransition)
simple.run
expect {simple.walk}.to raise_error(AASM::InvalidTransition)
expect {simple.walk!}.to raise_error(AASM::InvalidTransition)
expect {simple.stop}.to raise_error(AASM::InvalidTransition)
expect {simple.stop!}.to raise_error(AASM::InvalidTransition)
simple.start
expect {simple.start}.to raise_error(AASM::InvalidTransition)
expect {simple.start!}.to raise_error(AASM::InvalidTransition)
simple.stop
end
it 'defines constants for each state name' do
expect(SimpleMultipleExample::STATE_STANDING).to eq(:standing)
expect(SimpleMultipleExample::STATE_WALKING).to eq(:walking)
expect(SimpleMultipleExample::STATE_RUNNING).to eq(:running)
expect(SimpleMultipleExample::STATE_SLEEPING).to eq(:sleeping)
expect(SimpleMultipleExample::STATE_PROCESSING).to eq(:processing)
expect(SimpleMultipleExample::STATE_RUNNING).to eq(:running)
end
end

View file

@ -1,13 +1,15 @@
require 'spec_helper'
describe AASM::Core::State do
let(:state_machine) { AASM::StateMachine.new(:name) }
before(:each) do
@name = :astate
@options = { :crazy_custom_key => 'key' }
end
def new_state(options={})
AASM::Core::State.new(@name, Conversation, @options.merge(options))
AASM::Core::State.new(@name, Conversation, state_machine, @options.merge(options))
end
it 'should set the name' do

View file

@ -0,0 +1,39 @@
require 'spec_helper'
describe 'subclassing with multiple state machines' do
it 'should have the parent states' do
SuperClassMultiple.aasm(:left).states.each do |state|
expect(SubClassWithMoreStatesMultiple.aasm(:left).states).to include(state)
end
expect(SubClassMultiple.aasm(:left).states).to eq(SuperClassMultiple.aasm(:left).states)
SuperClassMultiple.aasm(:right).states.each do |state|
expect(SubClassWithMoreStatesMultiple.aasm(:right).states).to include(state)
end
expect(SubClassMultiple.aasm(:right).states).to eq(SuperClassMultiple.aasm(:right).states)
end
it 'should not add the child states to the parent machine' do
expect(SuperClassMultiple.aasm(:left).states).not_to include(:foo)
expect(SuperClassMultiple.aasm(:right).states).not_to include(:archived)
end
it "should have the same events as its parent" do
expect(SubClassMultiple.aasm(:left).events).to eq(SuperClassMultiple.aasm(:left).events)
expect(SubClassMultiple.aasm(:right).events).to eq(SuperClassMultiple.aasm(:right).events)
end
it 'should know how to respond to question methods' do
expect(SubClassMultiple.new.may_foo?).to be_truthy
expect(SubClassMultiple.new.may_close?).to be_truthy
end
it 'should not break if I call methods from super class' do
son = SubClassMultiple.new
son.update_state
expect(son.aasm(:left).current_state).to eq(:ended)
end
end

View file

@ -53,13 +53,13 @@ describe 'transitions' do
end
describe 'blocks' do
end
describe AASM::Core::Transition do
let(:state_machine) { AASM::StateMachine.new(:name) }
let(:event) { AASM::Core::Event.new(:event, state_machine) }
it 'should set from, to, and opts attr readers' do
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
expect(st.from).to eq(opts[:from])
expect(st.to).to eq(opts[:to])
@ -71,7 +71,7 @@ describe AASM::Core::Transition do
st = AASM::Core::Transition.allocate
expect(st).to receive(:warn).with('[DEPRECATION] :on_transition is deprecated, use :after instead')
st.send :initialize, opts do
st.send :initialize, event, opts do
guard :gg
on_transition :after_callback
end
@ -81,7 +81,7 @@ describe AASM::Core::Transition do
it 'should set after and guard from dsl' do
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
st = AASM::Core::Transition.new(opts) do
st = AASM::Core::Transition.new(event, opts) do
guard :gg
after :after_callback
end
@ -92,7 +92,7 @@ describe AASM::Core::Transition do
it 'should pass equality check if from and to are the same' do
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
obj = double('object')
allow(obj).to receive(:from).and_return(opts[:from])
@ -103,7 +103,7 @@ describe AASM::Core::Transition do
it 'should fail equality check if from are not the same' do
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
obj = double('object')
allow(obj).to receive(:from).and_return('blah')
@ -114,7 +114,7 @@ describe AASM::Core::Transition do
it 'should fail equality check if to are not the same' do
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
obj = double('object')
allow(obj).to receive(:from).and_return(opts[:from])
@ -125,16 +125,19 @@ describe AASM::Core::Transition do
end
describe AASM::Core::Transition, '- when performing guard checks' do
let(:state_machine) { AASM::StateMachine.new(:name) }
let(:event) { AASM::Core::Event.new(:event, state_machine) }
it 'should return true of there is no guard' do
opts = {:from => 'foo', :to => 'bar'}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
expect(st.allowed?(nil)).to be_truthy
end
it 'should call the method on the object if guard is a symbol' do
opts = {:from => 'foo', :to => 'bar', :guard => :test}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
obj = double('object')
expect(obj).to receive(:test)
@ -144,7 +147,7 @@ describe AASM::Core::Transition, '- when performing guard checks' do
it 'should call the method on the object if unless is a symbol' do
opts = {:from => 'foo', :to => 'bar', :unless => :test}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
obj = double('object')
expect(obj).to receive(:test)
@ -154,7 +157,7 @@ describe AASM::Core::Transition, '- when performing guard checks' do
it 'should call the method on the object if guard is a string' do
opts = {:from => 'foo', :to => 'bar', :guard => 'test'}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
obj = double('object')
expect(obj).to receive(:test)
@ -164,7 +167,7 @@ describe AASM::Core::Transition, '- when performing guard checks' do
it 'should call the method on the object if unless is a string' do
opts = {:from => 'foo', :to => 'bar', :unless => 'test'}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
obj = double('object')
expect(obj).to receive(:test)
@ -174,7 +177,7 @@ describe AASM::Core::Transition, '- when performing guard checks' do
it 'should call the proc passing the object if the guard is a proc' do
opts = {:from => 'foo', :to => 'bar', :guard => Proc.new { test }}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
obj = double('object')
expect(obj).to receive(:test)
@ -184,9 +187,12 @@ describe AASM::Core::Transition, '- when performing guard checks' do
end
describe AASM::Core::Transition, '- when executing the transition with a Proc' do
let(:state_machine) { AASM::StateMachine.new(:name) }
let(:event) { AASM::Core::Event.new(:event, state_machine) }
it 'should call a Proc on the object with args' do
opts = {:from => 'foo', :to => 'bar', :after => Proc.new {|a| test(a) }}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm')
@ -202,7 +208,7 @@ describe AASM::Core::Transition, '- when executing the transition with a Proc' d
prc = Proc.new { prc_object = self }
opts = {:from => 'foo', :to => 'bar', :after => prc }
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm')
@ -212,9 +218,12 @@ describe AASM::Core::Transition, '- when executing the transition with a Proc' d
end
describe AASM::Core::Transition, '- when executing the transition with an :after method call' do
let(:state_machine) { AASM::StateMachine.new(:name) }
let(:event) { AASM::Core::Event.new(:event, state_machine) }
it 'should accept a String for the method name' do
opts = {:from => 'foo', :to => 'bar', :after => 'test'}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm')
@ -225,7 +234,7 @@ describe AASM::Core::Transition, '- when executing the transition with an :after
it 'should accept a Symbol for the method name' do
opts = {:from => 'foo', :to => 'bar', :after => :test}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm')
@ -236,7 +245,7 @@ describe AASM::Core::Transition, '- when executing the transition with an :after
it 'should pass args if the target method accepts them' do
opts = {:from => 'foo', :to => 'bar', :after => :test}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm')
@ -251,7 +260,7 @@ describe AASM::Core::Transition, '- when executing the transition with an :after
it 'should NOT pass args if the target method does NOT accept them' do
opts = {:from => 'foo', :to => 'bar', :after => :test}
st = AASM::Core::Transition.new(opts)
st = AASM::Core::Transition.new(event, opts)
args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm')
@ -266,7 +275,7 @@ describe AASM::Core::Transition, '- when executing the transition with an :after
it 'should allow accessing the from_state and the to_state' do
opts = {:from => 'foo', :to => 'bar', :after => :test}
transition = AASM::Core::Transition.new(opts)
transition = AASM::Core::Transition.new(event, opts)
args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => AASM::InstanceBase.new('object'))