Skip to content

Commit 2ec5246

Browse files
authored
Fix ArgumentError for single-argument lambdas in expose and conditions (#399)
## Summary Upgrading to 1.0.2 breaks commonly used single-argument lambda patterns in `expose` blocks and `if:` conditions: ```ruby expose :property, if: ->(model) { model.active? } expose :storage, if: ->(model) { model.storage_method.present? } do |model| check_storage(model, :storage_method) end ``` These raise `ArgumentError: wrong number of arguments (given 2, expected 1)` since v1.0.2. This PR restores support for single-argument lambdas while preserving the symbol-to-proc (`&:method`) validation introduced in #389. Users who already applied the two-argument workaround (`->(model, _) { ... }`) are not affected — both styles work. Reported in #398.
1 parent bb355c8 commit 2ec5246

File tree

3 files changed

+80
-9
lines changed

3 files changed

+80
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#### Fixes
88

9+
* [#399](https://github.com/ruby-grape/grape-entity/pull/399): Fix `ArgumentError` for single-argument lambdas in `expose` blocks and `if:` conditions - [@numbata](https://github.com/numbata).
910
* Your contribution here.
1011

1112
### 1.0.2 (2026-04-13)

lib/grape_entity/entity.rb

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -525,13 +525,10 @@ def serializable_hash(runtime_options = {})
525525
end
526526

527527
def exec_with_object(options, &block)
528-
arity = if symbol_to_proc_wrapper?(block)
529-
ensure_block_arity!(block)
530-
else
531-
block.arity
532-
end
533-
534-
if arity.zero?
528+
if symbol_to_proc_wrapper?(block)
529+
ensure_block_arity!(block)
530+
instance_exec(object, &block)
531+
elsif block.arity == 1
535532
instance_exec(object, &block)
536533
else
537534
instance_exec(object, options, &block)
@@ -542,7 +539,7 @@ def ensure_block_arity!(block)
542539
# MRI currently always includes "( &:foo )" for symbol-to-proc wrappers.
543540
# If this format changes in a new Ruby version, this logic must be updated.
544541
origin_method_name = block.to_s.scan(/(?<=\(&:)[^)]+(?=\))/).first&.to_sym
545-
return 0 unless origin_method_name
542+
return unless origin_method_name
546543

547544
unless object.respond_to?(origin_method_name, true)
548545
raise ArgumentError, <<~MSG
@@ -551,7 +548,7 @@ def ensure_block_arity!(block)
551548
end
552549

553550
arity = object.method(origin_method_name).arity
554-
return 0 if arity.zero?
551+
return if arity.zero?
555552

556553
raise ArgumentError, <<~MSG
557554
Cannot use `&:#{origin_method_name}` because that method expects #{arity} argument#{'s' if arity != 1}.

spec/grape_entity/entity_spec.rb

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
expect { subject.expose :name, as: :foo }.not_to raise_error
2828
end
2929

30+
it 'supports callable :as with single-argument lambda' do
31+
subject.expose :name, as: ->(obj) { obj[:name].upcase }
32+
expect(subject.represent({ name: 'test' }).serializable_hash).to eq('TEST' => 'test')
33+
end
34+
3035
it 'makes sure that :format_with as a proc cannot be used with a block' do
3136
# rubocop:disable Style/BlockDelimiters
3237
expect {
@@ -148,6 +153,12 @@ def initialize(a, b, c)
148153
subject.expose(:c)
149154
expect(subject.represent(model, option_a: 100).serializable_hash).to eq(a: 100, b: nil, c: 'value')
150155
end
156+
157+
it 'works with single-argument block' do
158+
subject.expose(:a, expose_nil: false) { |obj| obj.c }
159+
subject.expose(:b)
160+
expect(subject.represent(model).serializable_hash).to eq(a: 'value', b: nil)
161+
end
151162
end
152163
end
153164

@@ -476,6 +487,42 @@ def raises_argument_error
476487
end.to raise_error ArgumentError, match(/method is not defined in the object/)
477488
end
478489
end
490+
491+
context 'with single-argument block' do
492+
it 'passes only the object without raising ArgumentError' do
493+
subject.expose :that_method_without_args do |object|
494+
object.method_without_args
495+
end
496+
497+
object = SomeObject.new
498+
value = subject.represent(object).value_for(:that_method_without_args)
499+
expect(value).to eq('result')
500+
end
501+
end
502+
503+
context 'with two-argument block' do
504+
it 'passes the object and options without raising ArgumentError' do
505+
subject.expose :that_method_without_args do |object, _options|
506+
object.method_without_args
507+
end
508+
509+
object = SomeObject.new
510+
value = subject.represent(object).value_for(:that_method_without_args)
511+
expect(value).to eq('result')
512+
end
513+
end
514+
515+
context 'with splat-argument block' do
516+
it 'passes the object and options' do
517+
subject.expose :args_count do |*args|
518+
args.size
519+
end
520+
521+
object = SomeObject.new
522+
value = subject.represent(object).value_for(:args_count)
523+
expect(value).to eq(2)
524+
end
525+
end
479526
end
480527

481528
context 'with no parameters passed to the block' do
@@ -521,6 +568,32 @@ def raises_argument_error
521568
expect(subject.represent({}).value_for(:awesome)).to eq(condition_met: 'value')
522569
end
523570

571+
it 'works with single-argument if condition lambdas' do
572+
subject.expose :awesome do
573+
subject.expose(:condition_met, if: ->(_) { true }) { |_| 'value' }
574+
subject.expose(:condition_not_met, if: ->(_) { false }) { |_| 'value' }
575+
end
576+
577+
expect(subject.represent({}).value_for(:awesome)).to eq(condition_met: 'value')
578+
end
579+
580+
it 'works with two-argument if condition lambdas' do
581+
subject.expose :awesome do
582+
subject.expose(:condition_met, if: ->(_, _) { true }) { |_| 'value' }
583+
subject.expose(:condition_not_met, if: ->(_, _) { false }) { |_| 'value' }
584+
end
585+
586+
expect(subject.represent({}).value_for(:awesome)).to eq(condition_met: 'value')
587+
end
588+
589+
it 'works with single-argument block exposures' do
590+
subject.expose :awesome do
591+
subject.expose(:nested) { |obj| obj.class.name }
592+
end
593+
594+
expect(subject.represent({}).value_for(:awesome)).to eq(nested: 'Hash')
595+
end
596+
524597
it 'does not represent attributes, declared inside nested exposure, outside of it' do
525598
subject.expose :awesome do
526599
subject.expose(:nested) { |_| 'value' }

0 commit comments

Comments
 (0)