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

Support for object proxies for RubyMotion !

This commit is contained in:
Yannick Rekinger 2016-03-19 03:41:04 +01:00
parent 79f9178286
commit 3214e46f74
14 changed files with 105 additions and 142 deletions

View file

@ -886,103 +886,6 @@ Now supports [CodeDataQuery](https://github.com/infinitered/cdq.git) !
However I'm still in the process of submitting my compatibility updates to their repository.
In the meantime you can use [my fork](https://github.com/Infotaku/cdq.git), there may still be some minor issues but I intend to extensively use it myself, so fixes should come fast.
AASM still has some issues with object proxies, so if you are using some libraries that put proxies in front of your objects, use
`AASM::StateMachine.copy_over(self, :ProxyClass)` after having defined your states.
ex :
```ruby
class WillBeProxiedByCDQ < CDQManagedObject; end
o = WillBeProxiedByCDQ.new
o.class
=> WillBeProxiedByCDQ_WillBeProxiedByCDQ_
include BW::KVO # From BubbleWrap
class WillBeProxiedByKVO
attr_accessor :any
end
o = WillBeProxiedByKVO.new
observe(o, [:any]) do; end
o.class
=> NSKVONotifying_WillBeProxiedByKVO
```
Any of these will break AASM if you include it to their class :
```ruby
class WillBeProxiedByCDQ < CDQManagedObject
include AASM
aasm do
state :state1, initial: true
state :state2
event :change_state do
transitions from: :state1, to: :state2
end
end
end
o = WillBeProxiedByCDQ.new
=> Error
include BW::KVO # From BubbleWrap
class WillBeProxiedByKVO
include AASM
attr_accessor :any
aasm do
state :state1, initial: true
state :state2
event :change_state do
transitions from: :state1, to: :state2
end
end
end
o = WillBeProxiedByKVO.new
observe(o, [:any]) do; end
o.state1?
=> Error
```
Fix it by doing :
```ruby
class WillBeProxiedByCDQ < CDQManagedObject
include AASM
aasm do
state :state1, initial: true
state :state2
event :change_state do
transitions from: :state1, to: :state2
end
end
AASM::StateMachine.copy_over(self, :WillBeProxiedByCDQ_WillBeProxiedByCDQ_)
end
o = WillBeProxiedByCDQ.new
o.state1?
=> true
include BW::KVO # From BubbleWrap
class WillBeProxiedByKVO
include AASM
attr_accessor :any
aasm do
state :state1, initial: true
state :state2
event :change_state do
transitions from: :state1, to: :state2
end
end
AASM::StateMachine.copy_over(self, :NSKVONotifying_WillBeProxiedByKVO)
end
o = WillBeProxiedByKVO.new
observe(o, [:any]) do; end
o.state1?
=> true
```
I'm working on a fix to remove all of that, but it does the trick for now.
Warnings:
- Due to RubyMotion Proc's lack of 'source_location' method, it may be harder
to find out the origin of a "cannot transition from" error. I would recommend using

View file

@ -10,6 +10,7 @@ require 'aasm/core/transition'
require 'aasm/core/event'
require 'aasm/core/state'
require 'aasm/localizer'
require 'aasm/state_machine_store'
require 'aasm/state_machine'
require 'aasm/persistence'
require 'aasm/persistence/base'

View file

@ -8,7 +8,7 @@ module AASM
# do not overwrite existing state machines, which could have been created by
# inheritance, see class method inherited
AASM::StateMachine[base] ||= {}
AASM::StateMachineStore.register(base)
AASM::Persistence.load_persistence(base)
super
@ -17,7 +17,8 @@ module AASM
module ClassMethods
# make sure inheritance (aka subclassing) works with AASM
def inherited(base)
AASM::StateMachine.copy_over(self, base)
AASM::StateMachineStore.register(base, self)
super
end
@ -33,7 +34,7 @@ module AASM
options = args[0] || {}
end
AASM::StateMachine[self][state_machine_name] ||= AASM::StateMachine.new(state_machine_name)
AASM::StateMachineStore.fetch(self, true).register(state_machine_name, AASM::StateMachine.new(state_machine_name))
# use a default despite the DSL configuration default.
# this is because configuration hasn't been setup for the AASM class but we are accessing a DSL option already for the class.
@ -52,7 +53,7 @@ module AASM
@aasm[state_machine_name] = aasm_klass.new(
self,
state_machine_name,
AASM::StateMachine[self][state_machine_name],
AASM::StateMachineStore.fetch(self, true).machine(state_machine_name),
options
)
end
@ -63,7 +64,7 @@ module AASM
# this is the entry point for all instance-level access to AASM
def aasm(name=:default)
unless AASM::StateMachine[self.class][name.to_sym]
unless AASM::StateMachineStore.fetch(self.class, true).machine(name)
raise AASM::UnknownStateMachineError.new("There is no state machine with the name '#{name}' defined in #{self.class.name}!")
end
@aasm ||= {}
@ -180,7 +181,7 @@ private
self.aasm_event_failed(event_name, old_state.name)
end
if AASM::StateMachine[self.class][state_machine_name].config.whiny_transitions
if AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.whiny_transitions
raise AASM::InvalidTransition.new(self, event_name, state_machine_name, failures)
else
false

View file

@ -91,11 +91,11 @@ module AASM
end
def aasm_enum(name=:default)
case AASM::StateMachine[self.class][name].config.enum
case AASM::StateMachineStore.fetch(self.class, true).machine(name).config.enum
when false then nil
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
else AASM::StateMachineStore.fetch(self.class, true).machine(name).config.enum
end
end
@ -111,7 +111,7 @@ module AASM
end
def aasm_skipping_validations(state_machine_name)
AASM::StateMachine[self.class][state_machine_name].config.skip_validation_on_save
AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.skip_validation_on_save
end
def aasm_write_attribute(state, name=:default)
@ -140,7 +140,7 @@ module AASM
# foo.aasm_state # => nil
#
def aasm_ensure_initial_state
AASM::StateMachine[self.class].keys.each do |state_machine_name|
AASM::StateMachineStore.fetch(self.class, true).machine_names.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 aasm_column_is_blank?(state_machine_name)
@ -187,18 +187,18 @@ module AASM
end
def requires_new?(state_machine_name)
AASM::StateMachine[self.class][state_machine_name].config.requires_new_transaction
AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.requires_new_transaction
end
def requires_lock?(state_machine_name)
AASM::StateMachine[self.class][state_machine_name].config.requires_lock
AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.requires_lock
end
def aasm_validate_states
AASM::StateMachine[self.class].keys.each do |state_machine_name|
AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
unless aasm_skipping_validations(state_machine_name)
if aasm_invalid_state?(state_machine_name)
self.errors.add(AASM::StateMachine[self.class][state_machine_name].config.column , "is invalid")
self.errors.add(AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.column , "is invalid")
end
end
end

View file

@ -69,7 +69,7 @@ module AASM
# foo.aasm_state # => nil
#
def aasm_ensure_initial_state
AASM::StateMachine[self.class].keys.each do |state_machine_name|
AASM::StateMachineStore.fetch(self.class, true).machine_names.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

View file

@ -14,7 +14,7 @@ module AASM
#
base.class_eval %Q(
def method_missing(method_name, *arguments, &block)
if (AASM::StateMachine[self.class].keys.map { |state_machine_name| self.class.aasm(state_machine_name).attribute_name.to_s + "=" }).include? method_name.to_s
if (AASM::StateMachineStore.fetch(self.class, true).machine_names.map { |state_machine_name| self.class.aasm(state_machine_name).attribute_name.to_s + "=" }).include? method_name.to_s
attribute_name = method_name.to_s.gsub("=", '')
write_attribute(attribute_name.to_sym, *arguments)
else
@ -82,7 +82,7 @@ module AASM
# foo.aasm_state # => nil
#
def aasm_ensure_initial_state
AASM::StateMachine[self.class].keys.each do |state_machine_name|
AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
aasm(state_machine_name).enter_initial_state if send(self.class.aasm(state_machine_name).attribute_name).blank?
end
end

View file

@ -85,11 +85,11 @@ module AASM
private
def aasm_enum(name=:default)
case AASM::StateMachine[self.class][name].config.enum
case AASM::StateMachineStore.fetch(self.class, true).machine(name).config.enum
when false then nil
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
else AASM::StateMachineStore.fetch(self.class, true).machine(name).config.enum
end
end
@ -102,7 +102,7 @@ module AASM
end
def aasm_skipping_validations(state_machine_name)
AASM::StateMachine[self.class][state_machine_name].config.skip_validation_on_save
AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.skip_validation_on_save
end
def aasm_write_attribute(state, name=:default)
@ -133,17 +133,17 @@ module AASM
# foo.aasm_state # => nil
#
def aasm_ensure_initial_state
AASM::StateMachine[self.class].keys.each do |state_machine_name|
AASM::StateMachineStore.fetch(self.class, true).machine_names.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
AASM::StateMachine[self.class].keys.each do |state_machine_name|
AASM::StateMachineStore.fetch(self.class, true).machine_names.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
unless AASM::StateMachineStore.fetch(self.class, true).machine(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")
self.errors.add(AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.column , "is invalid")
end
end
end

View file

@ -93,7 +93,7 @@ module AASM
# foo.aasm_state # => nil
#
def aasm_ensure_initial_state
AASM::StateMachine[self.class].keys.each do |state_machine_name|
AASM::StateMachineStore.fetch(self.class, true).machine_names.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

View file

@ -69,7 +69,7 @@ module AASM
# foo.aasm_state # => nil
#
def aasm_ensure_initial_state
AASM::StateMachine[self.class].keys.each do |state_machine_name|
AASM::StateMachineStore.fetch(self.class, true).machine_names.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?

View file

@ -1,21 +1,6 @@
module AASM
class StateMachine
# the following two methods provide the storage of all state machines
def self.[](klass)
(@machines ||= {})[klass.to_s]
end
def self.[]=(klass, machine)
(@machines ||= {})[klass.to_s] = machine
end
def self.copy_over(klass, alias_klass)
AASM::StateMachine[alias_klass] = {}
AASM::StateMachine[klass].keys.each do |state_machine_name|
AASM::StateMachine[alias_klass][state_machine_name] = AASM::StateMachine[klass][state_machine_name].clone
end
end
# the following four methods provide the storage of all state machines
attr_accessor :states, :events, :initial_state, :config, :name, :global_callbacks

View file

@ -0,0 +1,73 @@
module AASM
class StateMachineStore
class << self
def stores
@stores ||= {}
end
# do not overwrite existing state machines, which could have been created by
# inheritance, see AASM::ClassMethods method inherited
def register(klass, overwrite = false, state_machine = nil)
raise "Cannot register #{klass}" unless klass.is_a?(Class)
case name = template = overwrite
when FalseClass then stores[klass.to_s] ||= new
when TrueClass then stores[klass.to_s] = new
when Class then stores[klass.to_s] = stores[template.to_s].clone
when Symbol then stores[klass.to_s].register(name, state_machine)
when String then stores[klass.to_s].register(name, state_machine)
else raise "Don't know what to do with #{overwrite}"
end
end
alias_method :[]=, :register
def fetch(klass, fallback = nil)
stores[klass.to_s] || fallback && begin
match = klass.ancestors.find do |ancestor|
ancestor.include? AASM and stores[ancestor.to_s]
end
stores[match.to_s]
end
end
alias_method :[], :fetch
def unregister(klass)
stores.delete(klass.to_s)
end
end
def initialize
@machines = {}
end
def clone
StateMachineStore.new.tap do |store|
@machines.each_pair do |name, machine|
store.register(name, machine.clone)
end
end
end
def machine(name)
@machines[name.to_s]
end
alias_method :[], :machine
def machine_names
@machines.keys
end
alias_method :keys, :machine_names
def register(name, machine, force = false)
raise "Cannot use #{name.inspect} for machine name" unless name.is_a?(Symbol) or name.is_a?(String)
raise "Cannot use #{machine.inspect} as a machine" unless machine.is_a?(AASM::StateMachine)
if force
@machines[name.to_s] = machine
else
@machines[name.to_s] ||= machine
end
end
end
end

View file

@ -7,7 +7,7 @@
# end
# def machines
# AASM::StateMachine.instance_variable_get("@machines")
# AASM::StateMachineStore.instance_variable_get("@stores")
# end
# it "should be created without memory leak" do

View file

@ -452,7 +452,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[MultipleTransactor][:left].config.requires_new_transaction = false
AASM::StateMachineStore[MultipleTransactor][:left].config.requires_new_transaction = false
expect(transactor).to be_sleeping
expect(worker.status).to eq('sleeping')

View file

@ -486,7 +486,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][:default].config.requires_new_transaction = false
AASM::StateMachineStore[Transactor][:default].config.requires_new_transaction = false
expect(transactor).to be_sleeping
expect(worker.status).to eq('sleeping')