Rails: what's providing my API with default exception-handling? - ruby-on-rails

I'm working on a Rails 5 JSON API, and I've noticed that it's providing sensible exception-handling out of the box.
For example, ActiveRecord::RecordNotFound results in a 404, ActionController::ParameterMissing results in 400, and these are all returned as well-formed JSON responses.
I've found plenty of documentation on rescue_from, can't locate what's providing this exception-handling for me.
I've dug around in the Rails guides, the docs, as well as in the rails console, e.g.
ActionController::API.new.rescue_handlers
=> []
ApplicationController.new.rescue_handlers
=> []
I'd like to know where the exception-handling is coming from because I've noticed that it's gracefully applied for real requests, but not in controller specs.
For example, if I run a local server and send it a malformed request with curl, I get a sensible error response. But if I try to reproduce that in a controller spec,
it "returns 404 if Yogurt doesn't exist" do
put :update, params: { id: 293459 }
end
the test throws the raw exception (ActiveRecord::RecordNotFound in this case).

When you set up a Rails 5 app with the --api option you end up using ActionController::API. https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/api.rb
The modules are built here: https://github.com/rails/rails/blob/a0061d2389a178b093f0d3f64f58236ffbe088e0/actionpack/lib/action_controller/api.rb#L104 so that's where your responses are coming ultimately from. Looks like the rendering is being handed off to metal at some point.
API actions are configured to return :json by default, so that's why your test is behaving differently than your browser.
For your spec to work the same way you'll have to supply a format:
put :update, params: { id: 293459 }, format: :json

Related

Can't use rails http status on grape exception?

I'm new to rails programming and i'm trying to use rails status code when raising a grape exception but my tests are failing. I couldn't find any example on grape documentation so this may be impossible.
I want to know if it is possible and if not, why so?
This works:
rescue_from Example::IsExampleWorking do |_e|
error!({ messages: ["Example is not working because it was not found"] }, 404)
end
But this does not:
rescue_from Example::IsExampleWorking do |_e|
error!({ messages: ["Example is not working because it was not found"] }, :not_found)
end
My test is:
it "return a 404 Not Found status" do
expect(response).to have_http_status(:not_found)
end
edit: guys, I forgot to mention that the error message from he test is
undefined method `to_i' for :not_acceptable:Symbol
Did you mean? to_s
But I didn't find any documentation on grape's docs to make sure they accept only integer as second param.
Thank you :)
Grape allows specification of type when defining parameters.
params do
# Basic usage: require a parameter of a certain type
requires :param_name, type: Integer
end
See: https://www.rubydoc.info/github/ruby-grape/grape/Grape%2FDSL%2FParameters:requires
Http status code are always integer only. That is the industry standard. You can not change the http status code or send http status code as string.
Find the list of http status codes here.
https://www.restapitutorial.com/httpstatuscodes.html
And this is not specific to rails or grape or any other framework.

Do I need to generate a controller for a Ruby on Rails Controller call?

I'm setting a session variable to a user's geographical state. I have to use a session variable because I run code on the server specific to that user on page load and I need to know where they are. This code is set up to just update the session variable.
states_controller.rb
class StatesController < ApplicationController
def loc
session[:location] = params[:location]
end
end
routes.rb
post "states/loc" => "states#loc"
The code routes properly and the session variable is updated.
However, when the process is complete I get a 500 error in the console "Missing Template" in the views directory. I haven't seen any tutorials tell users to call the command "rails generate controller" and I'm in the unique situation where I can't call this command.
What possible side effect are there to ignoring this 500 error?
*I'm running an older version of ruby and rails.
What possible side effect are there to ignoring this 500 error?
Each request is crashing your rails server. Thats not good. Since it means that some cases it may have to restart after every failed request - that eats resources like Homer Simpson at a buffet.
Your app should not be raising uncaught exceptions that cause 500 errors - thats just decent professional practice.
So how do I fix it?
Simple, if you don't want the default behavior of rendering a view tell rails to do something else:
class StatesController < ApplicationController
def loc
session[:location] = params[:location]
head :created
end
end
This sends an empty response with the 201 - CREATED http header.
See Rails Guides - Layouts and Rendering in Rails

Rails JSONP Parsing error on receiving end

I have a very specific issue...
I have 2 rails apps:
App 1 makes a request to App 2 and gets a valid JSONP response.
App 2 has been outfitted with Rack JSONP Middleware so it returns with the
correct wrapped callback (i.e. JQuery1919191([{[{"find_item":{"geo_lon":-74....etc)
For some reason App 1 throws the following error in javascript console:
JSONP Error: parsererror Error: jQueryXXXXXX was not called.
It works fine, however, when testing this in a non-rails context. See my fiddle for an example: http://jsfiddle.net/pBDyW/16/ This code (that works in this example) is the exact same code I'm using in my rails app.
What I'm confused about is HOW am I supposed to configure App 1 (the receiving app) to handle an incoming JSONP resuqest properly. I'm convinced it must be something with Rails, as calling this from a generic page, served in Apache, works fine.
Please HELP!
It turns out that there are (for whatever reason) subtleties in the way the rails app (App 1) seems to process the incoming request. I modified my approach, which was to render the JSON response in my controller with:
respond_to :json
def json_result
#blah = [stuff from database]
respond_with #blah
end
methods (recommended with the Rack JSON Middlware approach, and restored my JSON response to:
render :json => #find_results, :callback => params[:callback]
The difference here is an odd one. The rails app JSONP call will only succeed if I execute the response like this:
success: callbackFunction
and NOT
success: function(){//do stuff}
However, the latter example works in the JS fiddle. Weird. I guess it's just some kinda formatting discrepancy in Rails 3+.
Anyway, hope this was helpful to anyone who comes across it!

Rails API functional tests with XML body

I've written functional tests for API endpoints built in Rails using shoulda testing framework.
An example looks like the following:
setup do
authenticated_xml_request('xml-file-name')
post :new
end
should respond_with :success
authenticated_xml_request is a test helper method that sets
#request.env['RAW_POST_DATA'] with XML content.
After upgrading the app from rails 2.3.3 to rails 2.3.8, the functional tests are failing because the XML content received is not merged in the params hash.
I'm setting the request with the correct mime type via #request.accept =
"text/xml"
I'm able to inspect the content of the request using request.raw_post but i'd like to keep the current setup working.
Also, running a test from the terminal using cURL or any other library (rest_http) in development mode, the API works perfectly well.
Any tips or help is much appreciated.
Now it's simpler:
post "/api/v1/users.xml", user.to_xml, "CONTENT_TYPE" => 'application/xml'
Note that you have to specify appropriate "CONTENT_TYPE". In other case your request will go as 'application/x-www-form-urlencoded' and xml won't be parsed properly.
I solved the issue by adding a custom patch to rails (test_process.rb file) to convert incoming xml to hash then merge into parameters hash.
on line 439:
parameters ||= {}
parameters.merge!(Hash.from_xml(#request.env['RAW_POST_DATA'])) if #request.env['RAW_POST_DATA'] && #request.env['CONTENT_TYPE']=='application/xml'

Rails request forgery protection settings

please help a newbie in Rails :) I have protect_from_forgery call (which is given by default) with no attributes in my ApplicationController class.
Basically here's the code:
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
protect_from_forgery
helper_method :current_user_session, :current_user
filter_parameter_logging :password, :password_confirmation
What I assume it should do is: it should prevent any POST requests without correct authenticity_token. But when I send post request with jQuery like the one below, it works fine (there's update statement that is executed in the database)!
$.post($(this).attr("href"), { _method: "PUT", data: { test: true } });
I see in console that there's no authenticity_token among sent parameters, but request is still considered valid. Why is that?
UPD
Found config setting in config/environments/development.rb
config.action_controller.consider_all_requests_local = true
Because of the DEV environment and local requests, these jQuery post requests were OK.
There is nothing wrong with your code as long as the request $.post($(this).attr("href"), { _method: "PUT", data: { test: true } }); is executed from within the app itself. If you had another app running elsewhere, say for example on localhost:3001, and you sent a post from there then it won't work. Infact if you are on firefox > 3.0 it has an early implementation of cross site xhr too. For example you can send a POST from any other site (but this works provided protect_from_forgery is turned off!). The reason why auth token is not necessary for xhr is that cross site xhr is disabled. So it is safe to use xhr without providing auth token. If you try from any where else other than your app, i am sure it will raise an exception asking for an auth token. Also you should have a crossdomain.xml defined to prevent access from outside sources.
Try doing this: curl -X -d url_endpoint_of_your_app. See if you get a 200 response code. If you do then there is something fishy.
Silly question, perhaps: Are you sure you're subclassing ApplicationController? What's your route map look like? And what version of Rails (just for clarity)?
Did you verify that the call from jQuery is actually a POST and not a GET? (I know, it seems obvious). Rails will only perform the protection on non-GET requests.
Also, what is the content-type of the request that's going out. Rails will also only perform the protection, according to the docs, if it's an HTML/Javascript request.

Resources