Can't catch ActiveRecord::RecordNotFound with rescue - ruby-on-rails

I'm new to Ruby, please bear with me if this is a stupid question, or if I'm not following the best practice.
I'm finding an object in the DB using find(), and expect it to throw RecordNotFound in case the object of the id does not exist, like this.
begin
event = Event.find(event_id)
rescue ActiveRecord::RecordNotFound => e
Rails.logger.debug "Event does not exist, id: " + event_id
return {
# return "unauthorized" to avoid testing existence of event id
# (some redacted codes)
}
end
But somehow it is not caught (the log in the rescue block is not printed) and the entire program just return internal server error. Here's the stack trace:
Completed 500 Internal Server Error in 22ms (ActiveRecord: 1.0ms)
ActiveRecord::RecordNotFound (Couldn't find Event with 'id'=999):
lib/sync/create_update_event_handler.rb:78:in `handleRequest'
app/controllers/sync_controller.rb:36:in `block in sync'
app/controllers/sync_controller.rb:31:in `each'
app/controllers/sync_controller.rb:31:in `sync'
Rendering /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout
Rendering /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
Rendered /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb (6.4ms)
Rendering /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
Rendered /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (2.3ms)
Rendering /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
Rendered /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.9ms)
Rendered /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (36.6ms)
The only thing I can think of is there are two different ActiveRecord::RecordNotFound, and I'm catching the wrong one, but I don't know if it is the case or how I can verify it.
What did I do wrong?
======================================
Update
The problem is in the rescue block, I was concatenating event_id (an integer) to a string.
The RecordNotFound exception was indeed caught, but when the type error was thrown in the rescue block, the wrong error message was printed.

You won't get an error if you do
event = Event.find_by(id: event_id)
In this case if the record can't be found by ID it will just event == nil be nil.
In this case if the record can't be found by ID it will just event == nil be nil.
The code you pasted works fine for me. If you don't see output in the log, check your environment and log level settings INFO, WARN, DEBUG etc. 500 error indicates some kind of controller action raising the error.
see Set logging levels in Ruby on Rails
To be sure your rescue block is executing try doing something besides log. If you're running a development server you can try :
begin
event = Event.find(event_id)
rescue ActiveRecord::RecordNotFound => e
msg = "Event does not exist, id: #{event_id.to_s}"
Rails.logger.debug msg.
puts msg
binding.pry # if you have gem 'pry' in your development gems.
File.open('test.log', 'w') {|f| f.write msg} #check if this appears in root of your app
return {
# return "unauthorized" to avoid testing existence of event id
# (some redacted codes)
}
end
UPDATE: I changed the string interpolation according to your answer. You can also call .to_s inside interpolation instead of closing quotes and appending.

Turned out the error message is wrong.
The problem is that I was concentating the event_id (an integer) to a string.
But somehow Rails prints out the RecordNotFound exception.
The problem is fixed by replacing
Rails.logger.debug "Event does not exist, id: " + event_id
with
Rails.logger.debug "Event does not exist, id: " + event_id.to_s
Thanks #lacostenycoder for bringing my attention to the error message.

#event = Event.find(params[:id]). you should write instead params[:id] .That's the cause of an error.

Related

How to catch a Rack RangeError in Rails 6

I have a Rails 6 app to which users can upload CSV files. Rails/Rack imposes a limit in the number of params that can be included in a request, and I've set this to a size larger than likely submissions to my app. However, I would like to return a friendly response if a too-large file is uploaded.
It looks like I need to add some custom middleware, to catch and rescue the error, but I can't get the code to work - the basic error is still raised without my rescue block being called.
The error from the server is:
Rack app error handling request { POST /[PATH_TO]/datasets }
#<RangeError: exceeded available parameter key space>
The code in my app/middleware/catch_errors.rb file is basically taken from a previous SO answer, where someone was catching ActionDispatch::ParamsParser::ParseError in JSON, but with my own code in the rescue block (which I realise may not work properly in this context, but that's not the issue right now):
class CatchErrors
def initialize(_app)
#app = _app
end
def call(_env)
begin
#app.call(_env)
rescue RangeError => _error
_error_output = "There were too many fields in the data you submitted: #{_error}"
if env['HTTP_ACCEPT'] =~ /application\/html/
Rails.logger.error("Caught RangeError: #{_error}")
flash[:error_title] = 'Too many fields in your data'
flash[:error_detail1] = _error_output
render 'static_pages/error', status: :bad_request
elsif env['HTTP_ACCEPT'] =~ /application\/json/
return [
:bad_request, { "Content-Type" => "application/json" },
[ { status: :bad_request, error: _error_output }.to_json ]
]
else
raise _error
end
end
end
end
I'm loading it in config.application.rb like this:
require_relative '../app/middleware/catch_errors'
...
config.middleware.use CatchErrors
I'm resetting the size limit for testing in app/initializers/rack.rb like this:
if Rack::Utils.respond_to?("key_space_limit=")
Rack::Utils.key_space_limit = 1
end
Any help gratefully received!
First, execute command to see all middlewares:
bin/rails middleware
config.middleware.use place your middleware at the bottom of the middleware stack. Because of that it can not catch error. Try to place it at the top:
config.middleware.insert_before 0, CatchErrors
Another point to mention, may be you will need to config.middleware.move_after or even config.middleware.delete some middleware. For instance, while tinkering I needed to place:
config.middleware.move_after CatchErrors, Rack::MiniProfiler

Raising another error from a rescue block emits the original error that brought us into the rescue block

The case
I have a Rail 4 application controller with a index method in it like this:
def index
raise "Original Error"
rescue => e
puts "We caught: '#{e.inspect}'"
raise "Another Error"
end
I am catching the Original Error, handling it, massaging it, doing whatever i want it, and then throwing a completely different error out - Another Error.
The expectation
It is, therefore, expected that my Rails app will produce Another Error when i visit the index page of controller and not the Original Error.
The unexpected behavior
But i am actually getting the Original Error instead. Copying the logs of the controller#action here:
Started GET "/homepage" for 127.0.0.1 at 2019-02-06 18:50:19 +0800
We caught: '#<RuntimeError: Original Error>'
Completed 500 Internal Server Error in 205ms (Flexirest: 0.0ms for 0 calls | ActiveRecord: 9.7ms)
RuntimeError - Original Error:
app/controllers/homepage_controller.rb:6:in `index'
Why is it throwing the Original Error here instead of the Another Error?
However, the behavior in the console is still as expected
I copy-pasted the same index method lines into my Rails console and then called the index method directly from within the console. Here is the console output for that.
[49] pry(main)> def index
[49] pry(main)* raise "Original Error"
[49] pry(main)* rescue => e
[49] pry(main)* puts "We caught: '#{e.inspect}'"
[49] pry(main)* raise "Another Error"
[49] pry(main)* end
=> :index
[50] pry(main)> index
We caught: '#<RuntimeError: Original Error>'
RuntimeError: Another Error
from (pry):67:in `rescue in index'
[51] pry(main)>
¯\_(ツ)_/¯
Since Ruby 2.1 the Kernel#raise method has a default parameter for cause that's using the "ambient exception" $!:
raise(string, cause: $!)
The effect is that every exception raised while in a scope of an ambient exception (i.e. in a rescue block) will populate the cause attribute of the raised exception with the current exception, unless the caller specifies a different value for cause during raise.
Your webserver/middleware is probably digging up the cause chain to find the "root cause" error and showing that one, probably using something like e = e.cause until e.cause.nil?.
If you want to suppress this behavior you can specify nil as the cause during raise:
raise "Another Error", cause: nil

Rescue not capturing failure

I'm missing something here, but for some reason my begin then rescue Ruby code isn't capturing this error:
#<ActiveResource::ResourceInvalid: Failed. Response code = 422. Response message = Unprocessable Entity.>
This is my code:
begin
ShopifyAPI::CarrierService.create(with some arguments)
rescue StandardError => e
pp e
end
It doesn't ever capture it. In my rescue section I've tried the above but also:
rescue Exception => e
rescue ActiveResource::Errors => e
All with no luck. Where did I go astray?
Thanks
EDIT:
This is the full error, it not really anymore info, but here goes:
#<ShopifyAPI::CarrierService:0x0000000357a0a0
#attributes=
{"name"=>"XXXX",
"callback_url"=>
"https://XX-XX-XX-XX.c9users.io/receive_rate_request",
"format"=>"json",
"service_discovery"=>"true",
"carrier_service_type"=>"api"},
#errors=
#<ActiveResource::Errors:0x00000003578930
#base=#<ShopifyAPI::CarrierService:0x0000000357a0a0 ...>,
#messages={:base=>["you already have XXX set up for this shop"]}>,
#persisted=false,
#prefix_options={},
#remote_errors=
#<ActiveResource::ResourceInvalid: Failed. Response code = 422. Response message = Unprocessable Entity.>,
#validation_context=nil>
That's it!
Because it is not raising an exception, If you want to raise the exception when the response is false, you may have to use create with bang
begin
ShopifyAPI::CarrierService.create!(with some arguments)
rescue StandardError => e
pp e
end
According to the ActiveResource code (lib/active_resource/base.rb):
# <tt>404</tt> is just one of the HTTP error response codes that Active Resource will handle with its own exception. The
# following HTTP response codes will also result in these exceptions:
#
# * 200..399 - Valid response. No exceptions, other than these redirects:
# * 301, 302, 303, 307 - ActiveResource::Redirection
# * 400 - ActiveResource::BadRequest
# * 401 - ActiveResource::UnauthorizedAccess
# * 403 - ActiveResource::ForbiddenAccess
# * 404 - ActiveResource::ResourceNotFound
...
# * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
So it indicates that 422's are rescued by save on validation, which happens when .create is fired, and are bubbled up as validation errors instead.
Looking at lib/active_resource/validations.rb, you can see the ResourceInvalid exception is gobbled:
# Validate a resource and save (POST) it to the remote web service.
# If any local validations fail - the save (POST) will not be attempted.
def save_with_validation(options={})
perform_validation = options[:validate] != false
# clear the remote validations so they don't interfere with the local
# ones. Otherwise we get an endless loop and can never change the
# fields so as to make the resource valid.
#remote_errors = nil
if perform_validation && valid? || !perform_validation
save_without_validation
true
else
false
end
rescue ResourceInvalid => error
# cache the remote errors because every call to <tt>valid?</tt> clears
# all errors. We must keep a copy to add these back after local
# validations.
#remote_errors = error
load_remote_errors(#remote_errors, true)
false
end
So I wonder if it's logging that an exception happened, but is not actually raising an exception because it turns it in to a return false. It does say "local validations" in the comment, but sets remote_errors, so it's not perfectly clear where this code path is executed.

"undefined method []" when parsing reddit api

I'm trying to request the json pages for multiple subreddits and take the title and link from each page for a college project. here's the code in question:
require 'rufus-scheduler'
require 'json'
require 'httparty'
ENV['TZ'] = 'Europe/Dublin'
scheduler = Rufus::Scheduler::singleton
scheduler.every '12h00m', :first_at => Time.now + 10 do
array_of_subreddits = ["pics", "memes", "funny", "aww", "memes",
"birdswitharms"]
array_of_subreddits.each do |category|
sleep 10 #wait 10 seconds between each request
#response = JSON.parse(HTTParty.get("http://reddit.com/r/#{category}/.json?limit=25").body)
#response['data']['children'].each do |data|
#link = data['data']['url']
#title = data['data']['title']
#category = category
Pic.create([{:title => "#{#title}", :link => "#{#link}", :category => "#{#category}"}])
end
end
end
this works perfectly sometimes, it will run through each one and end as it should. more often than not however it will give me this message after after one or two passes:
NoMethodError (undefined method `[]' for nil:NilClass):
app/controllers/home_controller.rb:17:in `block in index'
app/controllers/home_controller.rb:9:in `each'
app/controllers/home_controller.rb:9:in `index'
Rendered /Users/conorbreen/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/actionpack-4.2.6/lib/action_dispatch/middleware/templates/rescues/_source.erb (4.8ms)
Rendered /Users/conorbreen/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/actionpack-4.2.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (2.2ms)
Rendered /Users/conorbreen/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/actionpack-4.2.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.2ms)
Rendered /Users/conorbreen/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/actionpack-4.2.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (66.2ms)
Rendered /Users/conorbreen/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/web-console-2.3.0/lib/web_console/templates/_markup.html.erb (0.4ms)
Rendered /Users/conorbreen/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/web-console-2.3.0/lib/web_console/templates/_inner_console_markup.html.erb within layouts/inlined_string (0.3ms)
Rendered /Users/conorbreen/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/web-console-2.3.0/lib/web_console/templates/_prompt_box_markup.html.erb within layouts/inlined_string (0.3ms)
Rendered /Users/conorbreen/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/web-console-2.3.0/lib/web_console/templates/style.css.erb within layouts/inlined_string (0.5ms)
Rendered /Users/conorbreen/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/web-console-2.3.0/lib/web_console/templates/console.js.erb within layouts/javascript (51.6ms)
Rendered /Users/conorbreen/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/web-console-2.3.0/lib/web_console/templates/main.js.erb within layouts/javascript (0.3ms)
Rendered /Users/conorbreen/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/web-console-2.3.0/lib/web_console/templates/error_page.js.erb within layouts/javascript (0.5ms)
Rendered /Users/conorbreen/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/web-console-2.3.0/lib/web_console/templates/index.html.erb (124.8ms)
Creating client classes is a much better way to work with httparty:
class RedditClient
include HTTParty
format :json
base_uri "http://reddit.com/r/"
def self.get_category(category, *opts)
opts.reverse_merge(limit: 25)
get("/#{category}.json", opts)
end
end
This way HTTParty handles JSON parsing for us and does not attempt to an convert an empty response. Its also much easier to test separately.
However you should still check if the response was successful before trying to use it:
#response = RedditClient.get_category(category)
if #response.success?
attrs = #response['data']['children'].map do |child|
{
category: category,
link: child['data']['url'],
title: child['data']['title']
}
end
Pic.create!(attrs)
else
# log it or raise some sort of error
end
Note that you where passing an array containing a single hash to .create. You can instead pass an array of hashes and it will insert the records in a single SQL insert statement.
When you get errors like this, you should always dump the actual response so you can inspect it. The fact you got a error for a nil with code doing stuff like ['data']['children'] means Id guess you got a JSON response, but one that is missing one of the first items (e.g. ['data'] returned nil).
Dont just assume every request is successful, many things can make a HTTP fail. Its possible that you get a valid JSON response back, just not the one you are expecting, for example an error message which would have told you the problem.
Also even with a 10 second delay, you may be hitting a rate limit (never tested Reddit personally), but read the rules
Many default User-Agents (like "Python/urllib" or "Java") are drastically limited to encourage unique and descriptive user-agent strings.
This kind of errors are most common in ruby or rails. Can be handled in multiple ways. As #Stefan suggested you can use any of the bellow.
Most simply like this
response = HTTParty.get('http://reddit.com/r/#{category}/.json?limit=25')
if response.success?
response_body = response.body
# continue
end
or
response = HTTParty.get('http://reddit.com/r/#{category}/.json?limit=25')
case response.code
when 200
puts "Good!"
# Continue your parsing
when 404
puts "NOT FOUND!"
when 500...600
puts "ERROR #{response.code}"
end
or
begin
HTTParty.get('http://reddit.com/r/#{category}/.json?limit=25')
rescue HTTParty::Error
# HTTParty errors like Not found
rescue StandardError
# StandardError like Timeout
else
# continue
end

I get "undefined method" calling by Rails model, but works fine calling by Rails console

I receive undefined method scan for User:Class when scan method
(line 13: last = resp.headers['Link'].scan(/\d+/).last)
is called by Rails User model (inside class method self.create_user(auth)) :
class User
include Mongoid::Document
field :email
field :nickname
embeds_many :posts
def self.create_user(auth)
conn = FaradayStack.build 'https://api.example.com'
resp = conn.get "/users/#{auth.nickname}/list"
last = resp.headers['Link'].scan(/\d+/).last # <== error occurs here
n = 0
create! do |user|
user.email = auth["user_info"]["email"]
user.nickname = auth['user_info']['nickname']
while n <= last.to_i do
resp = conn.get "/users/#{auth.nickname}/list?page=#{n=n+1}"
resp.body.each do |repo|
user.posts.build( html_url: "#{repo['html_url']}",
description: "#{repo['description']}",
created_at: "#{repo['created_at']}",
pushed_at: "#{repo['pushed_at']}",
avatar_url: "#{repo['owner']['avatar_url']}" )
end
end
end
end
end
If I call the same code by Rails console it works fine :
ruby-1.9.2-p136 :158 > last = resp.headers['Link'].scan(/\d+/).last
=> "16"
ruby-1.9.2-p136 :159 > last = resp.headers['Link'].scan(/\d+/).last.to_i
=> 16
ruby-1.9.2-p136 :160 >
Something related to istance and class method concept I guess,
but I can't fix it.
UPDATE:
On suggestion of jimworm, I put rails logger instead of "offending" line :
Rails.logger.info "\r\n" + "#{Time.now} " + "resp.headers['Link']: #{resp.headers['Link']}" + "\r\n"
what I get is :
2011-09-30 16:40:03 +0200 resp.headers['Link']:
Redirected to http://localhost:3001/
Completed 302 Found in 3151ms
MONGODB blumb_dev['users'].find({:_id=>BSON::ObjectId('4e85d4c41d41c8103f000006')})
while in Rails console it is :
ruby-1.9.2-p136 :181 > resp.headers['Link']
=> "<https://api.example.com/users/lgs/list?page=18>; rel=\"next\", <https://api.example.com/users/lgs/list?page=51>; rel=\"last\""
ruby-1.9.2-p136 :182 >
Any idea ?
Our hero was perplexed... it worked in the console, surely it'll work from the controller? The village will be doomed if the bug wasn't caught. He decided to take a snapshot of the bug, catch it in the act.
Rails.logger.info...
The trap was set, then he waited. The screen flashed. A hit! The bug has left its traces in our trap. Our hero looked and...
to his surprise, the trap was empty!
2011-09-30 16:40:03 +0200 resp.headers['Link']: #nothing here#
"Now how could that be" he wondered, "a bug that left no traces? What kind of bug leaves no traces?"
Then it came to him. "Eureka, a nil!" he smacked the table triumphantly, "a nil appears as nothing in the log! That's why I couldn't scan it!"
The identity of the bug was revealed, and it wasn't long before our hero traced it back to its home at api.example.com and mashed it once and for all. Turns out it was the messages that the village was sending to api.example.com that lured the bug out of hiding. Now with the bug gone, the village was saved and everyone lived in peace, happily ever after.

Resources