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.
Related
Im trying to test if the format send through the request url is json or not?
so in link_to I sent the format like this
<%= link_to "Embed", {:controller=>'api/oembed' ,:action => 'show',:url => catalog_url, format: 'xml'} %>
In the relevant controller I catch the param and raise the exception like this
format_request = params[:format]
if format_request != "json"
raise DRI::Exceptions::NotImplemented
end
but the exception wont display instead the server simply ran into internal error but if I changed the param inside the controller then exception displayed so if the url is like this
<%= link_to "Embed", {:controller=>'api/oembed' ,:action => 'show',:url => catalog_url, format: 'json'} %>
format_request = "xml"
if format_request != "json"
raise DRI::Exceptions::NotImplemented
end
why 501 exception does not triggered if I send the format as xml in url? Im doing it for the testing purpose that in case if someone send the request with wrong format 501 expetion show up
Use ActionController::MimeResponds instead of badly reinventing the wheel:
# or whatever your base controller class is
class ApplicationController < ActionController::API
# MimeResponds is not included in ActionController::API
include ActionController::MimeResponds
# Defining this in your parent class avoids repeating the same error handling code
rescue_from ActionController::UnknownFormat do
raise DRI::Exceptions::NotImplemented # do you really need to add another layer of complexity?
end
end
module Api
class OembedController < ApplicationController
def oembed
respond_to :json
end
end
end
If you don't use respond_to Rails will implicitly assume that the controller responds to all response formats. But if you explicitly list the formats you respond to with a list of symbols (or the more common block syntax) Rails will raise ActionController::UnknownFormat if the request format is not listed. You can rescue exceptions with rescue_from which lets you use inheritance instead of repeating yourself with the same error handling.
As #max mentions, sending the format: 'xml' is unnecessary because Rails already knows the format of the request.
<%= link_to "Embed", {:controller=>'api/oembed' ,:action => 'show',:url => catalog_url } %>
In the controller:
def oembed
respond_to do |format|
format.json { # do the thing you want }
format.any(:xml, :html) { # render your DRI::Exceptions::NotImplemented }
end
end
Or if you want more control you could throw to a custom action:
def oembed
respond_to do |format|
format.json { # do the thing you want }
format.any(:xml, :html) { render not_implemented }
end
end
def not_implemented
# just suggestions here
flash[:notice] = 'No support for non-JSON requests at this time'
redirect_to return_path
# or if you really want to throw an error
raise DRI::Exceptions::NotImplemented
end
If you really want to reinvent the wheel (it's your wheel, reinvent if you want to):
I'd rename format to something else, it's probably reserved and might give you problems
<%= link_to "Embed", {:controller=>'api/oembed' ,:action => 'show',:url => catalog_url, custom_format: 'xml'} %>
Then, in your controller, you need to explicitly allow this parameter:
def oembed
raise DRI::Exceptions::NotImplemented unless format_params[:custom_format] == 'json'
end
private
def format_params
params.permit(:custom_format)
end
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
I am a newbie with Ruby on Rails.
This code returns DoubleRenderError:
class PostapisController < ApplicationController
def method1
method2()
render json: {:success: false}
end
def method2
render json: {:status => 'ok'} and return
end
end
Why does it return DoubleRenderError? How can I fix it if I still want both method1() and method2() to call render?
"render" and "redirect_to" are both ways to generate the response to the request your server received. The server can only give one response to each request, so you can't render twice.
In your case, you should be able to appreciate that it doesn't make sense to send a {:success: false} response AND send a {:status => 'ok'} response: it's got to be one or the other, right? That's not a rails thing, it's just a web thing. One response per request.
I don't know what you're trying to achieve, so can't advise any further, other than by telling you to read some more basic stuff about Rails before starting to use it.
I'd like a Rails controller (all of them, actually, it's an API) to render JSON always always.
I don't want Rails to return "route not found", or try and fail to find an HTML template, or return 406. I just want it to automatically and always render JSON, e.g. from a RABL or JBuilder view.
Is this possible? Related questions seem to have answers that have the aforementioned downsides.
You can add a before_filter in your controller to set the request format to json:
# app/controllers/foos_controller.rb
before_action :set_default_response_format
protected
def set_default_response_format
request.format = :json
end
This will set all response format to json. If you want to allow other formats, you could check for the presence of format parameter when setting request.format, for e.g:
def set_default_response_format
request.format = :json unless params[:format]
end
You can use format.any:
def action
respond_to do |format|
format.any { render json: your_json, content_type: 'application/json' }
end
end
It's just:
render formats: :json
I had similar issue but with '.js' extension. To solve I did the following in the view:
<%= params.except!(:format) %>
<%= will_paginate #posts %>
I tried the above solutions and it didn't solve my use case.
In some of the controllers of my Rails 4.2 app, there was no explicit render called. For example, a service object was called and nothing was returned. Since they are json api controllers, rails was complaining with a missing template error. To resolve I added this to our base controller.
def render(*args)
options = args.first
options.present? ? super : super(json: {}, status: :ok)
end
It's a large app I'm converting to Rails 5, so this is just a safety measure as I removed the RocketPants gem that seemed to do this automatically.
As a note, my controllers inherit from ActionController::Base
Of course:
before_filter :always_json
protected
def always_json
params[:format] = "json"
end
You should probably put this in a root controller for your API.
I have a model called Entity and the create action in the controller looks like this:
# enitities_controller.rb
def create
# loading params, etc...
#entity.save
respond_with #entity
end
I am using jbuilder for custom JSON views rather than rendering #entity.to_json, which works great. I have one last issue, which is when the model won't save due to validation errors I get the following response (with status 422 Unprocessable Entity):
{"errors":{"parent_share":["can't be blank","is not a number"]}}
I would like to override this json with my own. I am aware of he possibility to replace respond_with #entity with:
respond_with #entity do |format|
if #entity.errors.any?
format.json {
render "entities/create", :status => :unprocessable_entity
}
end
end
But shouldn't there be a more auto-magic way by defining some sort of errors view or something? This feels a bit dirty AND it makes me have to write more code each time I need this rather than allowing me to use respond_with. Is there another way?
Meanwhile I have found the answer:
You have to create the file lib/application_responder.rb and add the following:
class ApplicationResponder < ActionController::Responder
include Responders::FlashResponder
include Responders::HttpCacheResponder
def to_json
set_flash_message! if set_flash_message?
if !has_errors? || response_overridden?
default_render
else
controller.default_render( status: :unprocessable_entity )
end
end
end
And add the following to your application responder:
self.responder = ApplicationResponder
What this does is add a to_json method that will copy the behaviour of the to_js responder.