Skip to content

Commit a5339dd

Browse files
committed
Refactor Stack and Middleware closer to ActionDispatch::MiddlewareStack
Flag hash arg with `ruby2_keywords_hash` Add spec
1 parent de10be9 commit a5339dd

File tree

2 files changed

+52
-23
lines changed

2 files changed

+52
-23
lines changed

lib/grape/middleware/stack.rb

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,16 @@ module Middleware
77
class Stack
88
extend Forwardable
99
class Middleware
10-
extend Forwardable
11-
1210
attr_reader :args, :block, :klass
1311

14-
def_delegators :klass, :name
15-
16-
def initialize(klass, *args, &block)
12+
def initialize(klass, args, block)
1713
@klass = klass
1814
@args = args
1915
@block = block
2016
end
2117

18+
def name; klass.name; end # rubocop:disable Style/SingleLineMethods
19+
2220
def ==(other)
2321
case other
2422
when Middleware
@@ -32,7 +30,11 @@ def inspect
3230
klass.to_s
3331
end
3432

35-
def use_in(builder)
33+
def build(builder)
34+
# we need to force the ruby2_keywords_hash for middlewares that initialize contains keywords
35+
# like ActionDispatch::RequestId since middleware arguments are serialized
36+
# https://rubyapi.org/3.4/o/hash#method-c-ruby2_keywords_hash
37+
args[-1] = Hash.ruby2_keywords_hash(args[-1]) if args.last.is_a?(Hash) && Hash.respond_to?(:ruby2_keywords_hash)
3638
builder.use(klass, *args, &block)
3739
end
3840
end
@@ -48,51 +50,50 @@ def initialize
4850
@others = []
4951
end
5052

51-
def insert(index, *args, &block)
53+
def insert(index, klass, *args, &block)
5254
index = assert_index(index, :before)
53-
middleware = self.class::Middleware.new(*args, &block)
54-
middlewares.insert(index, middleware)
55+
middlewares.insert(index, self.class::Middleware.new(klass, args, block))
5556
end
56-
ruby2_keywords :insert if respond_to?(:ruby2_keywords, true)
5757

5858
alias insert_before insert
5959

6060
def insert_after(index, *args, &block)
6161
index = assert_index(index, :after)
6262
insert(index + 1, *args, &block)
6363
end
64-
ruby2_keywords :insert_after if respond_to?(:ruby2_keywords, true)
6564

66-
def use(...)
67-
middleware = self.class::Middleware.new(...)
65+
def use(klass, *args, &block)
66+
middleware = self.class::Middleware.new(klass, args, block)
6867
middlewares.push(middleware)
6968
end
7069

7170
def merge_with(middleware_specs)
72-
middleware_specs.each do |operation, *args|
71+
middleware_specs.each do |operation, klass, *args|
7372
if args.last.is_a?(Proc)
7473
last_proc = args.pop
75-
public_send(operation, *args, &last_proc)
74+
public_send(operation, klass, *args, &last_proc)
7675
else
77-
public_send(operation, *args)
76+
public_send(operation, klass, *args)
7877
end
7978
end
8079
end
8180

8281
# @return [Rack::Builder] the builder object with our middlewares applied
83-
def build(builder = Rack::Builder.new)
84-
others.shift(others.size).each { |m| merge_with(m) }
85-
middlewares.each do |m|
86-
m.use_in(builder)
82+
def build
83+
Rack::Builder.new.tap do |builder|
84+
others.shift(others.size).each { |m| merge_with(m) }
85+
middlewares.each do |m|
86+
m.build(builder)
87+
end
8788
end
88-
builder
8989
end
9090

9191
# @description Add middlewares with :use operation to the stack. Store others with :insert_* operation for later
9292
# @param [Array] other_specs An array of middleware specifications (e.g. [[:use, klass], [:insert_before, *args]])
9393
def concat(other_specs)
94-
@others << Array(other_specs).reject { |o| o.first == :use }
95-
merge_with(Array(other_specs).select { |o| o.first == :use })
94+
use, not_use = other_specs.partition { |o| o.first == :use }
95+
others << not_use
96+
merge_with(use)
9697
end
9798

9899
protected

spec/grape/api_spec.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,6 +1525,34 @@ def before
15251525
expect(last_response).to be_bad_request
15261526
expect(last_response.body).to eq('Caught in the Net')
15271527
end
1528+
1529+
context 'when middleware initialize as keywords' do
1530+
let(:middleware_with_keywords) do
1531+
Class.new do
1532+
def initialize(app, keyword:)
1533+
@app = app
1534+
@keyword = keyword
1535+
end
1536+
1537+
def call(env)
1538+
env['middleware_with_keywords'] = @keyword
1539+
@app.call(env)
1540+
end
1541+
end
1542+
end
1543+
1544+
before do
1545+
subject.use middleware_with_keywords, keyword: 'hello'
1546+
subject.get '/' do
1547+
env['middleware_with_keywords']
1548+
end
1549+
get '/'
1550+
end
1551+
1552+
it 'returns the middleware value' do
1553+
expect(last_response.body).to eq('hello')
1554+
end
1555+
end
15281556
end
15291557

15301558
describe '.insert_before' do

0 commit comments

Comments
 (0)