I want to use rescue_from method on a model concern but neither exception error is rescued to the method i specified:
require "active_support/concern"
module CustomErrorHandler
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
included do
rescue_from StandardError, with: :capture_error
end
private
def capture_error(e)
puts "Error catched!"
end
end
class FooClass
include CustomErrorHandler
def some_action
raise StandardError, 'Error'
end
end
err = FooClass.new
err.some_action
Traceback (most recent call last):
2: from (irb):28
1: from (irb):23:in `some_action'
StandardError (Error) # Didn't catch the error!
Anyone can help me on how can i solve this? Thanks!
This idea is fundamentially broken.
rescue_from is basically just syntactic sugar for:
class ThingsController < ApplicationController
around_action :wrap_in_a_rescue
def show
raise SomeKindOfException
end
private
def wrap_in_a_rescue
begin
yield
rescue SomeKindOfException
do_something_else
end
end
def do_something_else
render plain: 'Oh Noes!'
end
end
It only works because the controller declares a callback.
Its use makes sense in a controller as it rescues exceptions that occur in its callbacks as well as in the method itself and can be used with inheritiance to customize the error responses. It use does not make very much sense outside of that context.
While you could maybe fix this code by including ActiveSupport::Callbacks and declaring the requisite callbacks it is most likely a very overcomplicated solution to the original problem which can most likely be handled with a simple rescue or composition.
I am trying to create my own StandardError exception, but I cannot seem to fire rescue_from with the raise. The error is raised but never rescued. I built a simple application to try it, as follows:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
class ApplicationController::LocationInvalid < StandardError
end
rescue_from ApplicationController::LocationInvalid, with: :reset_it
raise ApplicationController::LocationInvalid
private
def reset_it(exception)
session.clear
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
puts 'Reset it processed'
end
end
When I wrap the raise in begin/rescue/end, it prints e as ApplicationController::LocationInvalid:
begin
raise ApplicationController::LocationInvalid
rescue => e
puts e
end
Output:
Started GET "/" for 127.0.0.1 at 2015-08-21 15:19:32 -0400
ApplicationController::LocationInvalid
I've tried various forms of specifying the ApplicationController class and not with no change in the results...
According to source code of Rails https://github.com/rails/rails/blob/f62fb985b6a7b90872148f0786219c9cc94c9356/actionpack/lib/action_controller/metal/rescue.rb#L32
rescue_with works only during process_action call but you are raising exception during class evaluation. Probably the easiest way how to test it is to create simple controller with some action and try to raise exception during that action and rescue_with should work.
So in my application_controller I have a helper method that raises an exception in a certain case, and a rescue_from for that exception in the same file. The structure looks something like the following:
application_controller.rb:
class ApplicationController < ActionController::Base
helper_method :my_method
def my_method
begin
# some code
rescue xyz
# some code
unless something
raise MyException.new()
end
rescue
raise MyException.new()
end
end
rescue_from 'MyException' do |e|
# some code
end
class MyException < StandardError; end
end
For some reason, when MyException is raised in my helper method, it is not caught by the rescue_from. I'm honestly not sure how to proceed...I don't think putting the rescue_from before the method would affect anything, since I have more than one handler defined like this and they work fine.
In my mailer controller, under certain conditions (missing data) we abort sending the email.
How do I exit the controller method without still rendering a view in that case?
return if #some_email_data.nil?
Doesn't do the trick since the view is still rendered (throwing an error every place I try to use #some_email_data unless I add a lot of nil checks)
And even if I do the nil checks, it complains there's no 'sender' (because I supposed did a 'return' before getting to the line where I set the sender and subject.
Neither does render ... return
Basically, RETURN DOESN'T RETURN inside a mailer method!
A much simpler solution than the accepted answer would be something like:
class SomeMailer < ActionMailer::Base
def some_method
if #some_email_data.nil?
self.message.perform_deliveries = false
else
mail(...)
end
end
end
If you're using Rails 3.2.9 (or later things even better) - there you can finally conditionally call mail(). Here's the related GitHub thread. Now the code can be reworked like this:
class SomeMailer < ActionMailer::Base
def some_method
unless #some_email_data.nil?
mail(...)
end
end
end
I just encountered same thing here.
My solution was following:
module BulletproofMailer
class BlackholeMailMessage < Mail::Message
def self.deliver
false
end
end
class AbortDeliveryError < StandardError
end
class Base < ActionMailer::Base
def abort_delivery
raise AbortDeliveryError
end
def process(*args)
begin
super *args
rescue AbortDeliveryError
self.message = BulletproofMailer::BlackholeMailMessage
end
end
end
end
Using these wrapper mailer would look like this:
class EventMailer < BulletproofMailer::Base
include Resque::Mailer
def event_created(event_id)
begin
#event = CalendarEvent.find(event_id)
rescue ActiveRecord::RecordNotFound
abort_delivery
end
end
end
It is also posted in my blog.
I've found this method that seems the least-invasive, as it works across all mailer methods without requiring you to remember to catch an error. In our case, we just want a setting to completely disable mailers for certain environments. Tested in Rails 6, although I'm sure it'll work just fine in Rails 5 as well, maybe lower.
class ApplicationMailer < ActionMailer::Base
class AbortDeliveryError < StandardError; end
before_action :ensure_notifications_enabled
rescue_from AbortDeliveryError, with: -> {}
def ensure_notifications_enabled
raise AbortDeliveryError.new unless <your_condition>
end
...
end
The empty lambda causes Rails 6 to just return an ActionMailer::Base::NullMail instance, which doesn't get delivered (same as if your mailer method didn't call mail, or returned prematurely).
Setting self.message.perform_deliveries = false did not work for me.
I used a similar approach as some of the other answers - using error handling to control the flow and prevent the mail from being sent.
The example below is aborting mail from being sent in non-Production ENVs to non-whitelisted emails, but the helper method logic can be whatever you need for your scenario.
class BaseMailer < ActionMailer::Base
class AbortedMailer < StandardError; end
def mail(**args)
whitelist_mail_delivery(args[:to])
super(args)
rescue AbortedMailer
Rails.logger.info "Mail aborted! We do not send emails to external email accounts outside of Production ENV"
end
private
def whitelist_mail_delivery(to_email)
return if Rails.env.production?
raise AbortedMailer.new unless internal_email?(to_email)
end
def internal_email?(to_email)
to_email.include?('#widgetbusiness.com')
end
end
I just clear the #to field and return, so deliver aborts when it doesn't have anything there. (Or just return before setting #to).
I haven't spent much time with rails 3 but you could try using
redirect_to some_other_route
alternatively, if you're really just checking for missing data you could do a js validation of the form fields and only submit if it passes.
For a specific role (group of users) I added the :readonly to every find on the active record
def self.find(*args)
if User.current_user.has_role? 'i_can_only_read'
with_scope({:find => {:readonly => true}}) do
result = super *args
end
end
end
Of course it raises now ActiveRecord::ReadOnlyRecord Exceptions in Controller passed on to the user; not very nice.
Can I catch this type of error in one place? Like in production.rb or in the application.rb? Or can I configure a specific error page for this error?
Yes, simply override rescue_action_in_public like this:
class ApplicationController < ActionController::Base
...
def rescue_action_in_public(exception)
case exception
when ActiveRecord::ReadOnlyRecord
# DO SOME LOGIC HERE
end
end
end
end
This will execute your action when in "production", but leave you with an informative stack trace when you are in "development".
Rails has a number of other rescue_action_* methods that might be more suitable to your problem...take a look at http://api.rubyonrails.org/classes/ActionController/Rescue.html