Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add deliver_at and deliver_in methods for scheduling mail #32

Closed
wants to merge 2 commits into from

5 participants

@hmarr

If resque-scheduler is installed, two extra methods (deliver_at and deliver_in) are available for scheduling mail in the future.

Thanks!

@tomblomfield

This looks awesome!

:thumbsup:

@hmarr hmarr Move environment_excluded logic to MessageDecoy
This means that extra methods defined on MessageDecoy (e.g. enqueue_at)
will still be available in excluded environments.
a1e10ca
@hmarr

I just added a commit that moves the logic for checking if the Rails environment is excluded to the MessageDecoy class.

Before, if we were in an excluded environment, no MessageDecoy was returned. This meant that in excluded environments (such as in tests) the extra methods added to MessageDecoy weren't present and result in a method undefined error.

Now, a MessageDecoy is always returned. When you call one of the #deliver* methods on the decoy object, the environment is checked.

@zapnap
Owner

That definitely makes me a little more comfortable -- was one of my questions about the approach before and I probably should have commented earlier. I'm still a little unsure if we should be adding methods for compatibility with other resque plugins -- but it does seem that scheduler is in a unique position for this. Hmm... going to think about this. Anyone else want to weigh in with an opinion?

@hmarr

I know what you mean about adding support for other plugins, but it does seem that scheduler is popular enough to make it worthwhile including this. I'd quite like to see this merged, but if you feel it's out of scope, that's cool - I could always just release it as a separate gem (resque-mailer-scheuler?)

@zapnap
Owner

Seems reasonable. I'll probably yank this into the next release once I have a few minutes to test it out.

@nc
nc commented

+1 Can't wait for this to make it in.

@zapnap
Owner

Thanks for your patience on this. I've merged it into master so it should be available now. Please check it out and let me know if things look good. I'll release an updated gem after a few folks confirm that it's working as expected (I'm not using resque-scheduler myself).

@zapnap zapnap closed this
@zapnap
Owner

@hmarr @excid3 @nc @tomblomfield have any of you had a chance to test?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 10, 2012
  1. @hmarr
Commits on May 22, 2012
  1. @hmarr

    Move environment_excluded logic to MessageDecoy

    hmarr authored
    This means that extra methods defined on MessageDecoy (e.g. enqueue_at)
    will still be available in excluded environments.
This page is out of date. Refresh to see the latest.
Showing with 134 additions and 18 deletions.
  1. +14 −1 README.md
  2. +39 −15 lib/resque_mailer.rb
  3. +81 −2 spec/resque_mailer_spec.rb
View
15 README.md
@@ -33,7 +33,7 @@ database-backed objects as parameters in your mailer and instead pass record
identifiers. Then, in your delivery method, you can look up the record from
the id and use it as needed.
-If you want to set a different default queue name for your mailer, you can
+If you want to set a different default queue name for your mailer, you can
change the <tt>default_queue_name</tt> property like so:
# config/initializers/resque_mailer.rb
@@ -45,6 +45,19 @@ name when starting your workers.
QUEUE=application_specific_mailer rake environment resque:work
+### Using with Resque Scheduler
+
+If [resque-scheduler](https://github.com/bvandenbos/resque-scheduler) is
+installed, two extra methods will be available: `deliver_at` and `deliver_in`.
+These will enqueue mail for delivery at a specified time in the future.
+
+ # Delivers on the 25th of December, 2012
+ MyMailer.reminder_email(params).deliver_at(Time.parse('2012-12-25'))
+
+ # Delivers in 7 days
+ MyMailer.reminder_email(params).deliver_in(7.days)
+
+
## Resque::Mailer as a Project Default
If you have a variety of mailers in your application and want all of them to use
View
54 lib/resque_mailer.rb
@@ -20,13 +20,7 @@ def included(base)
self.excluded_environments = [:test]
module ClassMethods
- def current_env
- ::Rails.env
- end
-
def method_missing(method_name, *args)
- return super if environment_excluded?
-
if action_methods.include?(method_name.to_s)
MessageDecoy.new(self, method_name, *args)
else
@@ -38,10 +32,6 @@ def perform(action, *args)
self.send(:new, action, *args).message.deliver
end
- def environment_excluded?
- !ActionMailer::Base.perform_deliveries || excluded_environment?(current_env)
- end
-
def queue
@queue || ::Resque::Mailer.default_queue_name
end
@@ -54,10 +44,6 @@ def resque
::Resque::Mailer.default_queue_target
end
- def excluded_environment?(name)
- ::Resque::Mailer.excluded_environments && ::Resque::Mailer.excluded_environments.include?(name.to_sym)
- end
-
def deliver?
true
end
@@ -65,7 +51,7 @@ def deliver?
class MessageDecoy
delegate :to_s, :to => :actual_message
-
+
def initialize(mailer_class, method_name, *args)
@mailer_class = mailer_class
@method_name = method_name
@@ -76,16 +62,54 @@ def resque
::Resque::Mailer.default_queue_target
end
+ def current_env
+ ::Rails.env
+ end
+
+ def environment_excluded?
+ !ActionMailer::Base.perform_deliveries || excluded_environment?(current_env)
+ end
+
+ def excluded_environment?(name)
+ ::Resque::Mailer.excluded_environments && ::Resque::Mailer.excluded_environments.include?(name.to_sym)
+ end
+
def actual_message
@actual_message ||= @mailer_class.send(:new, @method_name, *@args).message
end
def deliver
+ return deliver! if environment_excluded?
+
if @mailer_class.deliver?
resque.enqueue(@mailer_class, @method_name, *@args)
end
end
+ def deliver_at(time)
+ return deliver! if environment_excluded?
+
+ unless resque.respond_to? :enqueue_at
+ raise "You need to install resque-scheduler to use deliver_at"
+ end
+
+ if @mailer_class.deliver?
+ resque.enqueue_at(time, @mailer_class, @method_name, *@args)
+ end
+ end
+
+ def deliver_in(time)
+ return deliver! if environment_excluded?
+
+ unless resque.respond_to? :enqueue_in
+ raise "You need to install resque-scheduler to use deliver_in"
+ end
+
+ if @mailer_class.deliver?
+ resque.enqueue_in(time, @mailer_class, @method_name, *@args)
+ end
+ end
+
def deliver!
actual_message.deliver!
end
View
83 spec/resque_mailer_spec.rb
@@ -4,6 +4,11 @@ class FakeResque
def self.enqueue(*args); end
end
+class FakeResqueWithScheduler < FakeResque
+ def self.enqueue_in(time, *args); end
+ def self.enqueue_at(time, *args); end
+end
+
class Rails3Mailer < ActionMailer::Base
include Resque::Mailer
default :from => "from@example.org", :subject => "Subject"
@@ -25,7 +30,7 @@ class PriorityMailer < Rails3Mailer
before do
Resque::Mailer.default_queue_target = resque
Resque::Mailer.stub(:success!)
- Rails3Mailer.stub(:current_env => :test)
+ Resque::Mailer::MessageDecoy.any_instance.stub(:current_env).and_return(:test)
end
describe "resque" do
@@ -69,7 +74,7 @@ class PriorityMailer < Rails3Mailer
context "when current env is excluded" do
it 'should not deliver through Resque for excluded environments' do
Resque::Mailer.stub(:excluded_environments => [:custom])
- Rails3Mailer.should_receive(:current_env).and_return(:custom)
+ Resque::Mailer::MessageDecoy.any_instance.should_receive(:current_env).and_return(:custom)
resque.should_not_receive(:enqueue)
@delivery.call
end
@@ -81,6 +86,80 @@ class PriorityMailer < Rails3Mailer
end
end
+ describe '#deliver_at' do
+ before(:all) do
+ @time = Time.now
+ @delivery = lambda {
+ Rails3Mailer.test_mail(Rails3Mailer::MAIL_PARAMS).deliver_at(@time)
+ }
+ end
+
+ context "without resque-scheduler installed" do
+ it "raises an error" do
+ lambda { @delivery.call }.should raise_exception
+ end
+ end
+
+ context "with resque-scheduler installed" do
+ let(:resque) { FakeResqueWithScheduler }
+
+ it 'should not deliver the email synchronously' do
+ lambda { @delivery.call }.should_not change(ActionMailer::Base.deliveries, :size)
+ end
+
+ it 'should place the deliver action on the Resque "mailer" queue' do
+ resque.should_receive(:enqueue_at).with(@time, Rails3Mailer, :test_mail, Rails3Mailer::MAIL_PARAMS)
+ @delivery.call
+ end
+
+ context "when current env is excluded" do
+ it 'should not deliver through Resque for excluded environments' do
+ Resque::Mailer.stub(:excluded_environments => [:custom])
+ Resque::Mailer::MessageDecoy.any_instance.should_receive(:current_env).and_return(:custom)
+ resque.should_not_receive(:enqueue_at)
+ @delivery.call
+ end
+ end
+ end
+ end
+
+ describe '#deliver_in' do
+ before(:all) do
+ @time = 1234567
+ @delivery = lambda {
+ Rails3Mailer.test_mail(Rails3Mailer::MAIL_PARAMS).deliver_in(@time)
+ }
+ end
+
+ context "without resque-scheduler installed" do
+ it "raises an error" do
+ lambda { @delivery.call }.should raise_exception
+ end
+ end
+
+ context "with resque-scheduler installed" do
+ let(:resque) { FakeResqueWithScheduler }
+
+ it 'should not deliver the email synchronously' do
+ lambda { @delivery.call }.should_not change(ActionMailer::Base.deliveries, :size)
+ end
+
+ it 'should place the deliver action on the Resque "mailer" queue' do
+ resque.should_receive(:enqueue_in).with(@time, Rails3Mailer, :test_mail, Rails3Mailer::MAIL_PARAMS)
+ @delivery.call
+ end
+
+ context "when current env is excluded" do
+ it 'should not deliver through Resque for excluded environments' do
+ Resque::Mailer.stub(:excluded_environments => [:custom])
+ Resque::Mailer::MessageDecoy.any_instance.should_receive(:current_env).and_return(:custom)
+ resque.should_not_receive(:enqueue_in)
+ @delivery.call
+ end
+ end
+ end
+ end
+
describe '#deliver!' do
it 'should deliver the email synchronously' do
lambda { Rails3Mailer.test_mail(Rails3Mailer::MAIL_PARAMS).deliver! }.should change(ActionMailer::Base.deliveries, :size).by(1)
Something went wrong with that request. Please try again.