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.
Related
Let's imagine I have a class
class Test < ActiveRecord::Base
include AuthenticatorHelper
def test
authenticate_or_fail!
puts "If I fail, this should be unreachable"
end
end
and
module AuthenticationHelper
def authenticate_or_fail!
#user = User.find(params[:token])
unless #user
render :json => {code: 401, :err => 'Unauthorized'} and return
end
end
end
What I want to do is either authenticate or reply with a json msg. However, it will obviously ignore my return statement due to nesting and it will always print my message
If I fail, this should be unreachable
Regarding the question
You could extract the call into a before_filter/before_action (based on the rails version).
class Test < ActiveRecord::Base
include AuthenticatorHelper
before_action :authenticate_or_fail!
def test
puts "If I fail, this should be unreachable"
end
end
Please see the documentation for further details.
Because your helper method renders in case of a failure, rails will prevent the test method to be called. You will not need the and return part then, which would only have returned from the method anyway and as such was a NoOp.
Apart from the question but also noteworthy:
I don't want to point out errors for the sake of it. I just want to prevent the OP from running into a series of bugs later on.
User.find(params[:token])
Will raise an exception if no record is found. Because of that, the unless #user part will not be evaluated in case of an invalid token. You could use
User.find_by(id: params[:token])
instead.
Your class which looks like it acts as a controller is named Test and inherits from ActiveRecord::Base. The first is unusual as TestsController would be more along the lines of rails and the seconds looks plain wrong. A controller has to inherit from ApplicationController (which itself inherits from ActionController::Base)
I have a simple mailer
class ApplyMailer < ActionMailer::Base
def inform_teacher
end
def inform_division
end
def inform_everyone
inform_teacher.deliver
inform_division.deliver
end
end
Calling inform_teacher and inform_division everything works well. But when I try to call inform_everyone just one blank email arrives.
Is it possible to combine multiple email method though one method?
Found solution to this:
class ApplyMailer < ActionMailer::Base
def inform_teacher
end
def inform_division
end
def self.inform_everyone
ApplyMailer.inform_teacher.deliver
ApplyMailer.inform_division.deliver
end
end
I'm using observer pattern in rails. The "delivered_email" method in TestObserver will be called after notification email been sent out. How could I pass the instance variable in "notification" to the "delivered_email"? I could add it either in the header or subject. But it could pose security issue since user who received email could also see the variable. Is there any better way to solve it?
class GeneralMailer < ActionMailer::Base
def notification(data)
#emails = data[:emails]
subject = "#{#sender.to_s}"
mail(:to => #emails, :subject => subject)
end
end
class TestObserver
def self.delivered_email(message)
begin
# do something here
puts #emails
rescue => ex
# do something here
end
end
ActionMailer::Base.register_observer(TestObserver)
If you do want to send extended information to a mail observer – as I myself did – it can be done by monkey-patching your mailer a bit to use a custom class extending from Mail::Message.
Note that this requires setting an internal, undocumented attribute. In a future Rails version, it might not continue to work.
class ExtendedMessage < Mail::Message
attr_accessor :extra_args
end
class ApplicationMailer
before_action :patch_message
def patch_message
# Patch the internal attribute #_message to use our overridden class
# that you can store extra attributes inside.
# NOTE: If a future Rails version decides to change the internals, this could break.
#_message = ExtendedMessage.new
end
end
class MailObserver
def self.delivered_email(mail)
do_something_with mail.extra_args
end
end
In my Rails application I want to temporarily stop sending email for specific users (e.g. when I get bounces due to quota) until the user confirms he is able to receive email again.
I have a common superclass for all mailer classes. There I always call a method setup_email before sending a mail.
Where is the best place to call #user.mail_suspended??
Here is some simplified sample app, I use Rails 2.3:
# Common super class for all Mailers
class ApplicationMailer < ActionMailer::Base
protected
def setup_mail(user)
#recipients = user.email
#from = ...
end
end
# Specific Mailer for User model
class UserMailer < ApplicationMailer
def message(user, message)
setup_mail(user)
#subject = "You got new message"
#body[:message] = message
end
end
# Use the UserMailer to deliver some message
def MessagesController < ApplicationController
def create
#message = Message.new(params[:message])
#message.save
UserMailer.deliver_message(#message.user, #message)
redirect_to ...
end
end
I solved this by setting the ActionMailer::Base.perform_deliveries to false:
def setup_mail(user)
email = user.default_email
if email.paused?
ActionMailer::Base.perform_deliveries = false
logger.info "INFO: suspended mail for #{user.login} to #{email.email})"
else
ActionMailer::Base.perform_deliveries = true
end
# other stuff here
end
I wouldn't set perform_deliveries universally, just per message, e.g.
after_filter :do_not_send_if_old_email
def do_not_send_if_old_email
message.perform_deliveries = false if email.paused?
true
end
I tried many ways, but no one could help me except this one.
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
Make a class inherited with standardError to raise exception.
Check the condition, if false then raise exception.
Handle that exception with the empty lambda.
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).
I am using a mixin to add some functionality to my model (Person). In the mixin I need some initializations to be done so I am trying to use the "after_initialize" callback macro to invoke an initialization method. The model (Person) is only a base class for some other models.
The problem I am having is that it gets never called. I tried to debug it but the breakpoint never got hit. Also logging gives me no output.
I couldn`t find any help (as this construct should be working in Rails 3 according to the Api docs and some posts here).
/lib/mymodule.rb
module MyModule
after_initialize :generate_ids
def generate_ids
logger.info "invoked" #never hit
end
end
/models/person.rb
require "mymodule"
class Person < ActiveRecord::Base
include MyModule
end
/models/customer.rb
class Customer < Person
# nothing so far
end
*/controllers/customers_controller.rb (action => new)*
# GET /customers/new
# GET /customers/new.json
def new
#person = Customer.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #customer }
end
end
Please be indulgent to me as I am a "newbie" to RoR.
Thank you very much !
Best regards,
Thomas
UPDATE
After restarting the local app server it gives me the following exception:
ActionController::RoutingError (undefined method `after_initialize' for SequentialRecord:Module):
I assume this callback can`t be used in mixins ?
Try something like that (not tested).
module MyModule
def self.included(base)
base.after_initialize :generate_ids
end
def generate_ids
logger.info "invoked" #never hit
end
end