Skip to content

Commit

Permalink
fix(ruby-grape#1922): Allow to use instance variables defined in the …
Browse files Browse the repository at this point in the history
…endpoints inside rescue_from (ruby-grape#2377)

* fix(ruby-grape#1922): Allow to use instance variables defined in the endpoints when rescue_from

* fix(ruby-grape#1922): Update CHANGELOG and running rubocop

* fix(ruby-grape#1922): Updating UPGRADING and README files explaining the instance variables behavior. Extra tests added

* fix(ruby-grape#1922): Send endpoint parameter as the last param of the run_rescue_handler method

* fix(ruby-grape#1922): Adding short before/after example to UPGRADING

* fix(ruby-grape#1922): Fixing CHANGELOG format style
  • Loading branch information
jcagarcia authored Nov 24, 2023
1 parent 3f01d03 commit 103928a
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#### Features

* [#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).
* [#2377](https://github.com/ruby-grape/grape/pull/2377): Allow to use instance variables values inside `rescue_from` - [@jcagarcia](https://github.com/jcagarcia).
* Your contribution here.

#### Fixes
Expand Down
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
- [Current Route and Endpoint](#current-route-and-endpoint)
- [Before, After and Finally](#before-after-and-finally)
- [Anchoring](#anchoring)
- [Instance Variables](#instance-variables)
- [Using Custom Middleware](#using-custom-middleware)
- [Grape Middleware](#grape-middleware)
- [Rails Middleware](#rails-middleware)
Expand Down Expand Up @@ -3595,6 +3596,42 @@ end
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.
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.

## Instance Variables

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.

```ruby
class TwitterAPI < Grape::API
before do
@var = 1
end

get '/' do
puts @var # => 1
raise
end

rescue_from :all do
puts @var # => 1
end
end
```

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.

```ruby
class TwitterAPI < Grape::API
get '/first' do
@var = 1
puts @var # => 1
end

get '/second' do
puts @var # => nil
end
end
```

## Using Custom Middleware

### Grape Middleware
Expand Down
44 changes: 43 additions & 1 deletion UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,56 @@
Upgrading Grape
===============

### Upgrading to >= 2.0.1
### Upgrading to >= 2.1.0

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

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

#### Instance variables scope

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`.

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.

Before:
```ruby
class TwitterAPI < Grape::API
before do
@var = 1
end

get '/' do
puts @var # => 1
raise
end

rescue_from :all do
puts @var # => nil
end
end
```

After:
```ruby
class TwitterAPI < Grape::API
before do
@var = 1
end

get '/' do
puts @var # => 1
raise
end

rescue_from :all do
puts @var # => 1
end
end
```

### Upgrading to >= 2.0.0

#### Headers
Expand Down
17 changes: 17 additions & 0 deletions lib/grape/dsl/inside_route.rb
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,23 @@ def error!(message, status = nil, additional_headers = nil)
throw :error, message: message, status: self.status, headers: headers
end

# Creates a Rack response based on the provided message, status, and headers.
# The content type in the headers is set to the default content type unless provided.
# The message is HTML-escaped if the content type is 'text/html'.
#
# @param message [String] The content of the response.
# @param status [Integer] The HTTP status code.
# @params headers [Hash] (optional) Headers for the response
# (default: {Rack::CONTENT_TYPE => content_type}).
#
# Returns:
# A Rack::Response object containing the specified message, status, and headers.
#
def rack_response(message, status = 200, headers = { Rack::CONTENT_TYPE => content_type })
message = ERB::Util.html_escape(message) if headers[Rack::CONTENT_TYPE] == 'text/html'
Rack::Response.new([message], Rack::Utils.status_code(status), headers)
end

# Redirect to a new url.
#
# @param url [String] The url to be redirect.
Expand Down
16 changes: 12 additions & 4 deletions lib/grape/middleware/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def call!(env)
rescue_handler_for_any_class(e.class) ||
raise

run_rescue_handler(handler, e)
run_rescue_handler(handler, e, @env[Grape::Env::API_ENDPOINT])
end
end

Expand Down Expand Up @@ -119,21 +119,29 @@ def rescue_handler_for_any_class(klass)
options[:all_rescue_handler] || :default_rescue_handler
end

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

handler = public_method(handler)
end

response = handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
response = (catch(:error) do
handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
end)

response = error!(response[:message], response[:status], response[:headers]) if error?(response)

if response.is_a?(Rack::Response)
response
else
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new)
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new, endpoint)
end
end

def error?(response)
response.is_a?(Hash) && response[:message] && response[:status] && response[:headers]
end
end
end
end
57 changes: 57 additions & 0 deletions spec/grape/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4352,4 +4352,61 @@ def uniqe_id_route
expect(last_response.body).to be_eql('1-2')
end
end

context 'instance variables' do
context 'when setting instance variables in a before validation' do
it 'is accessible inside the endpoint' do
expected_instance_variable_value = 'wadus'

subject.before do
@my_var = expected_instance_variable_value
end

subject.get('/') do
{ my_var: @my_var }.to_json
end

get '/'
expect(last_response.body).to eq({ my_var: expected_instance_variable_value }.to_json)
end
end

context 'when setting instance variables inside the endpoint code' do
it 'is accessible inside the rescue_from handler' do
expected_instance_variable_value = 'wadus'

subject.rescue_from(:all) do
body = { my_var: @my_var }
error!(body, 400)
end

subject.get('/') do
@my_var = expected_instance_variable_value
raise
end

get '/'
expect(last_response.status).to be 400
expect(last_response.body).to eq({ my_var: expected_instance_variable_value }.to_json)
end

it 'is NOT available in other endpoints of the same api' do
expected_instance_variable_value = 'wadus'

subject.get('/first') do
@my_var = expected_instance_variable_value
{ my_var: @my_var }.to_json
end

subject.get('/second') do
{ my_var: @my_var }.to_json
end

get '/first'
expect(last_response.body).to eq({ my_var: expected_instance_variable_value }.to_json)
get '/second'
expect(last_response.body).to eq({ my_var: nil }.to_json)
end
end
end
end

0 comments on commit 103928a

Please sign in to comment.