Skip to content

Commit 7bf1cd5

Browse files
committed
Fix(Entity): Improve block arity detection for Symbol#to_proc
Addresses `ArgumentError`s when using `&:method_name` with `expose`, a scenario particularly affected by Ruby 3.0's stricter argument handling for procs. The previous arity check, including the condition `block.parameters == [[:req], [:rest]]`, was not consistently reliable for these cases. This change introduces `determine_block_arity` which: 1. Attempts to parse the original method name from `Proc#to_s` for blocks likely created via `&:method_name`. 2. If the method name is found and the object responds to it, this logic uses the *actual arity of the original method* to correctly determine how `instance_exec` should be called. 3. If the original method name can't be determined, it falls back to using `block.parameters.size`. This ensures methods exposed via `&:method_name` are called with the correct arguments (i.e., with or without `options`), resolving the `ArgumentError`s and removing the need for the previous `rescue` logic.
1 parent c8f7a22 commit 7bf1cd5

2 files changed

Lines changed: 23 additions & 30 deletions

File tree

lib/grape_entity/entity.rb

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -518,21 +518,26 @@ def serializable_hash(runtime_options = {})
518518
root_exposure.serializable_value(self, opts)
519519
end
520520

521+
def determine_block_arity(block)
522+
# As from Ruby 3.0, the block parameters are always equal to `[[:req], [:rest]]`
523+
# In that case the method name could be extracted from the block to find the original method arity.
524+
origin_method_name = block.to_s.scan(/(?<=\(&:)[^)]+(?=\))/).first&.to_sym
525+
526+
if origin_method_name && object.respond_to?(origin_method_name, true)
527+
object.method(origin_method_name).parameters.size
528+
else
529+
block.parameters.size
530+
end
531+
end
532+
521533
def exec_with_object(options, &block)
522-
if block.parameters.count == 1 || block.parameters == [[:req], [:rest]]
534+
block_arity = determine_block_arity(block)
535+
536+
if block_arity == 0
523537
instance_exec(object, &block)
524538
else
525539
instance_exec(object, options, &block)
526540
end
527-
rescue StandardError => e
528-
# it handles: https://github.com/ruby/ruby/blob/v3_0_0_preview1/NEWS.md#language-changes point 3, Proc
529-
# accounting for expose :foo, &:bar
530-
if e.is_a?(ArgumentError) && block.parameters == [[:req], [:rest]]
531-
Rails.logger.error("***** ERROR in exec_with_object: #{e.message}\n#{e.backtrace.join("\n")}")
532-
raise Grape::Entity::Deprecated.new e.message, 'in ruby 3.0'
533-
end
534-
535-
raise e
536541
end
537542

538543
def exec_with_attribute(attribute, &block)

spec/grape_entity/entity_spec.rb

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -423,28 +423,16 @@ def raises_argument_error
423423
end
424424

425425
context 'with block passed in via &' do
426-
if RUBY_VERSION.start_with?('3')
427-
specify do
428-
subject.expose :that_method_without_args, &:method_without_args
429-
subject.expose :method_without_args, as: :that_method_without_args_again
430-
431-
object = SomeObject.new
432-
expect do
433-
subject.represent(object).value_for(:that_method_without_args)
434-
end.to raise_error Grape::Entity::Deprecated
435-
436-
value2 = subject.represent(object).value_for(:that_method_without_args_again)
437-
expect(value2).to eq('result')
438-
end
439-
else
440-
specify do
441-
subject.expose :that_method_without_args_again, &:method_without_args
426+
specify do
427+
subject.expose :that_method_without_args, &:method_without_args
428+
subject.expose :method_without_args, as: :that_method_without_args_again
442429

443-
object = SomeObject.new
430+
object = SomeObject.new
431+
value1 = subject.represent(object).value_for(:that_method_without_args)
432+
expect(value1).to eq('result')
444433

445-
value2 = subject.represent(object).value_for(:that_method_without_args_again)
446-
expect(value2).to eq('result')
447-
end
434+
value2 = subject.represent(object).value_for(:that_method_without_args_again)
435+
expect(value2).to eq('result')
448436
end
449437
end
450438
end

0 commit comments

Comments
 (0)