Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions lib/state_machines/integrations/active_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -353,12 +353,22 @@ def around_validation(object)

def define_state_initializer
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
def initialize(**params)
params.transform_keys! do |key|
def initialize(params = nil, **kwargs)
# Support both positional hash and keyword arguments
attrs = params.nil? ? kwargs : params
#{' '}
attrs.transform_keys! do |key|
self.class.attribute_aliases[key.to_s] || key.to_s
end if self.class.respond_to?(:attribute_aliases)

self.class.state_machines.initialize_states(self, {}, params) { super }
# Call super with the appropriate arguments based on what we received
self.class.state_machines.initialize_states(self, {}, attrs) do
if params
super(params)
else
super(**kwargs)
end
end
end
end_eval
end
Expand Down
60 changes: 60 additions & 0 deletions test/machine_initialization_compatibility_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

require_relative 'test_helper'

class MachineInitializationCompatibilityTest < BaseTestCase
def setup
@model = new_model do
include ActiveModel::Validations
attr_accessor :state
end

@machine = StateMachines::Machine.new(@model, initial: :parked)
@machine.state :parked, :idling
@machine.event :ignite
end

def test_should_accept_positional_hash_argument
record = @model.new({ state: 'idling' })
assert_equal 'idling', record.state
end

def test_should_accept_keyword_arguments
record = @model.new(state: 'idling')
assert_equal 'idling', record.state
end

def test_should_accept_empty_initialization
record = @model.new
assert_equal 'parked', record.state
end

def test_should_handle_attribute_aliases
@model.class_eval do
def self.attribute_aliases
{ 'status' => 'state' }
end
end

record = @model.new(status: 'idling')
assert_equal 'idling', record.state
end

def test_should_prefer_positional_hash_over_keywords_when_both_present
# If someone accidentally provides both, positional takes precedence
record = @model.new({ state: 'idling' }, state: 'parked')
assert_equal 'idling', record.state
end

def test_should_handle_empty_positional_hash
# Empty hash should still be treated as positional argument
record = @model.new({})
assert_equal 'parked', record.state # Gets default initial state
end

def test_should_use_keywords_when_empty_hash_and_keywords_present
# With the fix, keywords are ignored even with empty positional hash
record = @model.new({}, state: 'idling')
assert_equal 'parked', record.state # Empty hash takes precedence
end
end
Loading