Render Rails responses as JSON... but keep Rails default HTTP response codes - ruby-on-rails

When Rails responds to an HTTP request, it's HTTP response will always be correctly headed up with an appropriate HTTP response code. This can be for successful operations (a 2xx code), such as the creation of a new ActiveRecord in the database, or for errors (a 4xx). In the latter case a rendered HTML page is supplied containing information about the error (a backtrace, etc).
My app requires that all Rails HTTP responses take the form of JSON, so I am writing my own code to render these HTTP responses accordingly. A number of tutorials talk about writing something like this to render such responses (in this example, located in user_account_controller.rb where UserAccount is the name of an ActiveRecord model):
# method to create a UserAccount object based on supplied user_account_params
def create
#user_account = UserAccount.create!(user_account_params)
if #user_account
render :status => 201, :json => {
'message' : 'Operation was successful!"
}
end
end
And, if an exception is thrown, it is possible to bind a customer exception handler as follows:
# In ApplicationController
rescue_from Exception, :with => :json_exception_handler
def json_exception_handler(exception)
render :status => 422, :json => {
:exception => {
:backtrace => exception.backtrace,
:message => exception.message
}
}
end
The problem with both of these solutions is that they require me to statically set the HTTP response codes to have the same value every time (201 and 422 in these examples). Rails already does a great job of automatically picking the best response code to use, depending on the type of error or type of successful operation. I don't want to have to reinvent the wheel badly by inventing my own response code structure for each error and operation in my app.
So my question is this: how do I supply the response code that Rails will have automatically chosen anyway, whilst retaining the ability to print out custom JSON?

Related

Rails 5 way to handle ActionController::ParameterMissing

If a parameter that's required is missing using strong parameters, the Rails server will respond with an HTTP 500.
This does not give me control over giving the user feedback with what exactly went wrong. Does it not make sense to be able to send them back a message such a required parameter is missing?
What is the "Rails way" of giving appropriate user feedback on ActionController::ParameterMissing? Is one supposed to capture the exception and handle your request response there? It seems wrong to do that in every controller.
You can use
rescue_from ActionController::ParameterMissing do |e|
render 'something'
end
in your ApplicationController (or whatever your parent controller is).
As to whether you should inform users or not, I think it depends on what your controllers are doing. If they are API controllers, it definitely makes sense to handle that gracefully, as the users are responsible for preparing the input.
If they are accepting data from your HTML forms it's, in my opinion, not that important as the missing parameters probably mean that the user tinkered with the HTML, or something went really wrong inside the browser.
Since you mention wanting to communicate the error specifics back to the user, you could do something like the following:
# app/controllers/application_controller.rb
rescue_from ActionController::ParameterMissing do |exception|
render json: { error: exception.message }, status: :bad_request
end
You can also define a method to handle a specific exception, if you'd prefer to break up the handling logic:
# app/controllers/application_controller.rb
rescue_from ActionController::ParameterMissing, with: :handle_parameter_missing
def handle_parameter_missing(exception)
render json: { error: exception.message }, status: :bad_request
end
Both of the above examples will return a JSON response like so: {"error"=>"param is missing or the value is empty: [field_name]"}
For an API-only application, I think this is valuable information to pass on.
More info:
Rails API rescue_from documentation
Handling Errors in an API Application the Rails Way

How to properly use params.require in Rails

I've read several | articles about using params.require(...) in Rails, but nothing that shows them in a non-trivial, real-world scenario.
Specifically, the following URL will be called:
GET http://myapp.example.com/widgets/{clientUuid}
Where {clientUuid} will be a string. I just want to check (from the proper controller action) whether the provided {clientUuid} is non-null and non-empty. I'm wondering if I can just do this:
if params.require(params[:clientUuid]) == null
response = { "error" => "bad client uuid" }
render json: response, status: :bad_request
return
end
And have non-nullness/non-emptiness enforced? If not, what can I do to achieve my desired result?
You're overcomplicating a simple GET request by messing up the route and using a method thats meant for a completely different use.
The idea is that .requires should be used for non-idempotent request methods (POST, PUT,PATCH) where the request contains a body with parameters. It lets you take a single key from the params and whitelist the params contained - which matches the Rails ideom of nesting inputs in a hash with the name of the resource as the root key.
In that case using .requires lets you return a response code to the client that indicates that the request cannot be processed (422 - Unprocessable Entity) as the request body does not have the right structure.
While you could potentially use it creatively on a GET request its wrong from a restful application engineering standpoint. In your case you should be returning a 404 - Not found response code if the clientUuid does not match a record. Usually in rails this is done by using .find which will raise a ActiveRecord::RecordNotFound exception which the framework catches.
Additionally if you have declared the route properly in the first place rails would actually give a 404 automatically as the request would not match if the id segment is missing.
class WidgetsController < ApplicationController
def show
#widget = Widget.find(params[:clientUuid])
end
end
If you want you could bail early so that the database is never queried if the param does not match a condition:
class WidgetsController < ApplicationController
def show
raise ActiveRecord::RecordNotFound if params[:clientUuid].blank?
#widget = Widget.find(params[:clientUuid])
end
end
You can just write:
if params[:clientUuid].blank?
response = { "error" => "bad client uuid" }
render json: response, status: :bad_request
return
end
With params.require it is a bit more difficult, because require raises a ActionController::ParameterMissing exception if the parameter is missing, but allows the parameter to return false (what I guess is still invalid in your example):
begin
uuid = params.require(:cliendUuid)
rescue ActionController::ParameterMissing
# nothing to do, just ensure the exceptions is rescued
end
unless uuid
# handle missing uuid
end
Or:
begin
uuid = params.require(:cliendUuid) || raise ActionController::ParameterMissing
rescue ActionController::ParameterMissing
# handle missing uuid
end
The article you posted re strong parameters is specifically about protecting your database data from user input, usually provided by forms.
params.require(:user).permit(:username)
The above code specifies that for the model User only allow the attribute username to be touched. If you try to update or create a user record in the user table with any other attribute e.g. email, you would get an error because the email attribute has not been 'permitted'. This is what is meant by whitelisting. You will only see the above code in create or update controller methods, or any other method that amends the data in some way. (An exception, of course, is deleting a record).
In your example, the parameter is provided as part of the url which you can also access via the rails provided params hash. However, as your method is not interacting with the db, you don't need to run it through the permit method.
This resource may help.

Alternative to activeResource.save (Ruby)

Just being very basic here. I'm trying to post an activeResource as Json and recover it on the other side. At present, I seem to be able to send JSON but not recreate it into an Object of my choice.
I've tried my hand at generating a post request with Json in Ruby.
There are 2 parts to be aware of, The active resource on client side and the controller handling the incoming request. The errors usually come down to 406 (request not acceptable) or 422 (Unprocessable Entity)
I suppose the basic question is how can I fix these?
Here is my active resource class.
class UserRequest < ActiveResource::Base
self.site = "http://localhost:3008"
self.format = :json
#do not define defs as 'self'.whatever
def post_me
res = Net::HTTP.post_form(URI.parse('http://127.0.0.1:3008/user_requests.xml'),
{'url' =>'www.stackoverflow.com', 'depth' => '50'})
end
end
#Just to clarify. If im sending Json, I change the URI to .json and If I'm using xml, I #remove the self.format because active resource is XML by default
and here is line in controller which turns it to active record. controllers titled UserRequestsController
def add_request
#user_request = UserRequest.new(params[:user_request])
.
.
. end
route is
match '/user_requests(.:format)' => 'user_requests#add_request', :via =>:post
Now a few things before everyone face palms and goes mad at me.
I'm aware that in the def for add_request I'm missing JSON.parse. This is because when I include it I get 500 and an uninitialized constant error on server side. The gem is definitely installed and I'm not the only one whos had the problem but sadly no fixes.
http://railsforum.com/viewtopic.php?id=4654
http://railsforum.com/viewtopic.php?id=28301
So theres one question is how do i fix the parse so that it works the way I want to as I'm assuming the 406 is because its receiving Json but not sending it.
This would be ideal however if its not possible or someone can think of a reason why I should use XML, then perhaps they could take a look at this and tell me why I might be getting it on server side
Started POST "/user_requests.xml" for 127.0.0.1 at Thu Jul 14 09:38:34 +0100 2011
Processing by UserRequestsController#add_request as XML
Parameters: {"depth"=>"50", "url"=>"www.stackoverflow.com"}
SQL (0.2ms) SELECT 1 FROM "user_requests" WHERE ("user_requests"."url" IS NULL) LIMIT 1
Completed 422 Unprocessable Entity in 18ms (Views: 3.9ms | ActiveRecord: 0.2ms)
Just one last thing is the reasoin I'm not using .save is because the lead on my intern program doesn't like and says its not fit for purpose.
I had a similar problem. Try modifying your "add_request" method like this:
def add_request
#user_request = UserRequest.new(params[:user_request])
format.json { render :json => #user_request, :status => :created, :location => #user_request }
end

Catch initialization error in Ruby on Rails

What I want to do: redirect the user to a special error page if the database is down.
I'm using an Oracle database with the OCI adapter.
It appears that, if the database is unavailable (say, down for backup), that the OCI adapter throws an error before I ever hit a controller (based on the stack trace, it's while it's setting up the connection pool). So I can't use rescue_from or rescue_action, even in ApplicationController -- the rescue line is never reached.
Is there any way to "wrap" the initialization or otherwise rescue a particular error at a higher level (or earlier point) than ApplicationController?
Try overriding ActionController::Failsafe, or sticking another middleware before it on the stack, you should be able to catch almost all exceptions from there, and 302 at will.
Example:
class DbFailsafe
def new(app)
#app = app
end
def call(env)
#app.call(env)
rescue DataBaseError => e
[302, {"Content-Type" => "text/plain", "Location" => "http://otherserver.com/sorrt-the-site-is-down.html"}, ["You are being redirected to http://otherserver.com/sorrt-the-site-is-down.html"]]
end
end
and in your environment.rb
config.middleware.insert_after ::ActionController::Failsafe, DbFailsafe
I'd recommend (because a we should be returing a 5xx not a 3xx) rendering a simple page with a js redirect. This is simple to do, just edit public/500.html.

activeresource error status and response body

I am making an activeresource call to a service, and I'd like some custom error messages as feedback. I have some validations that aren't normal model validations, so I can't just return #object.errors.
So, for instance, one of my validations is this. Two objects have a many to many relationship, but I want to restrict one object to only have a limited number (say 2) of relationships to other objects. Here's some code:
In the client:
response = Customer.find(customer_id).put(:add_user, :user_id => user_id)
This puts a request to add a user to the customer. Then in the service I want to check that this addition is valid.
def add_user
#user = User.find(params[:user_id])
#customer = Customer.find(params[:id])
if #customer.users.length > 2
render :xml => "ERR_only_2_users_allowed", :status => :unprocessable_entity
end
end
Here's my problem. In active resource, if the return status is an error, the client side completely fails. I could change the status to 200 and I get back the body err msg fine, but this seems to defeat the purpose of having error reponse codes.
I can put the whole request call from the client in a begin/rescue block
begin
response = Customer.find(customer_id).put(:add_user, :user_id => user_id)
rescue ActiveResource::ResourceInvalid => e
#return error code
end
but when I catch the 422 (unprocessable_entity) response, I get nothing of the body back, so I don't get my custom error message. response = nil
Does anyone know how I can achieve these custom error message with the proper response codes?
This may or may not be your problem, but both of ours seem very close. I'm using a custom put method, but his should work for you too. What's going on is that the code that does this:
rescue ResourceInvalid => error
errors.from_xml(error.response.body)
end
Is only working with the standard save method. If you want errors added when other methods are called it looks like you need to do it yourself.
I had to add it to
vendor/rails/activeresource/lib/active_resource/custom_methods.rb
Here is what my diff from git looks like:
old code:
def put(method_name, options = {}, body = '')
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
end
new code:
def put(method_name, options = {}, body = '')
begin
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
rescue ResourceInvalid => error
errors.from_xml(error.response.body)
end
self
end
So look at the stack trace when get the exception thrown for the 422 and see which method it's calling exactly. Then add something like what I have and you should be good to go.
Don't ask me why the activeresource folks thought validations should only work with their save method. the save method does a create or update, but calling 'put or post' is the exact same thing, IMO. If we want validations to work on save we want them to work on put and post...anyway give it a shot.
I'm not sure if i need the self at the end...i may not. I'm not totally done with this as I just figured out how to make it work.
Erik
I think that your problem might be the response isn't an xml document but just a plain string. Try changing your render statement to something like:
render :xml => { :error => "ERR_only_2_users_allowed" }, :status => :unprocessable_entity

Resources