Skip to content

Commit

Permalink
Fix "Separation of positional and keyword arguments in Ruby 3.0"
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfais committed Nov 26, 2021
1 parent 00b4c33 commit 00247f8
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 41 deletions.
4 changes: 4 additions & 0 deletions .reek.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ detectors:
- Transitions::InvalidTransition
LongParameterList:
exclude:
- Transitions::Event#can_execute_transition_from_state?
- Transitions::Event#fire
- Transitions::Machine#fire_event
- Transitions::Machine#transition_to_new_state
- Transitions::StateTransition#perform_guard
ManualDispatch:
exclude:
- Transitions::Event#default_timestamp_name
Expand Down
4 changes: 2 additions & 2 deletions lib/active_model/transitions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ module Transitions
alias_method :old_transitions_initialize, :initialize
alias_method :old_transitions_update, :update

def new_transitions_initialize(*args, &block)
def new_transitions_initialize(*args, **kwargs, &block)
@attribute_name = :state
old_transitions_initialize(*args, &block)
old_transitions_initialize(*args, **kwargs, &block)
end

def new_transitions_update(options = {}, &block)
Expand Down
24 changes: 12 additions & 12 deletions lib/transitions/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,39 @@ def initialize(machine, name, options = {}, &block)
@transitions = []
@timestamps = []
if machine
machine.klass.send(:define_method, "#{name}!") do |*args|
machine.fire_event(name, self, true, *args)
machine.klass.send(:define_method, "#{name}!") do |*args, **kwargs|
machine.fire_event(name, self, true, *args, **kwargs)
end

machine.klass.send(:define_method, name.to_s) do |*args|
machine.fire_event(name, self, false, *args)
machine.klass.send(:define_method, name.to_s) do |*args, **kwargs|
machine.fire_event(name, self, false, *args, **kwargs)
end

machine.klass.send(:define_method, "can_#{name}?") do |*_args|
machine.events_for(current_state).include?(name.to_sym)
end

machine.klass.send(:define_method, "can_execute_#{name}?") do |*args|
machine.klass.send(:define_method, "can_execute_#{name}?") do |*args, **kwargs|
event = name.to_sym

send("can_#{name}?", *args) &&
machine.events[event].can_execute_transition_from_state?(current_state, self, *args)
send("can_#{name}?", *args, **kwargs) &&
machine.events[event].can_execute_transition_from_state?(current_state, self, *args, **kwargs)
end
end
update(options, &block)
end

def fire(obj, to_state = nil, *args)
def fire(obj, to_state = nil, *args, **kwargs)
transitions = @transitions.select { |t| t.from == obj.current_state }
fail InvalidTransition, error_message_for_invalid_transitions(obj) if transitions.size == 0

next_state = nil
transitions.each do |transition|
next if to_state && !Array(transition.to).include?(to_state)
next unless transition.executable?(obj, *args)
next unless transition.executable?(obj, *args, **kwargs)

next_state = to_state || Array(transition.to).first
transition.execute(obj, *args)
transition.execute(obj, *args, **kwargs)
update_event_timestamp(obj, next_state) if timestamp_defined?
break
end
Expand All @@ -54,8 +54,8 @@ def transitions_from_state?(state)
@transitions.any? { |t| t.from? state }
end

def can_execute_transition_from_state?(state, obj, *args)
@transitions.select { |t| t.from? state }.any? { |t| t.executable?(obj, *args) }
def can_execute_transition_from_state?(state, obj, *args, **kwargs)
@transitions.select { |t| t.from? state }.any? { |t| t.executable?(obj, *args, **kwargs) }
end

def ==(other)
Expand Down
8 changes: 4 additions & 4 deletions lib/transitions/machine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ def update(options = {}, &block)
#
# rubocop:disable Metrics/MethodLength
#
def fire_event(event, record, persist, *args)
def fire_event(event, record, persist, *args, **kwargs)
handle_state_exit_callback record
if new_state = transition_to_new_state(record, event, *args)
if new_state = transition_to_new_state(record, event, *args, **kwargs)
handle_state_enter_callback record, new_state
handle_event_fired_callback record, new_state, event
record.update_current_state(new_state, persist)
Expand Down Expand Up @@ -61,8 +61,8 @@ def handle_state_exit_callback(record)
state_index[record.current_state].call_action(:exit, record)
end

def transition_to_new_state(record, event, *args)
@events[event].fire(record, nil, *args)
def transition_to_new_state(record, event, *args, **kwargs)
@events[event].fire(record, nil, *args, **kwargs)
end

def handle_state_enter_callback(record, new_state)
Expand Down
18 changes: 9 additions & 9 deletions lib/transitions/state_transition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def initialize(opts)
#
# @return [Bool]
#
def executable?(obj, *args)
[@guard].flatten.all? { |g| perform_guard(obj, g, *args) }
def executable?(obj, *args, **kwargs)
[@guard].flatten.all? { |g| perform_guard(obj, g, *args, **kwargs) }
end

#
Expand All @@ -40,17 +40,17 @@ def executable?(obj, *args)
#
# rubocop:disable Metrics/MethodLength
#
def execute(obj, *args)
def execute(obj, *args, **kwargs)
case @on_transition
when Symbol, String
obj.send(@on_transition, *args)
obj.send(@on_transition, *args, **kwargs)
when Proc
@on_transition.call(obj, *args)
@on_transition.call(obj, *args, **kwargs)
when Array
@on_transition.each do |callback|
# Yes, we're passing always the same parameters for each callback in here.
# We should probably drop args altogether in case we get an array.
obj.send(callback, *args)
obj.send(callback, *args, **kwargs)
end
else
# TODO: We probably should check for this in the constructor and not that late.
Expand All @@ -70,11 +70,11 @@ def from?(value)

private

def perform_guard(obj, guard, *args)
def perform_guard(obj, guard, *args, **kwargs)
if guard.respond_to?(:call)
guard.call(obj, *args)
guard.call(obj, *args, **kwargs)
elsif guard.is_a?(Symbol) || guard.is_a?(String)
obj.send(guard, *args)
obj.send(guard, *args, **kwargs)
else
true
end
Expand Down
29 changes: 15 additions & 14 deletions test/state_transition/test_state_transition_guard_check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,65 +2,66 @@

class TestStateTransitionGuardCheck < Test::Unit::TestCase
args = [:foo, 'bar']
kwargs = {}

test 'should return true of there is no guard' do
opts = { from: 'foo', to: 'bar' }
st = Transitions::StateTransition.new(opts)

assert st.executable?(nil, *args)
assert st.executable?(nil, *args, **kwargs)
end

test 'should call the method on the object if guard is a symbol' do
opts = { from: 'foo', to: 'bar', guard: :test_guard }
st = Transitions::StateTransition.new(opts)

obj = stub
obj.expects(:test_guard).with(*args)
obj.expects(:test_guard).with(*args, **kwargs)

st.executable?(obj, *args)
st.executable?(obj, *args, **kwargs)
end

test 'should call the method on the object if guard is a string' do
opts = { from: 'foo', to: 'bar', guard: 'test_guard' }
st = Transitions::StateTransition.new(opts)

obj = stub
obj.expects(:test_guard).with(*args)
obj.expects(:test_guard).with(*args, **kwargs)

st.executable?(obj, *args)
st.executable?(obj, *args, **kwargs)
end

test 'should call the proc passing the object if the guard is a proc' do
opts = { from: 'foo', to: 'bar', guard: proc { |o, *args| o.test_guard(*args) } }
opts = { from: 'foo', to: 'bar', guard: proc { |o, *args, **kwargs| o.test_guard(*args, **kwargs) } }
st = Transitions::StateTransition.new(opts)

obj = stub
obj.expects(:test_guard).with(*args)
obj.expects(:test_guard).with(*args, **kwargs)

st.executable?(obj, *args)
st.executable?(obj, *args, **kwargs)
end

test 'should call the callable passing the object if the guard responds to #call' do
callable = Object.new
callable.define_singleton_method(:call) { |obj, *args| obj.test_guard(*args) }
callable.define_singleton_method(:call) { |obj, *args, **kwargs| obj.test_guard(*args, **kwargs) }

opts = { from: 'foo', to: 'bar', guard: callable }
st = Transitions::StateTransition.new(opts)

obj = stub
obj.expects(:test_guard).with(*args)
obj.expects(:test_guard).with(*args, **kwargs)

st.executable?(obj, *args)
st.executable?(obj, *args, **kwargs)
end

test 'should call the method on the object if guard is a symbol' do
opts = { from: 'foo', to: 'bar', guard: [:test_guard, :test_another_guard] }
st = Transitions::StateTransition.new(opts)

obj = stub
obj.expects(:test_guard).with(*args).returns(true)
obj.expects(:test_another_guard).with(*args).returns(true)
obj.expects(:test_guard).with(*args, **kwargs).returns(true)
obj.expects(:test_another_guard).with(*args, **kwargs).returns(true)

assert st.executable?(obj, *args)
assert st.executable?(obj, *args, **kwargs)
end
end

0 comments on commit 00247f8

Please sign in to comment.