Skip to content
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

Add deliver_at and deliver_in methods for scheduling mail #32

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Expand Up @@ -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
Expand All @@ -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
Expand Down
54 changes: 39 additions & 15 deletions lib/resque_mailer.rb
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -54,18 +44,14 @@ 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
end

class MessageDecoy
delegate :to_s, :to => :actual_message

def initialize(mailer_class, method_name, *args)
@mailer_class = mailer_class
@method_name = method_name
Expand All @@ -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
Expand Down
83 changes: 81 additions & 2 deletions spec/resque_mailer_spec.rb
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down