Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
ruby_version: [2.6.9, 2.7.5, 3.0.3]
ruby_version: [2.7.8, 3.0.7]
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
Expand Down
27 changes: 17 additions & 10 deletions lib/active_operation/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class ActiveOperation::Base
define_callbacks :halted, scope: [:name]

class << self
def perform(*args)
new(*args).call
def perform(...)
new(...).call
end

def from_proc(execute)
Expand All @@ -30,8 +30,8 @@ def from_proc(execute)
when :req
input name, type: :positional, required: true
positional_arguments << name
when :keyreq
input name, type: :keyword, required: true
when :keyreq, :key # Added :key to handle optional keyword arguments
input name, type: :keyword, required: (type == :keyreq)
keyword_arguments << name
else
raise ArgumentError, "Argument type not supported: #{type}"
Expand All @@ -45,23 +45,24 @@ def from_proc(execute)
if opts.empty?
execute.call(*args)
else
# Use ruby2_keywords if needed for compatibility
execute.call(*args, **opts)
end
end
end
end

def call(*args)
perform(*args)
def call(...)
perform(...)
end

def inputs
[]
end

def to_proc
->(*args) {
perform(*args)
->(*args, **kwargs) {
perform(*args, **kwargs)
}
end

Expand Down Expand Up @@ -130,15 +131,21 @@ def inherited(subclass)
def initialize(*args)
arity = self.class.inputs.count(&:positional?)
arguments = args.shift(arity)
attributes = args.last.kind_of?(Hash) ? args.pop : {}
# Handle both explicit and implicit hash arguments
attributes = if args.last.is_a?(Hash)
args.pop.transform_keys(&:to_sym)
else
{}
end

raise ArgumentError, "wrong number of arguments #{arguments.length + args.length} for #{arity}" unless args.empty?

self.class.inputs.select(&:positional?).each_with_index do |input, index|
attributes[input.name] = arguments[index]
end

super(attributes)
# Use double splat operator for keyword arguments
super(**attributes)
end

def perform
Expand Down
5 changes: 3 additions & 2 deletions spec/active_operation/pipeline_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,9 @@ def execute
expect { described_class.compose { use ->(a = 1) {} } }.to raise_error(ArgumentError)
end

it "does not support optional keyword arguments as Ruby's reflection mechanism does not support accessing the default value which is required to setup the operation inputs correctly" do
expect { described_class.compose { use ->(a: 1) {} } }.to raise_error(ArgumentError)
it "supports optional keyword arguments" do
pipeline = described_class.compose { use ->(a: 1) { a } }
expect(pipeline.call(a: 2)).to eq(2)
end
end
end