Skip to content

Commit 103928a

Browse files
authored
fix(#1922): Allow to use instance variables defined in the endpoints inside rescue_from (#2377)
* fix(#1922): Allow to use instance variables defined in the endpoints when rescue_from * fix(#1922): Update CHANGELOG and running rubocop * fix(#1922): Updating UPGRADING and README files explaining the instance variables behavior. Extra tests added * fix(#1922): Send endpoint parameter as the last param of the run_rescue_handler method * fix(#1922): Adding short before/after example to UPGRADING * fix(#1922): Fixing CHANGELOG format style
1 parent 3f01d03 commit 103928a

File tree

6 files changed

+167
-5
lines changed

6 files changed

+167
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#### Features
44

55
* [#2371](https://github.com/ruby-grape/grape/pull/2371): Use a param value as the `default` value of other param - [@jcagarcia](https://github.com/jcagarcia).
6+
* [#2377](https://github.com/ruby-grape/grape/pull/2377): Allow to use instance variables values inside `rescue_from` - [@jcagarcia](https://github.com/jcagarcia).
67
* Your contribution here.
78

89
#### Fixes

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
- [Current Route and Endpoint](#current-route-and-endpoint)
122122
- [Before, After and Finally](#before-after-and-finally)
123123
- [Anchoring](#anchoring)
124+
- [Instance Variables](#instance-variables)
124125
- [Using Custom Middleware](#using-custom-middleware)
125126
- [Grape Middleware](#grape-middleware)
126127
- [Rails Middleware](#rails-middleware)
@@ -3595,6 +3596,42 @@ end
35953596
This will match all paths starting with '/statuses/'. There is one caveat though: the `params[:status]` parameter only holds the first part of the request url.
35963597
Luckily this can be circumvented by using the described above syntax for path specification and using the `PATH_INFO` Rack environment variable, using `env['PATH_INFO']`. This will hold everything that comes after the '/statuses/' part.
35973598

3599+
## Instance Variables
3600+
3601+
You can use instance variables to pass information across the various stages of a request. An instance variable set within a `before` validator is accessible within the endpoint's code and can also be utilized within the `rescue_from` handler.
3602+
3603+
```ruby
3604+
class TwitterAPI < Grape::API
3605+
before do
3606+
@var = 1
3607+
end
3608+
3609+
get '/' do
3610+
puts @var # => 1
3611+
raise
3612+
end
3613+
3614+
rescue_from :all do
3615+
puts @var # => 1
3616+
end
3617+
end
3618+
```
3619+
3620+
The values of instance variables cannot be shared among various endpoints within the same API. This limitation arises due to Grape generating a new instance for each request made. Consequently, instance variables set within an endpoint during one request differ from those set during a subsequent request, as they exist within separate instances.
3621+
3622+
```ruby
3623+
class TwitterAPI < Grape::API
3624+
get '/first' do
3625+
@var = 1
3626+
puts @var # => 1
3627+
end
3628+
3629+
get '/second' do
3630+
puts @var # => nil
3631+
end
3632+
end
3633+
```
3634+
35983635
## Using Custom Middleware
35993636

36003637
### Grape Middleware

UPGRADING.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,56 @@
11
Upgrading Grape
22
===============
33

4-
### Upgrading to >= 2.0.1
4+
### Upgrading to >= 2.1.0
55

66
#### Grape::Router::Route.route_xxx methods have been removed
77

88
- `route_method` is accessible through `request_method`
99
- `route_path` is accessible through `path`
1010
- Any other `route_xyz` are accessible through `options[xyz]`
1111

12+
#### Instance variables scope
13+
14+
Due to the changes done in [#2377](https://github.com/ruby-grape/grape/pull/2377), the instance variables defined inside each of the endpoints (or inside a `before` validator) are now accessible inside the `rescue_from`. The behavior of the instance variables was undefined until `2.1.0`.
15+
16+
If you were using the same variable name defined inside an endpoint or `before` validator inside a `rescue_from` handler, you need to take in mind that you can start getting different values or you can be overriding values.
17+
18+
Before:
19+
```ruby
20+
class TwitterAPI < Grape::API
21+
before do
22+
@var = 1
23+
end
24+
25+
get '/' do
26+
puts @var # => 1
27+
raise
28+
end
29+
30+
rescue_from :all do
31+
puts @var # => nil
32+
end
33+
end
34+
```
35+
36+
After:
37+
```ruby
38+
class TwitterAPI < Grape::API
39+
before do
40+
@var = 1
41+
end
42+
43+
get '/' do
44+
puts @var # => 1
45+
raise
46+
end
47+
48+
rescue_from :all do
49+
puts @var # => 1
50+
end
51+
end
52+
```
53+
1254
### Upgrading to >= 2.0.0
1355

1456
#### Headers

lib/grape/dsl/inside_route.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,23 @@ def error!(message, status = nil, additional_headers = nil)
167167
throw :error, message: message, status: self.status, headers: headers
168168
end
169169

170+
# Creates a Rack response based on the provided message, status, and headers.
171+
# The content type in the headers is set to the default content type unless provided.
172+
# The message is HTML-escaped if the content type is 'text/html'.
173+
#
174+
# @param message [String] The content of the response.
175+
# @param status [Integer] The HTTP status code.
176+
# @params headers [Hash] (optional) Headers for the response
177+
# (default: {Rack::CONTENT_TYPE => content_type}).
178+
#
179+
# Returns:
180+
# A Rack::Response object containing the specified message, status, and headers.
181+
#
182+
def rack_response(message, status = 200, headers = { Rack::CONTENT_TYPE => content_type })
183+
message = ERB::Util.html_escape(message) if headers[Rack::CONTENT_TYPE] == 'text/html'
184+
Rack::Response.new([message], Rack::Utils.status_code(status), headers)
185+
end
186+
170187
# Redirect to a new url.
171188
#
172189
# @param url [String] The url to be redirect.

lib/grape/middleware/error.rb

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def call!(env)
4646
rescue_handler_for_any_class(e.class) ||
4747
raise
4848

49-
run_rescue_handler(handler, e)
49+
run_rescue_handler(handler, e, @env[Grape::Env::API_ENDPOINT])
5050
end
5151
end
5252

@@ -119,21 +119,29 @@ def rescue_handler_for_any_class(klass)
119119
options[:all_rescue_handler] || :default_rescue_handler
120120
end
121121

122-
def run_rescue_handler(handler, error)
122+
def run_rescue_handler(handler, error, endpoint)
123123
if handler.instance_of?(Symbol)
124124
raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)
125125

126126
handler = public_method(handler)
127127
end
128128

129-
response = handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
129+
response = (catch(:error) do
130+
handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
131+
end)
132+
133+
response = error!(response[:message], response[:status], response[:headers]) if error?(response)
130134

131135
if response.is_a?(Rack::Response)
132136
response
133137
else
134-
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new)
138+
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new, endpoint)
135139
end
136140
end
141+
142+
def error?(response)
143+
response.is_a?(Hash) && response[:message] && response[:status] && response[:headers]
144+
end
137145
end
138146
end
139147
end

spec/grape/api_spec.rb

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4352,4 +4352,61 @@ def uniqe_id_route
43524352
expect(last_response.body).to be_eql('1-2')
43534353
end
43544354
end
4355+
4356+
context 'instance variables' do
4357+
context 'when setting instance variables in a before validation' do
4358+
it 'is accessible inside the endpoint' do
4359+
expected_instance_variable_value = 'wadus'
4360+
4361+
subject.before do
4362+
@my_var = expected_instance_variable_value
4363+
end
4364+
4365+
subject.get('/') do
4366+
{ my_var: @my_var }.to_json
4367+
end
4368+
4369+
get '/'
4370+
expect(last_response.body).to eq({ my_var: expected_instance_variable_value }.to_json)
4371+
end
4372+
end
4373+
4374+
context 'when setting instance variables inside the endpoint code' do
4375+
it 'is accessible inside the rescue_from handler' do
4376+
expected_instance_variable_value = 'wadus'
4377+
4378+
subject.rescue_from(:all) do
4379+
body = { my_var: @my_var }
4380+
error!(body, 400)
4381+
end
4382+
4383+
subject.get('/') do
4384+
@my_var = expected_instance_variable_value
4385+
raise
4386+
end
4387+
4388+
get '/'
4389+
expect(last_response.status).to be 400
4390+
expect(last_response.body).to eq({ my_var: expected_instance_variable_value }.to_json)
4391+
end
4392+
4393+
it 'is NOT available in other endpoints of the same api' do
4394+
expected_instance_variable_value = 'wadus'
4395+
4396+
subject.get('/first') do
4397+
@my_var = expected_instance_variable_value
4398+
{ my_var: @my_var }.to_json
4399+
end
4400+
4401+
subject.get('/second') do
4402+
{ my_var: @my_var }.to_json
4403+
end
4404+
4405+
get '/first'
4406+
expect(last_response.body).to eq({ my_var: expected_instance_variable_value }.to_json)
4407+
get '/second'
4408+
expect(last_response.body).to eq({ my_var: nil }.to_json)
4409+
end
4410+
end
4411+
end
43554412
end

0 commit comments

Comments
 (0)