Skip to content

Commit 99edb4c

Browse files
marcrohloffnumbata
authored andcommitted
PR recommendations
Add argumenterror specs
1 parent a892626 commit 99edb4c

4 files changed

Lines changed: 183 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
### Next Release
22

3-
* [#406](https://github.com/ruby-grape/grape-entity/pull/406): Handle symbol-to-proc wrappers (`&:method_name`) where the method uses `delegate` or `method_missing` - [@marcrohloff](https://github.com/marcrohloff).
4-
53
#### Features
64

5+
* [#406](https://github.com/ruby-grape/grape-entity/pull/406): Handle symbol-to-proc wrappers (`&:method_name`) where the method uses `delegate` or `method_missing` - [@marcrohloff](https://github.com/marcrohloff).
76
* Your contribution here.
87

98
#### Fixes

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Implement your feature or bug fix.
4444

4545
Ruby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop), run `bundle exec rubocop` and fix any style issues highlighted.
4646

47-
Make sure that `bundle exec rake` and `rubocop` completes without errors.
47+
Make sure that `bundle exec rake` completes without errors.
4848

4949
#### Write Documentation
5050

lib/grape_entity/entity.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ def self.documentation
291291
end
292292

293293
# This allows you to declare a Proc in which exposures can be formatted with.
294-
# It take a block with an arity of 1 which is passed as the value of the exposed attribute.
294+
# It takes a block with a single argument which is passed as the value of the exposed attribute.
295295
#
296296
# @param name [Symbol] the name of the formatter
297297
# @param block [Proc] the block that will interpret the exposed attribute
@@ -545,12 +545,14 @@ def ensure_block_arity!(block)
545545
MSG
546546
end
547547

548+
# Ensure that the function does not require any positional args
549+
# (functions defined using `delegate` or `method_missing` take an arg of `*rest`
548550
arity = object.method(origin_method_name).arity
549-
# functions defined using `delegate` or `method_missing` have an arity of -1
550-
return if arity <= 0
551+
required_positional_arg_count = arity >= 0 ? arity : -arity - 1
552+
return if required_positional_arg_count.zero?
551553

552554
raise ArgumentError, <<~MSG
553-
Cannot use `&:#{origin_method_name}` because that method expects #{arity} argument#{'s' if arity != 1}.
555+
Cannot use `&:#{origin_method_name}` because that method expects #{required_positional_arg_count} #{'argument'.pluralize(required_positional_arg_count)}.
554556
Symbol‐to‐proc shorthand only works for zero‐argument methods.
555557
MSG
556558
end

spec/grape_entity/entity_spec.rb

Lines changed: 175 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -400,19 +400,27 @@ class BogusEntity < Grape::Entity
400400

401401
describe 'blocks' do
402402
class SomeObject
403-
class SomeObjectDelegate
404-
def method_using_delegation
405-
'delegated-result'
406-
end
403+
def method_without_args
404+
'result'
407405
end
408406

409-
delegate :method_using_delegation, to: :delegate_object
407+
def method_with_one_arg(_object)
408+
'result'
409+
end
410410

411-
def method_without_args
411+
def method_with_only_optional_args(_optional1 = 1, _optional2 = 2)
412412
'result'
413413
end
414414

415-
def method_with_one_arg(_object)
415+
def method_with_required_and_optional_args(_required_arg, _optional1 = 1, _optional2 = 2)
416+
'result'
417+
end
418+
419+
def method_with_optional_args_as_a_splat(*_optional_args)
420+
'result'
421+
end
422+
423+
def method_with_required_args_and_an_optional_splat(_required_arg, *_optional_argds)
416424
'result'
417425
end
418426

@@ -423,25 +431,56 @@ def method_with_multiple_args(_object, _options)
423431
def raises_argument_error
424432
raise ArgumentError, 'something different'
425433
end
434+
end
426435

436+
class SomeObjectWithMethodMissing
427437
def method_missing(method, ...)
428-
return 'missing-result' if method.to_sym == :method_using_missing
438+
if method.to_sym == :method_without_args
439+
method_without_args_impl(...)
440+
elsif method.to_sym == :method_with_args
441+
method_with_args_impl(...)
442+
else
443+
super
444+
end
445+
end
429446

430-
super
447+
def method_without_args_impl
448+
'result'
431449
end
432450

433-
def delegate_object
434-
@delegate_object ||= SomeObjectDelegate.new
451+
def method_with_args_impl(_required_arg)
452+
'result'
435453
end
436454

437455
private
438456

439457
def respond_to_missing?(method, include_private = false) # rubocop:disable Style/OptionalBooleanParameter
440-
method.to_sym == :method_using_missing ||
458+
method.to_sym == :method_without_args ||
459+
method.to_sym == :method_with_args ||
441460
super
442461
end
443462
end
444463

464+
class SomeObjectWithDelegation
465+
class InnerDelegate
466+
def method_without_args
467+
'result'
468+
end
469+
470+
def method_with_args(_required_arg)
471+
'result'
472+
end
473+
end
474+
475+
delegate :method_without_args,
476+
:method_with_args,
477+
to: :delegate_object
478+
479+
def delegate_object
480+
@delegate_object ||= InnerDelegate.new
481+
end
482+
end
483+
445484
describe 'with block passed in' do
446485
specify do
447486
subject.expose :that_method_without_args do |object|
@@ -484,25 +523,135 @@ def respond_to_missing?(method, include_private = false) # rubocop:disable Style
484523
end
485524
end
486525

487-
context 'with block passed in via & that uses `missing_method`' do
488-
specify do
489-
subject.expose :using_missing, &:method_using_missing
526+
context 'with block passed in via & that references a method with optional args' do
527+
it 'succeeds if there no required arguments' do
528+
subject.expose :that_method_with_only_optional_args, &:method_with_only_optional_args
529+
subject.expose :method_with_only_optional_args, as: :that_method_with_only_optional_args_again
530+
531+
object = SomeObject.new
532+
533+
value = subject.represent(object).value_for(:method_with_only_optional_args)
534+
expect(value).to be_nil
535+
536+
value = subject.represent(object).value_for(:that_method_with_only_optional_args)
537+
expect(value).to eq('result')
538+
539+
value = subject.represent(object).value_for(:that_method_with_only_optional_args_again)
540+
expect(value).to eq('result')
541+
end
542+
543+
it 'raises an `ArgumentError` if there are required arguments' do
544+
subject.expose :that_method_with_required_and_optional_args, &:method_with_required_and_optional_args
545+
subject.expose :method_with_required_and_optional_args, as: :that_method_with_required_and_optional_args_again
490546

491547
object = SomeObject.new
492-
expect(object.method(:method_using_missing).arity).to eq(-1)
493-
value = subject.represent(object).value_for(:using_missing)
494-
expect(value).to eq('missing-result')
548+
549+
expect do
550+
subject.represent(object).value_for(:that_method_with_required_and_optional_args)
551+
end.to raise_error ArgumentError, include('method expects 1 argument.')
552+
553+
expect do
554+
subject.represent(object).value_for(:that_method_with_required_and_optional_args_again)
555+
end.to raise_error ArgumentError, include('(given 0, expected 1..3)')
495556
end
496557
end
497558

498-
context 'with block passed in via & that uses `delegate`' do
559+
context 'with block passed in via & that references a method with optional args as a splat' do
499560
specify do
500-
subject.expose :using_delegation, &:method_using_delegation
561+
subject.expose :that_method_with_optional_args_as_a_splat, &:method_with_optional_args_as_a_splat
562+
subject.expose :method_with_optional_args_as_a_splat, as: :that_method_with_optional_args_as_a_splat_again
563+
564+
object = SomeObject.new
565+
566+
value = subject.represent(object).value_for(:method_with_optional_args_as_a_splat)
567+
expect(value).to be_nil
568+
569+
value = subject.represent(object).value_for(:that_method_with_optional_args_as_a_splat)
570+
expect(value).to eq('result')
571+
572+
value = subject.represent(object).value_for(:that_method_with_optional_args_as_a_splat_again)
573+
expect(value).to eq('result')
574+
end
575+
576+
it 'raises an `ArgumentError` if there are required arguments' do
577+
subject.expose :that_method_with_required_args_and_an_optional_splat, &:method_with_required_args_and_an_optional_splat
578+
subject.expose :method_with_required_args_and_an_optional_splat, as: :that_method_with_required_args_and_an_optional_splat_again
501579

502580
object = SomeObject.new
503-
expect(object.method(:method_using_delegation).arity).to eq(-1)
504-
value = subject.represent(object).value_for(:using_delegation)
505-
expect(value).to eq('delegated-result')
581+
582+
expect do
583+
subject.represent(object).value_for(:that_method_with_required_args_and_an_optional_splat)
584+
end.to raise_error ArgumentError, include('method expects 1 argument.')
585+
586+
expect do
587+
subject.represent(object).value_for(:that_method_with_required_args_and_an_optional_splat_again)
588+
end.to raise_error ArgumentError, include('(given 0, expected 1+)')
589+
end
590+
end
591+
592+
context 'with block passed in via & that references a method implemented using `method_missing`' do
593+
specify do
594+
subject.expose :that_method_without_args, &:method_without_args
595+
subject.expose :method_without_args, as: :that_method_without_args_again
596+
597+
object = SomeObjectWithMethodMissing.new
598+
599+
value = subject.represent(object).value_for(:method_without_args)
600+
expect(value).to be_nil
601+
602+
value = subject.represent(object).value_for(:that_method_without_args)
603+
expect(value).to eq('result')
604+
605+
value = subject.represent(object).value_for(:that_method_without_args_again)
606+
expect(value).to eq('result')
607+
end
608+
609+
it 'raises an `ArgumentError` if there are required arguments' do
610+
subject.expose :that_method_with_args, &:method_with_args
611+
subject.expose :method_with_args, as: :that_method_with_args_again
612+
613+
object = SomeObjectWithMethodMissing.new
614+
615+
expect do
616+
subject.represent(object).value_for(:that_method_with_args)
617+
end.to raise_error ArgumentError, include('(given 0, expected 1)')
618+
619+
expect do
620+
subject.represent(object).value_for(:that_method_with_args_again)
621+
end.to raise_error ArgumentError, include('(given 0, expected 1)')
622+
end
623+
end
624+
625+
context 'with block passed in via & that references a method implemented using `delegate`' do
626+
specify do
627+
subject.expose :that_method_without_args, &:method_without_args
628+
subject.expose :method_without_args, as: :that_method_without_args_again
629+
630+
object = SomeObjectWithDelegation.new
631+
632+
value = subject.represent(object).value_for(:method_without_args)
633+
expect(value).to be_nil
634+
635+
value = subject.represent(object).value_for(:that_method_without_args)
636+
expect(value).to eq('result')
637+
638+
value = subject.represent(object).value_for(:that_method_without_args_again)
639+
expect(value).to eq('result')
640+
end
641+
642+
it 'raises an `ArgumentError` if there are required arguments' do
643+
subject.expose :that_method_with_args, &:method_with_args
644+
subject.expose :method_with_args, as: :that_method_with_args_again
645+
646+
object = SomeObjectWithDelegation.new
647+
648+
expect do
649+
subject.represent(object).value_for(:that_method_with_args)
650+
end.to raise_error ArgumentError, include('(given 0, expected 1)')
651+
652+
expect do
653+
subject.represent(object).value_for(:that_method_with_args_again)
654+
end.to raise_error ArgumentError, include('(given 0, expected 1)')
506655
end
507656
end
508657

@@ -515,11 +664,11 @@ def respond_to_missing?(method, include_private = false) # rubocop:disable Style
515664

516665
expect do
517666
subject.represent(object).value_for(:that_method_with_one_arg)
518-
end.to raise_error ArgumentError, match(/method expects 1 argument/)
667+
end.to raise_error ArgumentError, include('method expects 1 argument.')
519668

520669
expect do
521670
subject.represent(object).value_for(:that_method_with_multple_args)
522-
end.to raise_error ArgumentError, match(/method expects 2 arguments/)
671+
end.to raise_error ArgumentError, include('method expects 2 arguments.')
523672
end
524673
end
525674

@@ -531,7 +680,7 @@ def respond_to_missing?(method, include_private = false) # rubocop:disable Style
531680

532681
expect do
533682
subject.represent(object).value_for(:that_undefined_method)
534-
end.to raise_error ArgumentError, match(/method is not defined in the object/)
683+
end.to raise_error ArgumentError, include('method is not defined in the object')
535684
end
536685
end
537686

0 commit comments

Comments
 (0)