New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Async actionmailer #6839
Async actionmailer #6839
Conversation
Any ActionMailer class can be set to render and delier messages using the new Rails Queue. Some of this work was borrowed (with permission) from Nick Plante's (zapnap) reqsue_mailer gem.
|
||
def method_missing(method_name, *args) | ||
actual_message.send(method_name, *args) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why use method_missing
instead of ruby's DelegateClass (from the stdlib's "delegate") which copies the public methods and exposes the public API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't I have to explicitly state which methods I want to delegate? This QueuedMessage
class is meant to behave in most ways identical to the original message class. this is quicker to delegate those methods to it than listing each one out. Unless I'm mistaken about the behavior of Delegate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes and no, I just noticed that you don't know to which class you're delegating until runtime, in that case DelegateClass won't do, since you need to know the delegated class when you define the object.
That said, even if also uses method_missing
, I would still use Delegator because it handles things like #respond_to_missing?, #public_methods, etc.
Something like
class QueuedMessage < Delegator
def __getobj__
@actual_message ||= @mailer_class.send(:new, @method_name, *@args).message
end
def run
__getobj__.deliver
end
end
Then you don't need #method_missing, #actual_message, or #to_sin that implementation :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoa, that's way better. Thank you, updated and credited.
I don't really like this. I envision people putting things on to the queue, not things happening to be put on the queue because of a configuration setting. I'm not against something similar to this, but I'd rather we have like a /cc @wycats @josevalim |
@tenderlove the global setting is optional. You can call |
Credit goes to *Nicolás Sanguinetti* (foca) for this suggestion
I should also note that this implementation allows you to force message delivery by doing Email.welcome.deliver(true) or because everything else is delegated to the original message class you could call As far as usecase, email async delivery is always the first background job I setup. |
def initialize(mailer_class, method_name, *args) | ||
@mailer_class = mailer_class | ||
@method_name = method_name | ||
*@args = *args |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why *@args
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, it probably doesn't need to be. I was cribbing off of resque_mailer
. I'll update.
module ActionMailer::Async | ||
def self.included(base) | ||
base.extend(ClassMethods) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, couldn't we just expect people to say extend ActionMailer::Async
in their class? Then the ClassMethods
module and this method become unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, that is better. The extend ActionMailer::Async
can be called from the async=
setter on Base
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doesn't this effect how the QueuedMessage class gets inherited into the parent class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand. You mean the visibility of the constant?
I'm starting to warm up to this a lot. The only thing we need to figure out is that the job we push on to the queue can't contain a reference to the queue. Basically, we need to make sure the job pushed on to the queue can be marshal dump / loaded. |
Credit goes to *Aaron Patterson* (tenderlove)
@tenderlove technically you should send the generated email itself to the queue (and not the worker) since the |
@mauricio while marshalling the rendered email itself would be best it might defeat the purpose of pushing the email to a queue. I have had cases where the rendering of the email is expensive. Personally, I would want everything having to do with the email pushed to the background. |
@bcardarella it might be a problem if you use resque as a queuen since all worker parameters have to be simple objects that can correctly be turned into JSON. If one of the parameters can't be made a JSON value it won't work for Resque and the worker would then just fail. Other background emailer solutions like |
I don't know how DelayedJob works so I'm going to do an example based on Resque and ResqueMailer alone. I have this simple project and I call the mailer like this: PostMailer.new_post( Post.last ).deliver Since it uses Resque, the Post object (that is an AR object) will be turned to JSON like this: {"body"=>"sample", "id"=>1, "title"=>"sample"} There is no class information whatsoever here. When I start Resque to start working on this queue this is what happens:
Resque won't be able to figure out what this object was (a Post object) and all it sees is a hash, that doesn't have the A couple of years ago I even created a gem that delivers emails using Resque exactly because of this impossibility of using AR objects as parameters and using AR objects as mailer parameters seems to be a very common practice when using mailers. So I think it would be nice at least to let the users know that they need to understand how their queue implementation works before turning something like this on or just ignore it and send the TMail object encoded as string over the wire. |
@mauricio yes I agree with that, only simple objects should be used as args |
You can use any object you want with resque if you marshal and base64 encode it. You just need to know to do that on the other end. ;-) |
@tenderlove is there anything else you want to see in this PR or do you want to let it marinate until others weigh in? |
end | ||
|
||
# Will push the message onto the Queue to be processed | ||
def deliver(force = false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can get rid of this parameter.
Just remove that one param, and I'm happy. I'll merge after that! Thanks for writing this! ❤️❤️❤️❤️ |
@@ -7,6 +7,9 @@ | |||
require 'mailers/base_mailer' | |||
require 'mailers/proc_mailer' | |||
require 'mailers/asset_mailer' | |||
require 'mailers/async_mailer' | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for extra empty line.
@bcardarella thanks for that :), please don't forget to squash your commits after reviewing @tenderlove's comment :) |
@tenderlove all set :) |
I'll also be sure to do a write up for the guides |
@tenderlove any holds up on this? |
Y U NO SQUASH COMMITS. |
@arunagw I'm that guy now, sorry |
@bcardarella No probs at all :P This happend with me many times ;-) Thanks for Pull Request. Cheers, |
@bcardarella would be great if you can review the Action Mailer guide and explain how this new mailer will work. I think @fxn will appreciate. |
@rafaelfranca yup, I'm going to update the guides :) |
Adds support for ActionMailer to push messages to the Rails Queueing system and deliver async