Prompt error status RAILS - API - ruby-on-rails

I have basically this:
class Api::NameController < ApplicationController
skip_before_filter :verify_authenticity_token
def index
end
def create
begin
cou = params[:data].count
rescue
render status: 422, json: {
message: "Error Loading data- Check your data",
}.to_json
end
input = params[:data]
# Operations with the input data
render status: 200, json: {
message: "Succesful data calculation",
data_output: input
}.to_json
end
end
The data is: {"data":[1,34,5]}
I would like to render the status error 422, when the data has a bad sintax (example: {"dat":[1,34,5]} or {"data":1,34,5]} etc)
With the example above doe not work. When sending via cURL {"data":1,34,5]} I got:
Started POST "/api/energy" for 127.0.0.1 at 2015-05-29 08:35:50 +0200
Error occurred while parsing request parameters.
Contents:
{"data":1,5,67]}
ActionDispatch::ParamsParser::ParseError (795: unexpected token at '{"data":1,5,67]}'):
When sending {"dat":[1,34,5]} I got
Processing by Api::EnergyController#create as JSON
Parameters: {"dat"=>[1, 5, 67], "energy"=>{"dat"=>[1, 5, 67]}}
Completed 500 Internal Server Error in 1ms (Views: 0.2ms | ActiveRecord: 0.0ms)
AbstractController::DoubleRenderError (Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".):
Could anyone help me putting this error status in the correct form?

Related

Rails: send a 401 error without rendering a page

I'm trying to send a 401 authorized from a Rails controller for webhooks, but I can't seem to the right way to do it since I'm not rendering a page.
render plain: "Unauthorized", status: :unauthorized
throws this error:
Read error: #<NoMethodError: undefined method `bytesize' for [:error, "Unauthorized"]:Array>
I don't understand why because
render plain: "Unauthorized", status: 400
works fine.
head(:unauthorized)
Returns a response that has no content (merely a status code and headers).
See ActionController::Head.
Are you, by any chance, using this snippet from rails_warden or equivalent middleware?
If so, the correct stanza is
manager.failure_app = Proc.new { |_env|
['401', {'Content-Type' => 'application/json'}, [{ error: 'Unauthorized' }.to_json]]
}

Rails rendering JSON causing helper method to be called twice

I'm getting strange behavior when I render JSON from my Rails app. A helper method is run twice when render :json is called. Here's the controller and method:
class UsersController < ApplicationController
def current
render json: { :errors => "Incorrect credentials" }, :status => :bad_request
end
end
I have the following helper module, with a puts statement for debugging:
module SessionsHelper
def current_user
puts "current_user"
if encrypted_id = request.headers[:user_id]
user = User.find_by(id: EncryptionService.decrypt(encrypted_id))
if user && user.authenticated?(request.headers[:remember_token])
#curent_user = user
end
end
end
end
The SessionsHelper is included in the Application Controller
class ApplicationController < ActionController::API
include SessionsHelper
end
When sent the request, I get the following:
Started GET "/user/current" for ::1 at 2021-02-12 22:06:47 -0800
Processing by UsersController#current as */*
Parameters: {"user"=>{}}
current_user
[active_model_serializers] Rendered ActiveModel::Serializer::Null with Hash (0.06ms)
Completed 400 Bad Request in 1ms (Views: 0.7ms | ActiveRecord: 6.6ms | Allocations: 383)
current_user was printed, even though the function was never called. When I comment out the render json: statement, leaving:
class UsersController < ApplicationController
def current
end
end
I get the following:
Started GET "/user/current" for ::1 at 2021-02-12 22:09:43 -0800
Processing by UsersController#current as */*
Parameters: {"user"=>{}}
Completed 204 No Content in 0ms (ActiveRecord: 4.2ms | Allocations: 78)
current_user is not printed. Why would render json: call current_user? In my actual application, this is causing the database to be hit twice (although Rails wisely caches the result).
UPDATE: I'm onto something here. I ran puts caller[0] to see who was calling the function. The result:
/Users/izaguirrejoe/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/active_model_serializers-0.10.12/lib/action_controller/serialization.rb:40:in 'serialization_scope'
def serialization_scope
return unless _serialization_scope && respond_to?(_serialization_scope, true)
send(_serialization_scope)
end
Any ideas?
I see that you are using active_model_serializers, if you check out their docs it says here, the default serialisation scope is :current_user. It also emphasizes that
IMPORTANT: Since the scope is set at render, you may want to customize it so that current_user isn't called on every request. This was also a problem in 0.9.
This causes that the current_user method is always invoked. If you want to avoid this behaviour, you can set the serialization_scope in the controller for example:
class UsersController < ApplicationController
serialization_scope nil # also you can pass a custom method here
def current
render json: { :errors => "Incorrect credentials" }, :status => :bad_request
end
end
or in some cases only by calling self.class.serialization_scope nil.

Rails controller rescue exception in `lib` directory

I'm using a wrapper around a Wikipedia OmniAuth gem in my Devise initializer that is periodically throwing JWT::DecodeErrors. I'm trying to handle these errors with a custom error message, rather than throwing a 500. (Stick with me here.)
In my Devise initializer, I set up the strategy as such:
config.omniauth :mediawiki,
Figaro.env.wikipedia_token,
Figaro.env.wikipedia_secret,
strategy_class: CustomStrategy,
client_options: {
site: "https://#{Figaro.env.wiki_language}.wikipedia.org"
}
My CustomStrategy inherits from the default MediaWiki strategy and adds some data to the request object to check for it later in the ErrorsController:
# lib/custom_strategy.rb
class LoginError < StandardError; end
class CustomStrategy < OmniAuth::Strategies::Mediawiki
def parse_info(jwt_data)
begin
super
rescue JWT::DecodeError
request.env['JWT_ERROR'] = true
request.env['JWT_DATA'] = jwt_data
raise ::LoginError.new("\nA JWT::DecodeError occurred. Here is the web token data:\n#{jwt_data.body.inspect}")
end
end
end
So here I try to catch the JWT::DecodeError by raising a LoginError that I'm hoping to catch in my controller:
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :handle_login_error
...
protected
def handle_login_error
redirect_to errors_login_error_path
end
end
The problem is that rescue_from only handles exceptions thrown in the controller.
Since this exception is thrown in my strategy, I don't have the opportunity to rescue it, and Rails just treats it as a 500:
[2016-01-26 12:10:02.183 FATAL] StandardError (
A JWT::DecodeError occurred. Here is the web token data:
"{\"error\":\"mwoauthdatastore-access-token-not-found\",\"message\":\"No approved grant was found for that authorization token.\"}"):
lib/custom_strategy.rb:9:in `rescue in parse_info'
lib/custom_strategy.rb:6:in `parse_info'
[2016-01-26 12:10:02.251 INFO ] Processing by ErrorsController#internal_server_error as HTML
[2016-01-26 12:10:02.251 INFO ] Parameters: {"oauth_verifier"=>"XXXXXXXXXXXXXXXX", "oauth_token"=>"XXXXXXXXXXXXXXX"}
[2016-01-26 12:10:02.971 INFO ] Rendered errors/internal_server_error.html.erb within layouts/application (3.8ms)
[2016-01-26 12:10:03.204 INFO ] Rendered shared/_head.html.haml (10.1ms)
[2016-01-26 12:10:03.215 INFO ] Rendered shared/_nav.html.haml (9.7ms)
[2016-01-26 12:10:03.218 INFO ] Rendered shared/_foot.html.haml (2.3ms)
[2016-01-26 12:10:03.219 INFO ] Completed 500 Internal Server Error in 967ms (Views: 265.8ms | ActiveRecord: 0.0ms)
So here, Rails treats it as a 500, and I'm using custom error pages, not the Rails defaults.
For reference, here's my ErrorsController:
class ErrorsController < ApplicationController
respond_to :html, :json
def file_not_found
render status: 404
end
def unprocessable
render status: 422
end
def internal_server_error
if request.env['JWT_ERROR']
return login_error
end
render status: 500
end
def incorrect_passcode
render status: 401
end
def login_error
#return render status: 599
end
end
What's the best way to handle this exception that happens in the jwt-ruby gem, which is a dependency of the oauth-mediawiki gem that I'm wrapping with a custom strategy that hopes to catch the JWT::DecodeError and handle it within my app? If this specific error arises, I want to render a special view, not just the 500 view.
Edit 2: Further Articulation of the Problem
The problem is that I can't figure out a way to rescue the exception I've rescued and re-raised (JWT::DecodeError) in a way that allows me to render a custom view. As you can see from the log above, it just renders ErrorsContoller#internal_server_error.

New Relic 404, 422 and 500 Exceptions With Rails Dynamic Exception Handling

I'm running a Rails 4.0.0.rc application using New Relic for availability / exception monitoring. I modified application.rb with this snippet to enable dynamic exception pages:
config.exceptions_app = self.routes
However, I no longer see 404, 422 or 500 exceptions in New Relic. Any idea how I get them back?
Edit:
Note: this is what the controller handling the status looks like:
class ErrorsController < ApplicationController
# GET /404
def missing
render status: 404
end
# GET /422
def unprocessable
render status: 422
end
# GET /500
def exception
render status: 500
end
end
It sounds like you want to call NewRelic::Agent.notice_error manually.
You can reconstruct the request object from the Rack env and build an exception however you would like.
Something like this:
request = Rack::Request(env)
options = {
:uri => request.url,
:referrer => request.referrer,
:request_params => request.params
}
NewRelic::Agent.notice_error(your_custom_exception, options)
Note that the request parameters will be transmitted as is so be careful to filter anything sensitive.
Sources:
I work for New Relic as Ruby Agent Engineer
Documentation for NoticedError: http://rubydoc.info/gems/newrelic_rpm/frames
You will have to set the html status code to the correct value in your errors controller. If you for example have something like this:
class ErrorsController < ApplicationController
# 404
def not_found
render "not_found", status: 404
end
end
Otherwise will rails render the error page with a 200 status code, and new relic will not pick it up as an error.

Backbone Mongoid Rails set up will not let me update a record

Can someone help me understand how to make Ryan Bate's screencast on Backbone.js work with MongoDB as my database while using the Mongoid gem.
This is what I am seeing.
When I create a new entry via the console, similar to how Ryan did it in the video with entry.create, Rails adds that entry just fine. Below is my Ruby log and my JavaScript headers log from Chrome Inspector.
Ruby Log
Started POST "/api/entries" for 127.0.0.1 at 2012-02-12 17:31:24 -0600
Processing by EntriesController#create as JSON
Parameters: {"name"=>"Heather", "entry"=>{"name"=>"Heather", "action"=>"create", "controller"=>"entries"}}
MONGODB w_market_development['system.namespaces'].find({})
MONGODB w_market_development['entries'].insert([{"_id"=>BSON::ObjectId('4f384bcc504b9348be000003'), "name"=>"Heather"}])
Completed 201 Created in 11ms (Views: 2.4ms)
Headers Log
Request URL:http://0.0.0.0:3000/api/entries
Request Method:POST
Status Code:201 Created
Request Headers (14)
Request Payload
{"name":"Heather"}
As you can see it posted fine. Now let me show you an update via the entry.save() example Ryan showed us.
Ruby Log
Started POST "/api/entries" for 127.0.0.1 at 2012-02-12 17:34:25 -0600
Processing by EntriesController#create as JSON
Parameters: {"_id"=>"4f38152c504b9345dc000005", "name"=>"Bloip", "winner"=>true, "entry"=>{"_id"=>"4f38152c504b9345dc000005", "name"=>"Bloip", "winner"=>true, "action"=>"create", "controller"=>"entries"}}
MONGODB w_market_development['system.namespaces'].find({})
MONGODB w_market_development['entries'].insert([{"_id"=>BSON::ObjectId('4f38152c504b9345dc000005'), "name"=>"Bloip", "winner"=>true}])
Completed 201 Created in 12ms (Views: 2.7ms)
Headers Log
Request URL:http://0.0.0.0:3000/api/entries
Request Method:POST
Status Code:201 Created
Request Headers (14)
Request Payload
{"_id":"4f38152c504b9345dc000005","name":"Bloip","winner":true}
As you can see when I complete the entry.save() on a current entry, which should be an update, the JSON is showing a POST instead of a PUT, which Mongoid is doing nothing with and the DB shows no changes. After Googling I found the following articles but nothing really helped.
https://github.com/codebrew/backbone-rails/issues/8
http://lostechies.com/derickbailey/2011/06/17/making-mongoid-play-nice-with-backbone-js/
When I was going through the RailsCast as described above. I was using the entries controller that Ryan put together. After much searching, copying, pasting, and retrying I found that I need a completely new Controller set up. Below is what I originally had.
class EntriesController < ApplicationController
respond_to :json
def index
respond_with Entry.all
end
def show
respond_with Entry.find(params[:id])
end
def create
respond_with Entry.create(params[:entry])
end
def update
respond_with Entry.update(params[:id], params[:entry])
end
def destroy
respond_with Entry.destroy(params[:id])
end
end
This is the Controller code the fixed the issue for me.
class EntriesController < ApplicationController
def index
render :json => Entry.all
end
def show
render :json => Entry.find(params[:id])
end
def create
entry = Entry.create! params
render :json => entry
end
def update
entry = Entry.find(params[:id])
entry.update_attributes!(params[:entry])
render :json => entry
end
def destroy
render :json => Entry.destroy(params[:id])
end
end
Thanks All!
Travis

Resources