Skip to content

Commit 4898a9a

Browse files
committed
feat: Replaced returns kwarg with block or hashrocket - the choice is yours!
fixes #5
1 parent db740ed commit 4898a9a

File tree

4 files changed

+129
-32
lines changed

4 files changed

+129
-32
lines changed

.rubocop.yml

+9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ AllCops:
55

66
Style/Documentation:
77
Enabled: false
8+
Style/HashAsLastArrayItem:
9+
Enabled: false
10+
11+
Layout/LineLength:
12+
Max: 100
813

914
Lint/ConstantDefinitionInBlock:
1015
Exclude:
@@ -14,6 +19,10 @@ Metrics/AbcSize:
1419
Enabled: false
1520
Metrics/MethodLength:
1621
Enabled: false
22+
Metrics/CyclomaticComplexity:
23+
Enabled: false
24+
Metrics/PerceivedComplexity:
25+
Enabled: false
1726

1827
Metrics/BlockLength:
1928
Exclude:

README.md

+25-3
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ runtime. This is useful for ensuring that your methods are being called with the
55
and for providing better error messages when they are not. It also serves as a nice way of
66
documenting your methods using actual code instead of comments.
77

8+
## Usage
9+
810
Simply define a method signature using the `sig` method directly before the method to be checked,
9-
and Delivered will check that the method is being called with the correct arguments and types. it
10-
can also check the return value of the method if you pass `sig` a `returns` keyword argument.
11+
and Delivered will check that the method is being called with the correct arguments and types.
1112

1213
```ruby
1314
class User
1415
extend Delivered::Signature
1516

16-
sig String, age: Integer, returns: String
17+
sig String, age: Integer
1718
def create(name, age:)
1819
"User #{name} created with age #{age}"
1920
end
@@ -22,3 +23,24 @@ end
2223

2324
If an invalid argument is given to `User#create`, for example, if `age` is a `String` instead of
2425
the required `Integer`, a `NoMatchingPatternError` exception will be raised.
26+
27+
### Return Types
28+
29+
You can also check the return value of the method by passing a Hash with an Array as the key, and
30+
the value as the return type to check.
31+
32+
```ruby
33+
sig [String, age: Integer] => String
34+
def create(name, age:)
35+
"User #{name} created with age #{age}"
36+
end
37+
```
38+
39+
Or by placing the return type in a block to `sig`.
40+
41+
```ruby
42+
sig(String, age: Integer) { String }
43+
def create(name, age:)
44+
"User #{name} created with age #{age}"
45+
end
46+
```

lib/delivered/signature.rb

+25-4
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,29 @@ module Delivered
44
module Signature
55
NULL = Object.new
66

7-
def sig(*sig_args, returns: NULL, **sig_kwargs)
7+
def sig(*sig_args, **sig_kwargs, &return_blk)
8+
# ap [sig_args, sig_kwargs, return_blk]
9+
10+
# Block return
11+
returns = return_blk&.call
12+
13+
# Hashrocket return
14+
if sig_kwargs.keys[0].is_a?(Array)
15+
unless returns.nil?
16+
raise ArgumentError, 'Cannot mix block and hash for return type. Use one or the other.'
17+
end
18+
19+
returns = sig_kwargs.values[0]
20+
sig_args = sig_kwargs.keys[0]
21+
sig_kwargs = sig_args.pop
22+
end
23+
24+
# ap sig_args
25+
# ap sig_kwargs
26+
# ap returns
27+
828
meta = class << self; self; end
9-
sig_check = lambda { |klass, name, *args, **kwargs, &block|
29+
sig_check = lambda do |klass, name, *args, **kwargs, &block|
1030
sig_args.each.with_index do |arg, i|
1131
args[i] => ^arg
1232
end
@@ -21,10 +41,11 @@ def sig(*sig_args, returns: NULL, **sig_kwargs)
2141
klass.send(:"__#{name}", *args, **kwargs)
2242
end
2343

24-
result => ^returns if returns != NULL
44+
result => ^returns unless returns.nil?
2545

2646
result
27-
}
47+
end
48+
2849
meta.send :define_method, :method_added do |name|
2950
meta.send :remove_method, :method_added
3051
meta.send :remove_method, :singleton_method_added

test/delivered/signature.rb

+70-25
Original file line numberDiff line numberDiff line change
@@ -6,59 +6,104 @@ class User
66

77
attr_reader :town, :blk
88

9-
sig String, Integer, town: String
9+
sig(Integer) { Integer }
10+
attr_writer :age
11+
12+
sig String, town: String
1013
def initialize(name, age = nil, town: nil, &block)
1114
@name = name
1215
@age = age
1316
@town = town
1417
@blk = block
1518
end
1619

17-
sig returns: String
20+
sig(String, age: Integer) { Integer }
21+
def with_block_return(name, age:) = 1 # rubocop:disable Lint/UnusedMethodArgument
22+
23+
sig(String, age: Integer) { Integer }
24+
def with_incorrect_block_return = 'hello'
25+
26+
sig [String, age: Integer] => Integer
27+
def with_incorrect_hash_return = 'hello'
28+
29+
sig [String, age: Integer] => Integer
30+
def with_hash_return(name, age:) = 1 # rubocop:disable Lint/UnusedMethodArgument
31+
32+
sig [] => String
1833
def to_s = "#{@name}, #{@age}"
1934

20-
sig returns: Integer
35+
sig { Integer }
2136
def age = @age.to_s
2237

23-
sig Integer, returns: Integer
24-
attr_writer :age
38+
sig(String, _age: Integer) { Array }
39+
def self.where(_name, _age: nil) = []
40+
41+
sig [String] => Array
42+
def self.find_by_name(name) = User.new(name)
43+
end
2544

26-
sig String, _age: Integer, returns: Array
27-
def self.where(_name, _age: nil)
28-
[]
45+
with 'return type with hash' do
46+
it 'succeeds' do
47+
user = User.new('Joel')
48+
expect(user.with_hash_return('Joel', age: 47)).to be == 1
2949
end
3050

31-
sig String, returns: Array
32-
def self.find_by_name(name)
33-
User.new(name)
51+
it 'raises on incorrect types' do
52+
user = User.new('Joel')
53+
expect { user.with_hash_return }.to raise_exception NoMatchingPatternError
54+
end
55+
56+
it 'raises on incorrect return type' do
57+
user = User.new('Joel')
58+
expect { user.with_incorrect_hash_return }.to raise_exception NoMatchingPatternError
3459
end
3560
end
3661

37-
it 'supports positional args' do
38-
user = User.new('Joel', 47)
39-
expect(user.to_s).to be == 'Joel, 47'
62+
with 'return type as block' do
63+
it 'succeeds' do
64+
user = User.new('Joel')
65+
expect(user.with_block_return('Joel', age: 47)).to be == 1
66+
end
67+
68+
it 'raises on incorrect types' do
69+
user = User.new('Joel')
70+
expect { user.with_block_return }.to raise_exception NoMatchingPatternError
71+
end
72+
73+
it 'raises on incorrect return type' do
74+
user = User.new('Joel')
75+
expect { user.with_incorrect_block_return }.to raise_exception NoMatchingPatternError
76+
end
4077
end
4178

42-
# it 'supports optional positional args'
79+
it 'raises on mix of returns' do
80+
extend Delivered::Signature
4381

44-
it 'supports block' do
45-
user = User.new('Joel', 47) { 'Hello' }
46-
expect(user.blk.call).to be == 'Hello'
82+
expect do
83+
sig([] => Integer) { Integer }
84+
end.to raise_exception(ArgumentError, message: be =~ /Cannot mix/)
4785
end
4886

49-
it 'checks return type' do
87+
it 'supports positional args' do
5088
user = User.new('Joel', 47)
5189
expect(user.to_s).to be == 'Joel, 47'
5290
end
5391

54-
it 'raises on incorrect return type' do
55-
user = User.new('Joel', 47)
56-
expect { user.age }.to raise_exception NoMatchingPatternError
92+
it 'supports block' do
93+
user = User.new('Joel', 47) { 'Hello' }
94+
expect(user.blk.call).to be == 'Hello'
5795
end
5896

59-
it 'checks return type with args' do
60-
user = User.new('Joel', 47)
61-
expect(user.age = 48).to be == 48
97+
with 'attr_writer' do
98+
it 'succeeds' do
99+
user = User.new('Joel')
100+
expect(user.age = 47).to be == 47
101+
end
102+
103+
it 'raise on incorrect type' do
104+
user = User.new('Joel', 47)
105+
expect { user.age = '47' }.to raise_exception NoMatchingPatternError
106+
end
62107
end
63108

64109
it 'raises on missing args' do

0 commit comments

Comments
 (0)