Rails wont render a RestClient exception body unless I interpolate it - ruby-on-rails

I'm having a really weird issue trying to write a test around a rails controller.
The controller uses RestClient to get info from another server. If that 2nd server returns a bad response, I want to forward on that error message to the client.
The controller:
def callback_submit
begin
response = RestClient.post(...)
render json: response
rescue RestClient::BadRequest => e
render status: :bad_request, json: { error: e.response.body }
end
end
Then I have an rspec test for this:
it 'returns a failure message' do
stub_request( :post, 'http://...' ).to_return( status: 400, body: 'some error' )
post :callback_submit
expect(response.body).to eq({error: 'some error'}.to_json)
end
Now the problem is that the rspec output is:
expected: "{\"error\":\"some error\"}"
got: "{\"error\":\"\"}"
Where it gets weird is that I can change my controller to puts some details...
rescue RestClient::BadRequest => e
error = e.response.body.to_s
puts error.class
puts error
render status: :bad_request, json: { error: error }
Which outputs:
String
some error
expected: "{\"error\":\"some error\"}"
got: "{\"error\":\"\"}"
So the error variable seems to be a string and the correct value, but render turns it into an empty string.
Now it gets really weird... If I interpolate the string:
rescue RestClient::BadRequest => e
error = e.response.body.to_s
puts error.class
puts error
render status: :bad_request, json: { error: "#{error}" }
Or just simply:
rescue RestClient::BadRequest => e
render status: :bad_request, json: { error: "#{e.response.body}" }
Then my test PASSES!
At first I thought e.response.body must not be a String, which is why I puts its class which prints "String" and also called .to_s on it, but that didn't seem to change anything.
Can anyone explain this weird behavior?

Asked some Ruby friends and one of them found the problem, but didn't want to come post an answer here....
The problem ends up being that we are on an older RestClient (1.8). In the RestClient 2.0 notes:
Response objects are now a subclass of String rather than a String that mixes in the response functionality.
Response#body and #to_s will now return a true String object rather than self. Previously there was no easy way to get the true String response instead of the Frankenstein response string object with AbstractResponse mixed in.
So in the older version I am using, e.response ends up being this weird String mixin mashup thing, and render doesn't seem to like it.
The interpolation turns it back into a real Ruby string again.

The problem is that response.body is looking at the response object and the body is empty because of the error.
Chances are you want e.message or just e.to_s which will be the error message (or the error class name) rather than the response body.
Excerpt:
class Exception < RuntimeError
attr_accessor :response
attr_writer :message
#.......
def to_s
message
end
def message
#message || default_message
end
def default_message
self.class.name
end
end
Source

Related

Getting part of the raw SQL in Rails API response for not found records

I created a Rails 5 app with config.api_only set to true, and implemented a controller action that deletes a record in a scoped model Message the problem is that when I try to delete a record that doesn't exist, the ActiveRecord::RecordNotFound exception message includes part of the SQL query that is used in the scope, is possible to not include that SQL in the exception message?
Code of the model:
class Message < ActiveRecord::Base
scope(:active) { where(active: true) }
end
Code of the controller
class MessageController < ApplicationController
def delete
message = Message.active.find(params[:id])
begin
message.destroy
head :accepted # 202
rescue ActiveRecord::RecordNotFound => error
render json: { error: error.message }, status: 404
end
end
end
I would expect to get the next response if I send a wrong ID:
{
"error": "Couldn't find Message with 'id'=23444"
}
But instead, I'm getting this error:
{
"error": "Couldn't find Message with 'id'=23444 [WHERE \"message\".\"active\" = $1]"
}
As far as I know, there is no configuration to change the ActiveRecord::RecordNotFound Exception error message. The best thing you can do is fetch for the Message without the scope and then check if it's active or not before performing the destroy and return appropriate error message.
class MessageController < ApplicationController
def delete
message = Message.find(params[:id])
if message.active
message.destroy
head :accepted # 202
else
render json: { error: "Couldn't find Message with 'id'=#{params[:id]}" }, status: 404
end
rescue ActiveRecord::RecordNotFound => error
render json: { error: error.message }, status: 404
end
end
I assume you are running your application in 'development' mode when you are seeing the SQL part in the error message. By default, Rails will no longer include that information when running in 'production' mode.

Ruby API response view : how can I render a JSON response?

I'm new to Ruby, trying to building an API.
I've followed a tutorial and was able to return a JSON response when calling an API endpoint.
In this example, the function called raises an error that I want to pass as a JSON response.
my_controller.rb
class MyController < ApplicationController
def getTracklist
begin
importer = #this raises an error
rescue StandardError => e
#response = {
error: e.message,
}
return #response
end
end
end
my view look like this :
getTracklist.json.jbuilder
json.response #response
thing is,
this works but renders my response as
{"response":{"error":"the error message"}}
while I want it as
{"error":"the error message"}
I made an attempts by changing my view to
json #response
but it fails :
ActionView::Template::Error (undefined method `json' for
<#:0x0000559304675470> Did you mean? JSON):
1: json #response
So how could I render my response "fully" without having to put it in a property ?
I've also seen when reading stuff about ROR that this code is sometimes used, and I was wondering how I could use it in this situation :
render json: { error_code:'not_found', error: e.message }, status: :not_found
Thanks !
There are multiple ways of achieving what you want. You could merge! the response into the jbuilder root.
json.merge! #response
The above merges all key/value-pairs into the jbuilder root. You could also opt to extract! specific attributes.
json.extract! #response, :error
Alternatively you can simply render it in the controller, since you've already composed the structure the following would be enough.
render json: #response
You can do this for jBuilder:
json.merge!(#response)
Source
class MyController < ApplicationController
def getTracklist
begin
# you need to assign something to a variable
rescue StandardError => e
respond_to do |format|
format.any(:json, :js) do
render :json => {:error => e.message}
end
end
end
end
end
Making these changes to your controller can help you with your requirements.
You don't need a view after doing this.

Why is yield not passing the result to block (Rails)?

I know there are several SO questions as well as online articles on using yield in Rails. But I'm still having trouble understanding what's wrong with my code below, and would appreciate any advice.
In my app, I have:
A controller that passes data to the command class's run method, and returns the request status based on the result of the Command.run (true/false)
A command class that deals with the actual meat of the process, then yields true if it succeeded, or false if it failed
However, the command class seems to be failing to yield the results to my controller. According to the error messages when I run my tests, it seems like my block in the controller isn't being recognized as a block:
# If I use "yield result":
LocalJumpError: no block given (yield)
# If I use "yield result if block_given?":
# (This is because I have "assert_response :success" in my tests)
Expected response to be a <2XX: success>, but was a <400: Bad Request>
How should I rewrite the block (do ... end part in the controller below) so that yield works correctly? Or if the issue lies elsewhere, what am I doing wrong?
I've provided a simplified version of my code below. Thank you in advance!
# controller
def create
Command.run(params) do
render json: { message: 'Successfully processed request' }
return
end
render json: { message: 'Encountered an error' }, status: :bad_request
end
# command class
def run(params)
# Do some stuff, then send HTTP request
# "result" below returns true or false
result = send_http_request.parsed_response == 'ok'
yield result
end
def self.run(params)
new.run(params)
end
Note: This code works if I use if true... else... in the controller instead of a block, and just return the boolean result instead of yielding it. But here I'd like to know how to make yield work.
In your controller you need to have a variable for the result.
def create
Command.run(params) do |result|
if result
render json: { message: 'Successfully processed request' }, status: :success
else
render json: { message: 'Encountered an error' }, status: :bad_request
end
return
end
render json: { message: 'Encountered an error' }, status: :bad_request
end
(EDIT)
Also, you are calling the class method which call the instance method. You have to pass the block from the calling code to the instance method you are calling.
def self.run(params, &block)
new.run(params, &block)
end
EDIT: ah, so you have a class method run and instance method run.
Either do as Marlin has suggested and supply the block explicitly from class method to the instance method.
Or use only the class method as I've initially suggested (it doesn't
seem like there's any reason to instantiate Command in your case):
def self.run(params, &block)
result = send_http_request.parsed_response == 'ok'
block.yield(result)
end

How to handle bad requests / wrong parameters in a rails api?

For example, an api consumer sends:
"ticket": [{ "param": "value"}]
The controller does:
params.require(:ticket).permit(:name)
This would return a 500 error: "Undefined method permit for array"
Is there a DRY / best practice way to handle this? I think a status 400 should be returned instead.
I think the strong parameters gem should handle this internally, but they don't, so here is my solution
rescue_from NoMethodError do |exception|
if exception.message =~ /undefined method `permit' for/
render_error(message: 'Invalid parameter format.', type: :invalid_parameters, status: :bad_request)
else
raise
end
end
Based on your solution .. you would want to handle all the possibilities verse a specific error response. You can set in a rescue in your render method like this and set unauthorized if there is a method error or just something else going wrong :
begin
render json: json, status: 200
rescue_from NoMethodError do |exception|
render :unauthorized
end
rescue => e
render :unauthorized
end

Rails 4.2 ActionController:BadRequest custom error message

I want to return from my controller if either a validation failed or a parameter is missing with 400 - bad request. So in my controller if have
if params["patch"].nil? then
raise ActionController::BadRequest.new( "The Json body needs to be wrapped inside a \"Patch\" key")
end
and i catch this error in my Application Controller with:
rescue_from ActionController::BadRequest, with: :bad_request
def bad_request(exception)
render status: 400, json: {:error => exception.message}.to_json
end
But it seems like i cannot add custom messages when raising ActionController::BadRequest. Because when passing an invalid body the response is only {"error":"ActionController::BadRequest"} and not the hash i provided.
In the console i get the same behaviour. raise ActiveRecord::RecordNotFound, "foo" indeed raises ActiveRecord::RecordNotFound: foo.
But raise ActionController::BadRequest, "baa" results in
ActionController::BadRequest: ActionController::BadRequest
How can i add custom messages to the BadRequest exception?
Try this:
raise ActionController::BadRequest.new(), "The Json body needs to be wrapped inside a \"Patch\" key"

Resources