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

chore(invokers): Refactor callback invokers, add class-callbacks support

This commit is contained in:
Vlad Gramuzov 2018-06-19 21:11:56 +03:00
parent c3153d56ad
commit e43b87444c
No known key found for this signature in database
GPG key ID: 6D48E708EDC4E583
16 changed files with 934 additions and 103 deletions

View file

@ -130,16 +130,26 @@ the transition succeeds :
### Callbacks
You can define a number of callbacks for your transitions. These methods will be
called, when certain criteria are met, like entering a particular state:
You can define a number of callbacks for your events, transitions and states. These methods, Procs or classes will be
called, when certain criteria are met, like entering a particular state (note that class must respond to `call` method):
```ruby
class LogRunTime
def initialize(resource)
@resource = resource
end
def call
# Do whatever you want with @resource
end
end
class Job
include AASM
aasm do
state :sleeping, :initial => true, :before_enter => :do_something
state :running
state :running, before_enter: Proc.new { do_something && notify_somebody }
state :finished
after_all_transitions :log_status_change
@ -195,6 +205,8 @@ is finished.
AASM will also initialize `LogRunTime` and run the `call` method for you after the transition from `running` to `finished` in the example above. You can pass arguments to the class by defining an initialize method on it, like this:
Note that Procs are executed in the context of a record, it means that you don't need to expect the record as an argument, just call the methods you need.
```ruby
class LogRunTime
# optional args parameter can be omitted, but if you define initialize

View file

@ -9,6 +9,11 @@ require 'aasm/instance_base'
require 'aasm/core/transition'
require 'aasm/core/event'
require 'aasm/core/state'
require 'aasm/core/invoker'
require 'aasm/core/invokers/base_invoker'
require 'aasm/core/invokers/class_invoker'
require 'aasm/core/invokers/literal_invoker'
require 'aasm/core/invokers/proc_invoker'
require 'aasm/localizer'
require 'aasm/state_machine_store'
require 'aasm/state_machine'

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module AASM::Core
class Event
include DslHelper
@ -156,27 +158,9 @@ module AASM::Core
end
def invoke_callbacks(code, record, args)
case code
when Symbol, String
unless record.respond_to?(code, true)
raise NoMethodError.new("NoMethodError: undefined method `#{code}' for #{record.inspect}:#{record.class}")
end
arity = record.__send__(:method, code.to_sym).arity
record.__send__(code, *(arity < 0 ? args : args[0...arity]))
true
when Proc
arity = code.arity
record.instance_exec(*(arity < 0 ? args : args[0...arity]), &code)
true
when Array
code.each {|a| invoke_callbacks(a, record, args)}
true
else
false
end
Invoker.new(code, record, args)
.with_default_return_value(false)
.invoke
end
end
end # AASM

129
lib/aasm/core/invoker.rb Normal file
View file

@ -0,0 +1,129 @@
# frozen_string_literal: true
module AASM
module Core
##
# main invoker class which encapsulates the logic
# for invoking literal-based, proc-based, class-based
# and array-based callbacks for different entities.
class Invoker
DEFAULT_RETURN_VALUE = true
##
# Initialize a new invoker instance.
# NOTE that invoker must be used per-subject/record
# (one instance per subject/record)
#
# ==Options:
#
# +subject+ - invoking subject, may be Proc,
# Class, String, Symbol or Array
# +record+ - invoking record
# +args+ - arguments which will be passed to the callback
def initialize(subject, record, args)
@subject = subject
@record = record
@args = args
@options = {}
@failures = []
@default_return_value = DEFAULT_RETURN_VALUE
end
##
# Pass additional options to concrete invoker
#
# ==Options:
#
# +options+ - hash of options which will be passed to
# concrete invokers
#
# ==Example:
#
# with_options(guard: proc {...})
def with_options(options)
@options = options
self
end
##
# Collect failures to a specified buffer
#
# ==Options:
#
# +failures+ - failures buffer to collect failures
def with_failures(failures)
@failures = failures
self
end
##
# Change default return value of #invoke method
# if none of invokers processed the request.
#
# The default return value is #DEFAULT_RETURN_VALUE
#
# ==Options:
#
# +value+ - default return value for #invoke method
def with_default_return_value(value)
@default_return_value = value
self
end
##
# Find concrete invoker for specified subject and invoker it,
# or return default value set by #DEFAULT_RETURN_VALUE or
# overridden by #with_default_return_value
# rubocop:disable Metrics/AbcSize
def invoke
return invoke_array if subject.is_a?(Array)
return literal_invoker.invoke if literal_invoker.may_invoke?
return proc_invoker.invoke if proc_invoker.may_invoke?
return class_invoker.invoke if class_invoker.may_invoke?
default_return_value
end
# rubocop:enable Metrics/AbcSize
private
attr_reader :subject, :record, :args, :options, :failures,
:default_return_value
def invoke_array
return subject.all? { |item| sub_invoke(item) } if options[:guard]
return subject.all? { |item| !sub_invoke(item) } if options[:unless]
subject.map { |item| sub_invoke(item) }
end
def sub_invoke(new_subject)
self.class.new(new_subject, record, args)
.with_failures(failures)
.with_options(options)
.invoke
end
def proc_invoker
@proc_invoker ||= Invokers::ProcInvoker
.new(subject, record, args)
.with_failures(failures)
end
def class_invoker
@class_invoker ||= Invokers::ClassInvoker
.new(subject, record, args)
.with_failures(failures)
end
def literal_invoker
@literal_invoker ||= Invokers::LiteralInvoker
.new(subject, record, args)
.with_failures(failures)
end
end
end
end

View file

@ -0,0 +1,75 @@
# frozen_string_literal: true
module AASM
module Core
module Invokers
##
# Base concrete invoker class which contain basic
# invoking and logging definitions
class BaseInvoker
attr_reader :failures, :subject, :record, :args, :result
##
# Initialize a new concrete invoker instance.
# NOTE that concrete invoker must be used per-subject/record
# (one instance per subject/record)
#
# ==Options:
#
# +subject+ - invoking subject comparable with this invoker
# +record+ - invoking record
# +args+ - arguments which will be passed to the callback
def initialize(subject, record, args)
@subject = subject
@record = record
@args = args
@result = false
@failures = []
end
##
# Collect failures to a specified buffer
#
# ==Options:
#
# +failures+ - failures buffer to collect failures
def with_failures(failures_buffer)
@failures = failures_buffer
self
end
##
# Execute concrete invoker, log the error and return result
def invoke
return unless may_invoke?
log_failure unless invoke_subject
result
end
##
# Check if concrete invoker may be invoked for a specified subject
def may_invoke?
raise NoMethodError, '"#may_invoke?" is not implemented'
end
##
# Log failed invoking
def log_failure
raise NoMethodError, '"#log_failure" is not implemented'
end
##
# Execute concrete invoker
def invoke_subject
raise NoMethodError, '"#invoke_subject" is not implemented'
end
end
end
end
end

View file

@ -0,0 +1,52 @@
# frozen_string_literal: true
module AASM
module Core
module Invokers
##
# Class invoker which allows to use classes which respond to #call
# to be used as state/event/transition callbacks.
class ClassInvoker < BaseInvoker
def may_invoke?
subject.is_a?(Class) && subject.instance_methods.include?(:call)
end
def log_failure
return log_source_location if Method.method_defined?(:source_location)
log_method_info
end
def invoke_subject
@result = retrieve_instance.call
end
private
def log_source_location
failures << instance.method(:call).source_location.join('#')
end
def log_method_info
failures << instance.method(:call)
end
def instance
@instance ||= retrieve_instance
end
# rubocop:disable Metrics/AbcSize
def retrieve_instance
return subject.new if subject_arity.zero?
return subject.new(record) if subject_arity == 1
return subject.new(record, *args) if subject_arity < 0
subject.new(record, *args[0..(subject_arity - 2)])
end
# rubocop:enable Metrics/AbcSize
def subject_arity
@arity ||= subject.instance_method(:initialize).arity
end
end
end
end
end

View file

@ -0,0 +1,47 @@
# frozen_string_literal: true
module AASM
module Core
module Invokers
##
# Literal invoker which allows to use strings or symbols to call
# record methods as state/event/transition callbacks.
class LiteralInvoker < BaseInvoker
def may_invoke?
subject.is_a?(String) || subject.is_a?(Symbol)
end
def log_failure
failures << subject
end
def invoke_subject
@result = exec_subject
end
private
def subject_arity
@arity ||= record.__send__(:method, subject.to_sym).arity
end
# rubocop:disable Metrics/AbcSize
def exec_subject
raise(*record_error) unless record.respond_to?(subject, true)
return record.__send__(subject) if subject_arity.zero?
return record.__send__(subject, *args) if subject_arity < 0
record.__send__(subject, *args[0..(subject_arity - 1)])
end
# rubocop:enable Metrics/AbcSize
def record_error
[
NoMethodError,
'NoMethodError: undefined method ' \
"`#{subject}' for #{record.inspect}:#{record.class}"
]
end
end
end
end
end

View file

@ -0,0 +1,59 @@
# frozen_string_literal: true
module AASM
module Core
module Invokers
##
# Proc invoker which allows to use Procs as
# state/event/transition callbacks.
class ProcInvoker < BaseInvoker
def may_invoke?
subject.is_a?(Proc)
end
def log_failure
return log_source_location if Method.method_defined?(:source_location)
log_proc_info
end
def invoke_subject
@result = if support_parameters?
exec_proc(parameters_to_arity)
else
exec_proc(subject.arity)
end
end
private
def support_parameters?
subject.respond_to?(:parameters)
end
# rubocop:disable Metrics/AbcSize
def exec_proc(parameters_size)
return record.instance_exec(&subject) if parameters_size.zero?
return record.instance_exec(*args, &subject) if parameters_size < 0
record.instance_exec(*args[0..(parameters_size - 1)], &subject)
end
# rubocop:enable Metrics/AbcSize
def log_source_location
failures << subject.source_location.join('#')
end
def log_proc_info
failures << subject
end
def parameters_to_arity
subject.parameters.inject(0) do |memo, parameter|
memo += 1
memo *= -1 if parameter[0] == :rest && memo > 0
memo
end
end
end
end
end
end

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module AASM::Core
class State
attr_reader :name, :state_machine, :options
@ -13,7 +15,13 @@ module AASM::Core
def initialize_copy(orig)
super
@options = {}
orig.options.each_pair { |name, setting| @options[name] = setting.is_a?(Hash) || setting.is_a?(Array) ? setting.dup : setting }
orig.options.each_pair do |name, setting|
@options[name] = if setting.is_a?(Hash) || setting.is_a?(Array)
setting.dup
else
setting
end
end
end
def ==(state)
@ -75,14 +83,7 @@ module AASM::Core
end
def _fire_callbacks(action, record, args)
case action
when Symbol, String
arity = record.__send__(:method, action.to_sym).arity
record.__send__(action, *(arity < 0 ? args : args[0...arity]))
when Proc
arity = action.arity
action.call(record, *(arity < 0 ? args : args[0...arity]))
end
Invoker.new(action, record, args).invoke
end
end

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module AASM::Core
class Transition
include DslHelper
@ -67,77 +69,14 @@ module AASM::Core
record.aasm(event.state_machine.name).to_state = @to if record.aasm(event.state_machine.name).respond_to?(:to_state=)
end
case code
when Symbol, String
result = (record.__send__(:method, code.to_sym).arity == 0 ? record.__send__(code) : record.__send__(code, *args))
failures << code unless result
result
when Proc
if code.respond_to?(:parameters)
# In Ruby's Proc, the 'arity' method is not a good condidate to know if
# we should pass the arguments or not, since it does return 0 even in
# presence of optional parameters.
result = (code.parameters.size == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code))
failures << code.source_location.join('#') unless result
else
# In RubyMotion's Proc, the 'parameter' method does not exists, however its
# 'arity' method works just like the one from Method, only returning 0 when
# there is no parameters whatsoever, optional or not.
result = (code.arity == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code))
# Sadly, RubyMotion's Proc does not define the method 'source_location' either.
failures << code unless result
end
result
when Class
arity = code.instance_method(:initialize).arity
if arity == 0
instance = code.new
elsif arity == 1
instance = code.new(record)
else
instance = code.new(record, *args)
end
result = instance.call
if Method.method_defined?(:source_location)
failures << instance.method(:call).source_location.join('#') unless result
else
# RubyMotion support ('source_location' not defined for Method)
failures << instance.method(:call) unless result
end
result
when Array
if options[:guard]
# invoke guard callbacks
code.all? {|a| invoke_callbacks_compatible_with_guard(a, record, args)}
elsif options[:unless]
# invoke unless callbacks
code.all? {|a| !invoke_callbacks_compatible_with_guard(a, record, args)}
else
# invoke after callbacks
code.map {|a| invoke_callbacks_compatible_with_guard(a, record, args)}
end
else
true
end
Invoker.new(code, record, args)
.with_options(options)
.with_failures(failures)
.invoke
end
def _fire_callbacks(code, record, args)
case code
when Symbol, String
arity = record.send(:method, code.to_sym).arity
record.send(code, *(arity < 0 ? args : args[0...arity]))
when Proc
code.arity == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code)
when Array
code.map {|a| _fire_callbacks(a, record, args)}
else
true
end
Invoker.new(code, record, args).invoke
end
end

189
spec/unit/invoker_spec.rb Normal file
View file

@ -0,0 +1,189 @@
require 'spec_helper'
describe AASM::Core::Invoker do
let(:target) { nil }
let(:record) { double }
let(:args) { [] }
subject { described_class.new(target, record, args) }
describe '#with_options' do
context 'when passing array as a subject' do
context 'and "guard" option is set to true' do
let(:target) { [subject_1, subject_2] }
before { subject.with_options(guard: true) }
context 'and all the subjects are truthy' do
let(:subject_1) { Proc.new { true } }
let(:subject_2) { Proc.new { true } }
it 'then returns "true" while invoking' do
expect(subject.invoke).to eq(true)
end
end
context 'and any subject is falsely' do
let(:subject_1) { Proc.new { false } }
let(:subject_2) { Proc.new { true } }
it 'then returns "false" while invoking' do
expect(subject.invoke).to eq(false)
end
end
end
context 'and "unless" option is set to true' do
let(:target) { [subject_1, subject_2] }
before { subject.with_options(unless: true) }
context 'and all the subjects are falsely' do
let(:subject_1) { Proc.new { false } }
let(:subject_2) { Proc.new { false } }
it 'then returns "true" while invoking' do
expect(subject.invoke).to eq(true)
end
end
context 'and any subject is truthy' do
let(:subject_1) { Proc.new { false } }
let(:subject_2) { Proc.new { true } }
it 'then returns "false" while invoking' do
expect(subject.invoke).to eq(false)
end
end
end
end
end
describe '#with_failures' do
let(:concrete_invoker) { AASM::Core::Invokers::ProcInvoker }
let(:target) { Proc.new {} }
it 'then sets failures buffer for concrete invokers' do
expect_any_instance_of(concrete_invoker)
.to receive(:with_failures)
.and_call_original
subject.invoke
end
end
describe '#with_default_return_value' do
context 'when return value is "true"' do
before { subject.with_default_return_value(true) }
it 'then returns "true" when was not picked up by any invoker' do
expect(subject.invoke).to eq(true)
end
end
context 'when return value is "false"' do
before { subject.with_default_return_value(false) }
it 'then returns "false" when was not picked up by any invoker' do
expect(subject.invoke).to eq(false)
end
end
end
describe '#invoke' do
context 'when subject is a proc' do
let(:concrete_invoker) { AASM::Core::Invokers::ProcInvoker }
let(:target) { Proc.new {} }
it 'then calls proc invoker' do
expect_any_instance_of(concrete_invoker)
.to receive(:invoke)
.and_call_original
expect(record).to receive(:instance_exec)
subject.invoke
end
end
context 'when subject is a class' do
let(:concrete_invoker) { AASM::Core::Invokers::ClassInvoker }
let(:target) { Class.new { def call; end } }
it 'then calls proc invoker' do
expect_any_instance_of(concrete_invoker)
.to receive(:invoke)
.and_call_original
expect_any_instance_of(target).to receive(:call)
subject.invoke
end
end
context 'when subject is a literal' do
let(:concrete_invoker) { AASM::Core::Invokers::LiteralInvoker }
let(:record) { double(invoke_me: nil) }
let(:target) { :invoke_me }
it 'then calls literal invoker' do
expect_any_instance_of(concrete_invoker)
.to receive(:invoke)
.and_call_original
expect(record).to receive(:invoke_me)
subject.invoke
end
end
context 'when subject is an array of procs' do
let(:subject_1) { Proc.new {} }
let(:subject_2) { Proc.new {} }
let(:target) { [subject_1, subject_2] }
it 'then calls each proc' do
expect(record).to receive(:instance_exec).twice
subject.invoke
end
end
context 'when subject is an array of classes' do
let(:subject_1) { Class.new { def call; end } }
let(:subject_2) { Class.new { def call; end } }
let(:target) { [subject_1, subject_2] }
it 'then calls each class' do
expect_any_instance_of(subject_1).to receive(:call)
expect_any_instance_of(subject_2).to receive(:call)
subject.invoke
end
end
context 'when subject is an array of literals' do
let(:subject_1) { :method_one }
let(:subject_2) { :method_two }
let(:record) { double(method_one: nil, method_two: nil) }
let(:target) { [subject_1, subject_2] }
it 'then calls each class' do
expect(record).to receive(:method_one)
expect(record).to receive(:method_two)
subject.invoke
end
end
context 'when subject is not supported' do
let(:target) { nil }
it 'then just returns default value' do
expect(subject.invoke).to eq(described_class::DEFAULT_RETURN_VALUE)
end
end
end
end

View file

@ -0,0 +1,72 @@
require 'spec_helper'
describe AASM::Core::Invokers::BaseInvoker do
let(:target) { double }
let(:record) { double }
let(:args) { [] }
subject { described_class.new(target, record, args) }
describe '#may_invoke?' do
it 'then raises NoMethodError' do
expect { subject.may_invoke? }.to raise_error(NoMethodError)
end
end
describe '#log_failure' do
it 'then raises NoMethodError' do
expect { subject.log_failure }.to raise_error(NoMethodError)
end
end
describe '#invoke_subject' do
it 'then raises NoMethodError' do
expect { subject.log_failure }.to raise_error(NoMethodError)
end
end
describe '#with_failures' do
it 'then sets failures buffer' do
buffer = [1, 2, 3]
subject.with_failures(buffer)
expect(subject.failures).to eq(buffer)
end
end
describe '#invoke' do
context 'when #may_invoke? respond with "false"' do
before { allow(subject).to receive(:may_invoke?).and_return(false) }
it 'then returns "nil"' do
expect(subject.invoke).to eq(nil)
end
end
context 'when #invoke_subject respond with "false"' do
before do
allow(subject).to receive(:may_invoke?).and_return(true)
allow(subject).to receive(:invoke_subject).and_return(false)
end
it 'then calls #log_failure' do
expect(subject).to receive(:log_failure)
subject.invoke
end
end
context 'when #invoke_subject succeed' do
before do
allow(subject).to receive(:may_invoke?).and_return(true)
allow(subject).to receive(:invoke_subject).and_return(true)
end
it 'then returns result' do
expect(subject).to receive(:result)
subject.invoke
end
end
end
end

View file

@ -0,0 +1,95 @@
require 'spec_helper'
describe AASM::Core::Invokers::ClassInvoker do
let(:target) { Class.new { def call; end } }
let(:record) { double }
let(:args) { [] }
subject { described_class.new(target, record, args) }
describe '#may_invoke?' do
context 'when subject is a Class and responds to #call' do
it 'then returns "true"' do
expect(subject.may_invoke?).to eq(true)
end
end
context 'when subject is not a class or not respond to #call' do
let(:target) { Class.new {} }
it 'then returns "false"' do
expect(subject.may_invoke?).to eq(false)
end
end
end
describe '#log_failure' do
context 'when subject respond to #source_location' do
it 'then adds "source_location" to a failures buffer' do
subject.log_failure
expect(subject.failures)
.to eq([target.instance_method(:call).source_location.join('#')])
end
end
context 'when subject does not respond to #source_location' do
before do
Method.__send__(:alias_method, :original_source_location, :source_location)
Method.__send__(:undef_method, :source_location)
end
after do
Method.__send__(
:define_method,
:source_location,
Method.instance_method(:original_source_location)
)
end
it 'then adds the subject to a failures buffer' do
subject.log_failure
expect(subject.failures.first).to be_a(Method)
end
end
end
describe '#invoke_subject' do
context 'when passing no arguments' do
let(:args) { [1, 2 ,3] }
let(:target) { Class.new { def call; end } }
it 'then correctly uses passed arguments' do
expect { subject.invoke_subject }.not_to raise_error
end
end
context 'when passing single argument' do
let(:args) { [1, 2 ,3, 4, 5, 6] }
let(:target) { Class.new { def initialize(_a); end; def call; end } }
it 'then correctly uses passed arguments' do
expect { subject.invoke_subject }.not_to raise_error
end
end
context 'when passing variable number arguments' do
let(:args) { [1, 2 ,3, 4, 5, 6] }
let(:target) { Class.new { def initialize(_a, _b, *_c); end; def call; end } }
it 'then correctly uses passed arguments' do
expect { subject.invoke_subject }.not_to raise_error
end
end
context 'when passing one or more arguments' do
let(:args) { [1, 2 ,3, 4, 5, 6] }
let(:target) { Class.new { def initialize(_a, _b, _c); end; def call; end } }
it 'then correctly uses passed arguments' do
expect { subject.invoke_subject }.not_to raise_error
end
end
end
end

View file

@ -0,0 +1,86 @@
require 'spec_helper'
describe AASM::Core::Invokers::LiteralInvoker do
let(:target) { nil }
let(:record) { double }
let(:args) { [] }
subject { described_class.new(target, record, args) }
describe '#may_invoke?' do
context 'when subject is a Symbol' do
let(:target) { :i_am_symbol }
it 'then returns "true"' do
expect(subject.may_invoke?).to eq(true)
end
end
context 'when subject is a String' do
let(:target) { 'i_am_string' }
it 'then returns "true"' do
expect(subject.may_invoke?).to eq(true)
end
end
context 'when subject is neither a String nor Symbol' do
let(:target) { double }
it 'then returns "false"' do
expect(subject.may_invoke?).to eq(false)
end
end
end
describe '#log_failure' do
let(:target) { Proc.new { false } }
it 'then adds the subject to a failures buffer' do
subject.log_failure
expect(subject.failures).to eq([target])
end
end
describe '#invoke_subject' do
context 'when passing no arguments' do
let(:record) { Class.new { def my_method; end }.new }
let(:args) { [1, 2 ,3] }
let(:target) { :my_method }
it 'then correctly uses passed arguments' do
expect { subject.invoke_subject }.not_to raise_error
end
end
context 'when passing variable number arguments' do
let(:record) { Class.new { def my_method(_a, _b, *_c); end }.new }
let(:args) { [1, 2 ,3, 4, 5, 6] }
let(:target) { :my_method }
it 'then correctly uses passed arguments' do
expect { subject.invoke_subject }.not_to raise_error
end
end
context 'when passing one or more arguments' do
let(:record) { Class.new { def my_method(_a, _b, _c); end }.new }
let(:args) { [1, 2 ,3, 4, 5, 6] }
let(:target) { :my_method }
it 'then correctly uses passed arguments' do
expect { subject.invoke_subject }.not_to raise_error
end
end
context 'when record does not respond to subject' do
let(:record) { Class.new { }.new }
let(:target) { :my_method }
it 'then raises uses passed arguments' do
expect { subject.invoke_subject }.to raise_error(NoMethodError)
end
end
end
end

View file

@ -0,0 +1,86 @@
require 'spec_helper'
describe AASM::Core::Invokers::ProcInvoker do
let(:target) { Proc.new {} }
let(:record) { double }
let(:args) { [] }
subject { described_class.new(target, record, args) }
describe '#may_invoke?' do
context 'when subject is a Proc' do
it 'then returns "true"' do
expect(subject.may_invoke?).to eq(true)
end
end
context 'when subject is not a Proc' do
let(:target) { nil }
it 'then returns "false"' do
expect(subject.may_invoke?).to eq(false)
end
end
end
describe '#log_failure' do
context 'when subject respond to #source_location' do
it 'then adds "source_location" to a failures buffer' do
subject.log_failure
expect(subject.failures)
.to eq([target.source_location.join('#')])
end
end
context 'when subject does not respond to #source_location' do
before do
Method.__send__(:alias_method, :original_source_location, :source_location)
Method.__send__(:undef_method, :source_location)
end
after do
Method.__send__(
:define_method,
:source_location,
Method.instance_method(:original_source_location)
)
end
it 'then adds the subject to a failures buffer' do
subject.log_failure
expect(subject.failures).to eq([target])
end
end
end
describe '#invoke_subject' do
context 'when passing no arguments' do
let(:args) { [1, 2 ,3] }
let(:target) { ->() {} }
it 'then correctly uses passed arguments' do
expect { subject.invoke_subject }.not_to raise_error
end
end
context 'when passing variable number arguments' do
let(:args) { [1, 2 ,3, 4, 5, 6] }
let(:target) { ->(_a, _b, *_c) {} }
it 'then correctly uses passed arguments' do
expect { subject.invoke_subject }.not_to raise_error
end
end
context 'when passing one or more arguments' do
let(:args) { [1, 2 ,3, 4, 5, 6] }
let(:target) { ->(_a, _b, _c) {} }
it 'then correctly uses passed arguments' do
expect { subject.invoke_subject }.not_to raise_error
end
end
end
end

View file

@ -64,7 +64,7 @@ describe AASM::Core::State do
expect(record).to receive(:c)
expect(record).to receive(:foobar)
state.fire_callbacks(:entering, record)
state.fire_callbacks(:entering, record, record)
end
it "should stop calling actions if one of them raises :halt_aasm_chain" do
@ -84,6 +84,6 @@ describe AASM::Core::State do
record = double('record')
expect(record).to receive(:foobar)
state.fire_callbacks(:entering, record)
state.fire_callbacks(:entering, record, record)
end
end