Apipie interferes with proper error codes - ruby-on-rails

I can't seem to find this bit in Apipie's docs.
I have this RESTful api ready to go, and I thought I'd document it with Apipie. All fine and dandy, and param validation is nice, but I can't seem to make it throw the right HTTP code in response when validation fails. For example, I have this POST /users method which, obviously, creates a user in my database. Nice, right? Well, it responds with 422 Unprocessable entity if something's wrong, and that's fine, but then if, say, I throw at it parameters that do not pass apipie's validation, apipies chokes and throws a good old fashioned error, something along the lines of
Failure/Error: post '/users.json', :user => d
Apipie::ParamInvalid:
Invalid parameter 'email' value "": Must match regular expression /[a-zA-Z0-9\-.]+\#[a-zA-Z0-9\-.]+\.[a-z]{2,}/.
# ./spec/requests/usersapi_spec.rb:66:in `block (3 levels) in <top (required)>'
assuming I have a regexp validation on the email parameter. Instead I would very much have liked it to respond nicely (possibly in json, or with empty body) and set the respose's code to 422.
Any ideas?
EDIT: Yes, I know I can disable apipie's validation and do it otherwise...

You can use a rescue_from and then respond with the error that makes sense for you.
class ApplicationController < ActionController::Base
# ParamError is superclass of ParamMissing, ParamInvalid
rescue_from Apipie::ParamError do |e|
render text: e.message, status: :unprocessable_entity
end
# ...
end

Hope you already found the answer - but incase if anyone wants to disable the validations from Apipie - here it is.
Apipie.configure do |config|
config.validate = false
end

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

Ruby o Rails Rspec - How to validate arguments when creating custom matchers

I would like to create a validation like the matcher have_http_status to the content_type.
I created the following matcher:
require 'rspec/expectations'
RSpec::Matchers.define :have_content_type do |expected|
match do |actual|
actual.content_type == expected
end
description do
"respond with content_type #{expected}"
end
end
However, I would like to check if the actual value is a response object. If not, I would like to give a message like the have_http_status, which would be:
Failure/Error: it { expect(legal_person).to have_http_status(200) }
expected a response object, but an instance of LegalPerson was received
# ./spec/controllers/legal_people_controller_spec.rb:105:in `block (5 levels) in <top (required)>'
So when I pass an object different from a request-response object, I would expect an error saying that a response object was expected.
It works without it but would be better if it shows an informative message saying what went wrong exactly.
Thanks in advance for the help.
You're trying to have 2 matchers under 1 name. I believe this is a bad practice, and there should be two matchers - one for content type, another one - for checking request/response type. So your tests should look like:
expect(response).to be_a(ActionDispatch::Response)
expect(response).to have_content_type('application/json')
Also, I don't see any problem of having just content type check. If the object passed does not have .content_type method, the matcher will throw corresponding error: Undefined method 'content_type' and you should be fine figuring out that you've passed wrong object.
But, if you are still sure you need to check two things in one matcher, check this:
RSpec::Matchers.define :have_content_type do |expected|
match do |actual|
request_or_response?(actual) && actual.content_type.to_s == expected
end
description do |actual|
if request_or_response?(actual)
"respond with content_type #{expected}"
else
"a response object, but an instance of #{actual.class} was received"
end
end
private
def request_or_response?(actual)
[Rack::Request, ActionDispatch::Response].any? do |klass|
actual.is_a?(klass)
end
end
end

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.

Custom Devise 401 unauthorized response

I'm working on a JSON-based API for my Rails 3.1 app. I'd like to provide a custom failure response instead of the default, which is:
{"error":"You need to sign in or sign up before continuing."}
My API controller includes a before_filter call to authenticate_user!, which is what is rendering this JSON response.
While searching, I came across this StackOverflow question, which references this Devise wiki entry. Unfortunately, the wiki entry isn't verbose enough for me to understand what it's telling me. Specifically, I have no clue where I'm supposed to put that code such that Devise/Warden knows to render what I want returned.
From the comments on the other SA question, it sounds like I don't need to call custom_failure! since I'm using a version of Devise above 1.2 (1.4.2 to be specific). However, the wiki entry doesn't explain where the render call should go such that authenticate_user! knows to use that instead of its own render call.
Where does this render call go?
Edit: I'm not just trying to change the message itself (a la the devise en.yml config); I'm trying to change the actual format of the response. Specifically, I want to return this:
render :text => "You must be logged in to do that.", :status => :unauthorized
For reference in case anyone else stumbles upon this question when looking for how to customize the json error response when a failed login attempt is made using Devise, the key is to use your own custom FailureApp implementation. (You can also use this approach to override some redirect behavior.)
class CustomFailureApp < Devise::FailureApp
def respond
if request.format == :json
json_error_response
else
super
end
end
def json_error_response
self.status = 401
self.content_type = "application/json"
self.response_body = [ { message: i18n_message } ].to_json
end
end
and in your devise.rb, look for the config.warden section:
config.warden do |manager|
manager.failure_app = CustomFailureApp
end
Some related info:
At first I thought I would have to override Devise::SessionsController, possibly using the recall option passed to warden.authenticate!, but as mentioned here, "recall is not invoked for API requests, only for navigational ones. If you want to customise the http status code, you will have better luck doing so at the failure app level."
Also https://github.com/plataformatec/devise/wiki/How-To%3a-Redirect-to-a-specific-page-when-the-user-can-not-be-authenticated shows something very similar for redirection.
If you're simply wanting to change the text displayed with the error message, I believe you can just edit the locale file (/config/locales/devise.en.yml).
The RailsCast on this topic might be helpful too, if you want more specific details. You can find it at http://railscasts.com/episodes/210-customizing-devise

Ruby equivalent to PHPs set_error_handler

I have just barely gotten into Ruby / ROR but need to quickly write a class for handling errors and doing something with them. I've been able to find the important examples/tutorials for the rest of what I need but I'm having trouble finding what the best alternative to PHP's "set_error_handler" is.
My goals are:
I'd like to write a class that will capture any ruby-level errors automatically.
I'd like for the class to also be called by the user when there are custom errors/exceptions to report.
I'd like this work for any ruby app, but my main focus is for ruby-on-rails applications as well. Thanks for your advice.
I think the closest equivalent in Rails is rescue_from - it allows you to specify code will catch any given exception (except some template errors - though there are ways round that). If you want, you could then hand it off to some other class. So I guess what you'd do in your case would be:
in app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
rescue_from Exception do |e|
MyExceptionHandler.handle_exception(e)
end
end
in lib/my_exception_handler.rb:
class MyExceptionHandler
def self.handle_exception exception
# your code goes here
end
end
If that helps, let me know and I'll dig out the link to how you catch template errors.
begin
#require all_kinds_of_things
"abc".size(1,2)
123.reverse
# rest of brilliant app
rescue Exception => e #Custom, catch-all exeption handler
puts "Doh...#{e}"
print "Do you want the backtrace? (Y) :"
puts e.backtrace if gets.chomp == "Y"
end
Define ApplicationController#rescue_in_public(exception) and put your custom handling code there.
This augments Rails' default exception handling at the top level - right before the HTTP response is generated. As your Rails apps grow in complexity and use external resources, there will be more exceptions that you'll want to handle much closer to where the exceptions are thrown, but this can get you started.
This method will only work on HTTP requests and will not catch exceptions in any custom rake tasks you create or code executed via rails runner.
Here's an example from one of my applications:
class ApplicationController < ActionController::Base
...
protected
def rescue_action_in_public (exception)
case exception
when ActionController::InvalidAuthenticityToken
if request.xhr?
render :update do |page|
page.redirect_to '/sessions/new/'
end
else
redirect_to '/sessions/new/'
end
when ActionController::NotImplemented
RAILS_DEFAULT_LOGGER.info("ActionController::NotImplemented\n#{request.inspect}")
render :nothing => true, :status => '500 Error'
else
super
end
end
end

Resources