I'm running ActiveStorage direct upload on a form and when the form gets submitted it uploads to S3. I have a check in a service object that checks to see if the file is too large even after getting past some javascript that checks the size. If it is, the file is deleted off S3 and I remove the ActiveStorage::Blob from my database (for technical constraints). When I run the following code:
def blob
#blob ||= ActiveStorage::Blob.find_signed(signed_id) unless signed_id.blank?
rescue StandardError => error
# Handle error
end
if a user refreshes the page and the form is resubmitted with the signed_int ActiveRecord::RecordNotFound is still raised to the controller and fails on the memoization line.
What could cause a rescue on StandardError to not be caught?
Edit: Here's the stack trace
ActiveRecord::RecordNotFound - Couldn't find ActiveStorage::Blob with 'id'=80:
app/services/contact_us_form_manager.rb:76:in `blob'
app/services/contact_us_form_manager.rb:66:in `check_file_size'
app/services/contact_us_form_manager.rb:37:in `create'
app/controllers/admin/contacts_controller.rb:18:in `create'
app/controllers/application_controller.rb:280:in `set_true_user'
app/controllers/application_controller.rb:262:in `set_current_user'
app/controllers/application_controller.rb:255:in `set_current_client'
app/controllers/application_controller.rb:113:in `assign_time_zone'
Related
If a template causes an ActiveRecord::RecordNotFound exception to be raised, the original exception is swallowed up by ActionView and turned into an ActionView::Template::Error.
It seems that in Rails 5 there was an original_exception method but that appears to be gone in Rails 6.
I'd like to be able to inspect the cause of the ActionView::Template::Error so I can show better contextual errors.
Is there any way to do so?
If you catch the ActionView::Template::Error, there should be a #cause method defined on it which returns the original exception. Example:
begin
begin
raise ArgumentError
rescue
raise RuntimeError
end
rescue => e
puts "#{ e } caused by #{ e.cause }"
end
prints
RuntimeError caused by ArgumentError
When debugging failing integration tests, I keep running into the same problem where the exceptions raised in my code are suppressed and not shown in the testing output.
For example, for the following controller and test:
class RegistrationController::ApplicationController
def create
# some code that raises an exception
end
end
class RegistrationFlowTest < ActionDispatch::IntegrationTest
test 'user registers successfully' do
post sign_up_path, params: { username: 'arnold', password: '123' }
assert_response :success
end
end
The output is something like
Minitest::Assertion: Expected response to be a <2XX: success>, but was a <500: Internal Server Error>
Is there a way to see the exact raised exception? Instead of just the difference of HTTP response code?
Thanks!
Simon
My recommended approach to this issue would be to actually parse the response provided by Rails (at least by default in test and development environments) which includes the stacktrace for the error and handle that in the case that your test fails. This has the advantage that it won't output the stacktrace when errors are raised that don't result in failing tests (e.g. scenarios where you are intentionally testing how failures are handled).
This little module that I've crafted will allow you to call assert_response_with_errors to assert the response to a call but output the exception message and stack trace in a readable format when the response is not what you expected.
module ActionDispatch
module Assertions
module CustomResponseAssertions
# Use this method when you want to assert a response body but also print the exception
# and full stack trace to the test console.
# Useful when you are getting errors in integration tests but don't know what they are.
#
# Example:
# user_session.post create_gene_path, params: {...}
# user_session.assert_response_with_errors :created
def assert_response_with_errors(type, message = nil)
assert_response(type, message)
rescue Minitest::Assertion => e
message = e.message
message += "\nException message: #{#response.parsed_body[:exception]}"
stack_trace = #response.parsed_body[:traces][:'Application Trace'].map { |line| line[:trace] }.join "\n"
message += "\nException stack trace start"
message += "\n#{stack_trace}"
message += "\nException stack trace end"
raise Minitest::Assertion, message
end
end
end
end
To use this, you need to include it into ActionDispatch::Assertions before Rails has loaded its stack in your test_helper.rb. So just prepend the include into your test_helper.rb, like this:
ActionDispatch::Assertions.include ActionDispatch::Assertions::CustomResponseAssertions
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
...
This will happen because Rails controllers by default handle exceptions and raise the 500 status, making the exceptions invisible to the test suite (which is very helpful if errors are raised in unit tests of a model). Options for disabling this in your test suite, or alternative workarounds, are discussed here.
The key lines of code from that link, which should be added to test/integration/integration_test_helper.rb:
ActionController::Base.class_eval do
def perform_action
perform_action_without_rescue
end
end
Dispatcher.class_eval do
def self.failsafe_response(output, status, exception = nil)
raise exception
end
end
EDIT: I've noticed that that link is quite old now. I'm really familiar with Rack, so whilst the first block looks ok to me, I'm not sure if the second will still be current. You might need to have a look at the relevant current Rails guide if it needs bringing up to date.
I am attempting to add puffing-billy to a Rails app by following the instructions here.
Running the test suite raises:
in `require': cannot load such file -- capybara/webkit (LoadError)
I am using Poltergeist not Webkit, and so don't need to load this file.
The error originates from the following line in puffing-billy
# /lib/billy/browsers/capybara.rb
DRIVERS = {
poltergeist: 'capybara/poltergeist',
webkit: 'capybara/webkit',
selenium: 'selenium/webdriver'
}
def self.register_drivers
DRIVERS.each do |name, driver|
require driver rescue next # this line should be rescued
send("register_#{name}_driver")
end
end
What could prevent require driver from being rescued and cause this error, and what is a methodical approach to debug this?
In general it's a bad idea to use an inline rescuse in Ruby (having rescue be at the end of the line). Here is a nice article w/ more details: https://www.new-bamboo.co.uk/blog/2015/09/23/dont-inline-rescue-in-ruby/
Inline rescue and a "unfiltered" rescue (without specifying the type of error you want caught) both catch any exceptions that inherit from StandardError LoadError is not a descendant of StandardError so it's is not caught by your inline rescue.
Here is a chart of error hierarchy in Ruby:
http://blog.honeybadger.io/understanding-the-ruby-exception-hierarchy/
So here is your code modified so it will work and also not catch unintentional errors:
DRIVERS.each do |name, driver|
begin
require driver
send("register_#{name}_driver")
rescue LoadError
# This skips to the next driver
# It would be nice to add logging here to notify that the driver was skipped
end
end
In one of my Rails application controllers, I do something like this:
def create
#user = User.create(user_params)
# send welcome email
UserMailer.welcome(#user).deliver_later
end
Now, I've intentionally disabled my Redis server so that I can replicate what would happen in the case that a connection couldn't be made from my app.
Unfortunately, this entire create request fails with a 500 if the deliver_later is unable to connect to Redis.
What I'd like is that the request still succeeds, but the mailer fails silently.
How can I accomplish this?
Additional information:
In config/initializers/action_mailer.rb:
rescue_from(Redis::CannotConnectError) do |exception|
Rails.logger.error "Original record not found: #{#serialized_arguments.join(', ')}"
end
This never gets called though on exception. I tried rescue_from(StandardError) and (Exception), but that was never called either.
I'm using sidekiq as my job queue adapter:
config.active_job.queue_adapter = :sidekiq
The 500 error I get is:
Redis::CannotConnectError (Error connecting to Redis on localhost:6379 (Errno::ECONNREFUSED)):
My UserMailer is a subclass of ApplicationMailer which is a subclass of ActionMailer::Base.
In order to prevent calls to deliver_later from crashing when Redis is down, we added the following monkey-patch:
# If +raise_delivery_errors+ is false, errors occurring while attempting to
# enqueue the email for delivery will be ignored (for instance, if Redis is
# unreachable). In these cases, the email will never be delivered.
module MessageDeliveryWithSilentQueueingErrors
def deliver_later
super
rescue Redis::CannotConnectError => e
raise if raise_delivery_errors
# Log details of the failed email here
end
def deliver_later!
super
rescue Redis::CannotConnectError => e
raise if raise_delivery_errors
# Log details of the failed email here
end
end
ActionMailer::MessageDelivery.send(:prepend, MessageDeliveryWithSilentQueueingErrors)
I'm trying to figure out the best way to catch a specific error thrown AND the error's message in Ruby on Rails. My use case is that I encounter a timeout error every now and then which is thrown with a general error and I want to treat the timeout error differently than other errors within the same general error. I'm not sure what other type of errors could be thrown in the general error but I assume more of them exist. I have some sample code below of how I'm currently handling it, but I was thinking there might be a better way which I haven't found yet?
tries = 0
begin
tries += 1
<code>
rescue Foo::Bar => e
case e.to_s
when 'More specific timeout error message'
retry unless tries >= 5
else
# Let me see other error messages
log.info("Error: #{e.to_s}")
end
end
You can use multi rescue, to handle different errors.
begin
# DO SOMETHING
rescue Net::Timeout => e # change it to the error your want to catch, check the log.
# handle it
rescue SyntaxError => e # just an example
# handle it
rescue => e # any error that not catch by above rescue go here.
# handle it
end
Read more:
http://phrogz.net/programmingruby/tut_exceptions.html
You can try Rollbar, it help report error on production.
Take a look at retriable gem. It seems like a good fit for what you're proposing. Usually you'd rescue from an specific error type, but retriable also gives you the choice to rescue based on the error message.
begin
Retriable.retriable on: { Foo::Bar => /More specific timeout error message/ }, tries: 3 do
# will retry if an error of type Foo::Bar is raised
# and its message matches /More specific timeout error message/
# code here...
end
rescue => e # rescue for everything else
puts e.message # same as e.to_s
end