Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
  • 6 commits
  • 25 files changed
  • 0 commit comments
  • 1 contributor
View
5 Gemfile
@@ -0,0 +1,5 @@
+source "http://rubygems.org"
+
+gem "rails", ">= 3"
+gem "mysql2"
+gem "rspec", ">= 2.5"
View
83 Gemfile.lock
@@ -0,0 +1,83 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ abstract (1.0.0)
+ actionmailer (3.0.5)
+ actionpack (= 3.0.5)
+ mail (~> 2.2.15)
+ actionpack (3.0.5)
+ activemodel (= 3.0.5)
+ activesupport (= 3.0.5)
+ builder (~> 2.1.2)
+ erubis (~> 2.6.6)
+ i18n (~> 0.4)
+ rack (~> 1.2.1)
+ rack-mount (~> 0.6.13)
+ rack-test (~> 0.5.7)
+ tzinfo (~> 0.3.23)
+ activemodel (3.0.5)
+ activesupport (= 3.0.5)
+ builder (~> 2.1.2)
+ i18n (~> 0.4)
+ activerecord (3.0.5)
+ activemodel (= 3.0.5)
+ activesupport (= 3.0.5)
+ arel (~> 2.0.2)
+ tzinfo (~> 0.3.23)
+ activeresource (3.0.5)
+ activemodel (= 3.0.5)
+ activesupport (= 3.0.5)
+ activesupport (3.0.5)
+ arel (2.0.9)
+ builder (2.1.2)
+ diff-lcs (1.1.2)
+ erubis (2.6.6)
+ abstract (>= 1.0.0)
+ i18n (0.5.0)
+ mail (2.2.15)
+ activesupport (>= 2.3.6)
+ i18n (>= 0.4.0)
+ mime-types (~> 1.16)
+ treetop (~> 1.4.8)
+ mime-types (1.16)
+ mysql2 (0.2.6)
+ polyglot (0.3.1)
+ rack (1.2.1)
+ rack-mount (0.6.13)
+ rack (>= 1.0.0)
+ rack-test (0.5.7)
+ rack (>= 1.0)
+ rails (3.0.5)
+ actionmailer (= 3.0.5)
+ actionpack (= 3.0.5)
+ activerecord (= 3.0.5)
+ activeresource (= 3.0.5)
+ activesupport (= 3.0.5)
+ bundler (~> 1.0)
+ railties (= 3.0.5)
+ railties (3.0.5)
+ actionpack (= 3.0.5)
+ activesupport (= 3.0.5)
+ rake (>= 0.8.7)
+ thor (~> 0.14.4)
+ rake (0.8.7)
+ rspec (2.5.0)
+ rspec-core (~> 2.5.0)
+ rspec-expectations (~> 2.5.0)
+ rspec-mocks (~> 2.5.0)
+ rspec-core (2.5.1)
+ rspec-expectations (2.5.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.5.0)
+ thor (0.14.6)
+ treetop (1.4.9)
+ polyglot (>= 0.3.1)
+ tzinfo (0.3.24)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ mysql2
+ rails (>= 3)
+ rspec (>= 2.5)
View
20 MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2011 Jonathan Viney
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
73 README
@@ -1,73 +0,0 @@
-ActionMailer::Queue
-===================
-
-Store emails into ActiveRecord
-
-Author
-======
-
-Andrew Beam
-KRAXNET s.r.o.
-
-Example
-=======
-
-class Notification < ActionMailer::Queue
-
- def test
- end
-
-end
-
-New mail
---------
-mail = Notification.deliver_test
-mail.class # => TMail::Mail
-mail.queue_id # => 1
-
-Notification.queue # => ActionMailer::Queue::Store => ActiveRecord::Base
-
-Deliver mail
-------------
-mail = Notification.queue.find(1)
-mail.class # => ActionMailer::Queue::Store
-mail.deliver! # => false || TMail::Mail
-
-if mail.deliver! == false
- mail.last_error # String (Error.to_s)
- mail.last_attempt_at # Datetime
- mail.tries # => 1
-else
- mail.sent # => true
- mail.message_id # String
- mail.sent_at # Datetime
-end
-
-Process queue
--------------
-Notification.queue.process! # { :limit => 100 } use scope for_send and with_processing_rules
-
-
-Settings
---------
-ActionMailer::Queue.delivery_method = :action_mailer_queue
-
-ActionMailer::Queue.limit_for_processing = 100
-# - limit for Notification.queue.process!
-
-ActionMailer::Queue.max_tries_in_process = 5
-# - trying send mail only X times (with delay between attempt)
-
-ActionMailer::Queue.delay_between_attempt_in_process = 240 # [minutes]
-# - delay between attempt after previous attempt failed
-
-ActionMailer::Queue.destroy_message_after_deliver = false
-# - delete message from database after mail deliver
-
-Queue scopes
-------------
-Notification.queue.for_send # emails for send
-Notification.queue.already_sent # emails already sent
-Notification.queue.with_processing_rules # apply processing rules
-Notification.queue.with_error # emails with more tries
-Notification.queue.without_error # emails with 0 tries
View
23 README.rdoc
@@ -0,0 +1,23 @@
+= ActionMailerQueue
+
+This plugin was originally based on action-mailer-queue by Andrew Beam.
+It has been rewritten for Rails 3.
+
+== Usage
+
+ class MyMailer < ActionMailer::Base
+ # Tell the mailer to deliver with active record
+ self.delivery_method = :active_record
+
+ def test
+ mail(:to => "test@test.com", :from => "test@test.com")
+ end
+ end
+
+ # Creates a records in the emails table (see spec/schema.rb for structure).
+ MyMailer.test.deliver
+
+== Contributors
+
+* Jonathan Viney
+* Andrew Beam - original author
View
13 Rakefile
@@ -0,0 +1,13 @@
+require 'rspec/core/rake_task'
+
+desc 'Default: run specs'
+task :default => :spec
+RSpec::Core::RakeTask.new do |t|
+ t.pattern = "spec/**/*_spec.rb"
+end
+
+RSpec::Core::RakeTask.new('rcov') do |t|
+ t.pattern = "spec/**/*_spec.rb"
+ t.rcov = true
+ t.rcov_opts = ['--exclude', 'spec']
+end
View
11 generators/action_mailer_queue/action_mailer_queue_generator.rb
@@ -1,11 +0,0 @@
-class ActionMailerQueueGenerator < Rails::Generator::NamedBase
-
- def manifest
- record do |m|
- m.template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
- m.directory File.join('app/views', class_path, "#{file_name}")
- m.migration_template 'create_migration.rb', 'db/migrate', { :migration_file_name => "create_#{file_name}" }
- end
- end
-
-end
View
25 generators/action_mailer_queue/templates/create_migration.rb
@@ -1,25 +0,0 @@
-class Create<%= class_name %> < ActiveRecord::Migration
- def self.up
- create_table "<%= table_name %>" do |t|
- t.column :id, :integer
- t.column :from, :string, :limit => 255, :default => nil
- t.column :to, :string, :limit => 255, :default => nil
- t.column :subject, :string, :limit => 255, :default => nil
- t.column :content, :longblob
- t.column :message_id, :string, :limit => 64
- t.column :in_progress, :boolean, :default => false, :null => false
- t.column :sent, :boolean, :default => false, :null => false
- t.column :attempts, :integer, :default => 0, :null => false
- t.column :last_error, :string, :limit => 128, :default => nil
- t.column :priority, :integer, :default => 10, :null => false
- t.column :last_attempt_at, :datetime, :default => nil
- t.column :sent_at, :datetime, :default => nil
- t.column :created_at, :datetime, :default => nil
- t.column :updated_at, :datetime, :default => nil
- end
- end
-
- def self.down
- drop_table "<%= table_name %>"
- end
-end
View
3  generators/action_mailer_queue/templates/model.rb
@@ -1,3 +0,0 @@
-class <%= class_name %> < ActionMailer::Queue
-
-end
View
5 init.rb
@@ -1,4 +1 @@
-require 't_mail/mail'
-require 'action_mailer/queue'
-require 'action_mailer/queue_mailer'
-require 'action_mailer/queue_store'
+require "action_mailer_queue"
View
35 lib/action_mailer/queue.rb
@@ -1,35 +0,0 @@
-module ActionMailer
- class Queue < ActionMailer::Base
-
- @@delivery_method = :action_mailer_queue
- cattr_accessor :delivery_method
-
- @@limit_for_processing = 100
- cattr_accessor :limit_for_processing
-
- @@max_attempts_in_process = 5
- cattr_accessor :max_attempts_in_process
-
- @@delay_between_attempts_in_process = 240
- cattr_accessor :delay_between_attempts_in_process
-
- @@destroy_message_after_deliver = false
- cattr_accessor :destroy_message_after_deliver
-
- def self.queue
- return new.queue
- end
-
- def queue
- return Store.create_by_table_name(self.class.to_s.tableize)
- end
-
- def perform_delivery_action_mailer_queue(mail)
- store = self.queue.new(:tmail => mail)
- store.save
- mail.queue_id = store.id
- return true
- end
-
- end
-end
View
5 lib/action_mailer/queue_mailer.rb
@@ -1,5 +0,0 @@
-module ActionMailer
- class Queue < ActionMailer::Base
- class Mailer < ActionMailer::Base; end
- end
-end
View
76 lib/action_mailer/queue_store.rb
@@ -1,76 +0,0 @@
-module ActionMailer
- class Queue < ActionMailer::Base
- class Store < ActiveRecord::Base
-
- named_scope :for_send, :conditions => [ "sent = ?", false]
- named_scope :already_sent, :conditions => [ "sent = ?", true]
-
- named_scope :with_processing_rules, lambda {{
- :conditions => [ "attempts < ? AND (last_attempt_at < ? OR last_attempt_at IS NULL)", ActionMailer::Queue.max_attempts_in_process, Time.now - ActionMailer::Queue.delay_between_attempts_in_process.minutes],
- :limit => ActionMailer::Queue.limit_for_processing,
- :order => "priority asc, last_attempt_at asc"
- }}
- named_scope :with_error, :conditions => ["attempts > ?", 0]
- named_scope :without_error, :conditions => ["attempts = ?", 0]
-
- class MailAlreadySent < StandardError; end
- class MailSendingInProgress < StandardError; end
-
- def self.create_by_table_name(table_name)
- self.set_table_name table_name
- return self
- end
-
- def self.process!(options = {})
- self.for_send.with_processing_rules(:all, options.merge(:select => :id)).each { |q| self.find(q.id).deliver! }
- end
-
- def tmail=(mail)
- self.to = mail.to.uniq.join(",") unless mail.to.blank?
- self.from = mail.from.uniq.join(",") unless mail.from.blank?
- self.subject = mail.subject unless mail.subject.blank?
- self.content = mail.encoded
- end
-
- def to_tmail
- tmail = TMail::Mail.parse(self.content)
- tmail.to = self.to.split(",") unless self.to.blank?
- tmail.from = self.from.split(",") unless self.from.blank?
- tmail.subject = self.subject unless self.subject.blank?
- return tmail
- end
-
- def resend!
- self.sent = false
- self.save
- self.deliver!
- end
-
- def deliver!
- raise MailAlreadySent if self.sent == true
- raise MailSendingInProgress if self.in_progress == true
- self.update_attribute(:in_progress, true)
- mail = Mailer.deliver(self.to_tmail)
- if ActionMailer::Queue.destroy_message_after_deliver
- self.destroy
- else
- self.message_id = mail.message_id
- self.sent = true
- self.in_progress = false
- self.sent_at = Time.now
- self.save
- end
- return mail
- rescue => err
- raise err if [ActionMailer::Queue::Store::MailAlreadySent, ActionMailer::Queue::Store::MailSendingInProgress].include?(err.class)
- self.in_progress = false
- self.attempts += 1
- self.last_error = err.to_s
- self.last_attempt_at = Time.now
- self.save
- return false
- end
-
- end
- end
-end
View
22 lib/action_mailer_queue.rb
@@ -0,0 +1,22 @@
+require "mail"
+require "mail/active_record"
+
+require "action_mailer"
+
+require "action_mailer_queue/mailer"
+require "action_mailer_queue/store"
+
+ActionMailer::Base.class_eval do
+ add_delivery_method :active_record, Mail::ActiveRecord,
+ :table_name => "emails",
+ :limit_for_processing => 100,
+ :max_attempts_in_process => 5,
+ :delay_between_attempts_in_process => 240
+
+ def self.queue
+ settings = active_record_settings
+ @queue ||= Class.new(ActionMailerQueue::Store) do
+ self.table_name = settings[:table_name]
+ end
+ end
+end
View
4 lib/action_mailer_queue/mailer.rb
@@ -0,0 +1,4 @@
+module ActionMailerQueue
+ class Mailer < ActionMailer::Base
+ end
+end
View
80 lib/action_mailer_queue/store.rb
@@ -0,0 +1,80 @@
+module ActionMailerQueue
+ class Store < ActiveRecord::Base
+ self.abstract_class = true
+
+ scope :for_send, lambda { where("sent = ?", false) }
+ scope :already_sent, lambda { where("sent = ?", true) }
+
+ scope :with_processing_rules, lambda {
+ where(
+ "attempts < ? AND (last_attempt_at < ? OR last_attempt_at IS NULL)",
+ active_record_settings[:max_attempts_in_process],
+ Time.now - active_record_settings[:delay_between_attempts_in_process].minutes).
+ limit(active_record_settings[:limit_for_processing]).
+ order("priority asc, last_attempt_at asc")
+ }
+
+ scope :with_error, lambda { where("attempts > ?", 0) }
+ scope :without_error, lambda { where("attempts = ?", 0) }
+
+ class MailAlreadySent < StandardError; end
+
+ class << self
+ def process!(options = {})
+ records_for_processing(options).each { |q| q.deliver! }
+ end
+
+ def records_for_processing(options = {})
+ for_send.with_processing_rules.all(options)
+ end
+
+ def active_record_settings
+ ActionMailer::Base.active_record_settings
+ end
+ end
+
+ def mail=(mail)
+ self.to = Array.wrap(mail.to).first
+ self.from = Array.wrap(mail.from).first
+ self.subject = mail.subject
+
+ # JV: Hack! Forces the bcc header to be encoded
+ bcc_header_field = mail.header.fields.find { |f| f.name == "Bcc" }
+ def bcc_header_field.encoded
+ do_encode("Bcc")
+ end
+
+ self.content = mail.encoded
+ end
+
+ def to_mail
+ Mail.new(content)
+ end
+
+ def deliver!
+ raise MailAlreadySent if sent?
+
+ mail = to_mail
+ Mailer.wrap_delivery_behavior(mail)
+ mail.deliver
+
+ update_attributes!(
+ :message_id => mail.message_id,
+ :sent => true,
+ :sent_at => Time.now
+ )
+
+ mail
+ rescue => err
+ raise err if err.class == MailAlreadySent
+
+ update_attributes(
+ :attempts => attempts + 1,
+ :last_error => err.to_s,
+ :last_attempt_at => Time.now
+ )
+
+ false
+ end
+ end
+end
View
9 lib/mail/active_record.rb
@@ -0,0 +1,9 @@
+class Mail::ActiveRecord
+ def initialize(values)
+ end
+
+ def deliver!(mail)
+ mail.delivery_handler.queue.create!(:mail => mail)
+ self
+ end
+end
View
5 lib/t_mail/mail.rb
@@ -1,5 +0,0 @@
-module TMail
- class Mail
- attr_accessor :queue_id
- end
-end
View
38 spec/action_mailer_queue/action_mailer_spec.rb
@@ -0,0 +1,38 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+describe "ActionMailer::Base extensions " do
+ def subject
+ ActionMailer::Base
+ end
+
+ it "should have the active_record delivery method" do
+ subject.delivery_methods.should include(:active_record)
+ subject.delivery_methods[:active_record].should == Mail::ActiveRecord
+ subject.active_record_settings.should == {
+ :table_name => "emails",
+ :limit_for_processing => 100,
+ :max_attempts_in_process => 5,
+ :delay_between_attempts_in_process => 240
+ }
+ end
+
+ it "should return a queue" do
+ queue = subject.queue
+ queue.superclass.should == ActionMailerQueue::Store
+ queue.table_name.should == "emails"
+ end
+
+ context "a subclass with a different table name" do
+ before do
+ @klass = Class.new(ActionMailer::Base)
+ @klass.active_record_settings[:table_name] = "my_emails"
+ end
+
+ it "should return the queue" do
+ queue = @klass.queue
+
+ queue.superclass.should == ActionMailerQueue::Store
+ queue.table_name.should == "my_emails"
+ end
+ end
+end
View
15 spec/action_mailer_queue/delivery_spec.rb
@@ -0,0 +1,15 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+describe "active_record delivery method " do
+ def subject
+ MyMailer
+ end
+
+ context "a message" do
+ before { @message = subject.test }
+
+ it "should create a database row when delivered" do
+ lambda { @message.deliver }.should change { MyMailer.queue.count }.by(1)
+ end
+ end
+end
View
70 spec/action_mailer_queue/store_spec.rb
@@ -0,0 +1,70 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+describe "action_mailer_queue/store" do
+ def subject
+ MyMailer
+ end
+
+ context "a new email" do
+ before { @email = subject.queue.new }
+
+ context "that is assigned a mail object" do
+ before { @email.mail = subject.test }
+
+ it "should set all the values" do
+ @email.to.should == "to@to.com"
+ @email.from.should == "from@from.com"
+ @email.subject.should == "subject"
+ @email.content.should_not be_blank
+ end
+
+ it "should return a new mail object from the stored string" do
+ mail = @email.to_mail
+
+ mail.to.should == ["to@to.com"]
+ mail.from.should == ["from@from.com"]
+ mail.cc.should == ["cc@cc.com"]
+ mail.bcc.should == ["bcc@bcc.com"]
+ mail.reply_to.should == ["replyto@replyto.com"]
+ mail.subject.should == "subject"
+ end
+ end
+ end
+
+ context "with emails in the queue" do
+ before do
+ 3.times { subject.test.deliver }
+ end
+
+ it "should deliver 3 messages" do
+ lambda { subject.queue.process! }.should change { ActionMailer::Base.deliveries.size }.by(3)
+ end
+
+ it "should raise an error when mail was already sent" do
+ records = subject.queue.all
+ records.first.sent = true
+
+ subject.queue.should_receive(:records_for_processing).and_return(records)
+
+ lambda { subject.queue.process! }.should raise_error(subject.queue::MailAlreadySent)
+
+ error_record = records.first.reload
+ error_record.sent.should == false
+ error_record.attempts.should == 0
+ error_record.last_error.should be_nil
+ error_record.last_attempt_at.should be_nil
+ end
+
+ context "and the queue is processed" do
+ before { subject.queue.process! }
+
+ it "should update the record values" do
+ subject.queue.all.each do |record|
+ record.sent.should == true
+ record.message_id.should == record.to_mail.message_id
+ record.sent_at.should be_within(2).of(Time.now)
+ end
+ end
+ end
+ end
+end
View
11 spec/database.yml
@@ -0,0 +1,11 @@
+sqlite3:
+ adapter: sqlite3
+ database: action_mailer_queue.sqlite3
+
+mysql:
+ adapter: mysql2
+ hostname: localhost
+ username: root
+ password:
+ database: rails_plugin_test
+ socket: /tmp/mysql.sock
View
17 spec/models.rb
@@ -0,0 +1,17 @@
+class Email < ActiveRecord::Base
+end
+
+class MyMailer < ActionMailer::Base
+ self.delivery_method = :active_record
+
+ def test
+ mail(
+ :to => "to@to.com",
+ :from => "from@from.com",
+ :cc => "cc@cc.com",
+ :bcc => "bcc@bcc.com",
+ :subject => "subject",
+ :reply_to => "replyto@replyto.com"
+ )
+ end
+end
View
18 spec/schema.rb
@@ -0,0 +1,18 @@
+ActiveRecord::Schema.define :version => 0 do
+ create_table :emails, :force => true do |t|
+ t.column :id, :integer
+ t.column :from, :string
+ t.column :to, :string
+ t.column :subject, :string
+ t.column :content, :longblob
+ t.column :message_id, :string
+ t.column :sent, :boolean, :default => false, :null => false
+ t.column :attempts, :integer, :default => 0, :null => false
+ t.column :last_error, :string
+ t.column :priority, :integer, :default => 10, :null => false
+ t.column :last_attempt_at, :datetime
+ t.column :sent_at, :datetime
+ t.column :created_at, :datetime
+ t.column :updated_at, :datetime
+ end
+end
View
53 spec/spec_helper.rb
@@ -0,0 +1,53 @@
+$LOAD_PATH << "." unless $LOAD_PATH.include?(".")
+
+require "rubygems"
+require "bundler"
+
+if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.5")
+ raise RuntimeError, "Your bundler version is too old." +
+ "Run `gem install bundler` to upgrade."
+end
+
+# Set up load paths for all bundled gems
+Bundler.setup
+
+Bundler.require
+
+require "active_record"
+require "action_mailer"
+require "mail"
+require "active_support/core_ext/logger"
+
+ENV['DB'] ||= 'mysql'
+
+database_yml = File.expand_path('../database.yml', __FILE__)
+active_record_configuration = YAML.load_file(database_yml)[ENV['DB']]
+
+ActiveRecord::Base.establish_connection(active_record_configuration)
+ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
+
+ActiveRecord::Base.silence do
+ ActiveRecord::Migration.verbose = false
+
+ load(File.dirname(__FILE__) + '/schema.rb')
+ load(File.dirname(__FILE__) + '/models.rb')
+end
+
+def clean_database!
+ ActiveRecord::Base.connection.tables.each do |table|
+ ActiveRecord::Base.connection.execute "DELETE FROM #{table}"
+ end
+end
+
+RSpec.configure do |config|
+ config.before do
+ ActionMailer::Base.active_record_settings[:table_name] = "emails"
+ ActionMailer::Base.delivery_method = :test
+ ActionMailerQueue::Mailer.delivery_method = :test
+ clean_database!
+ end
+end
+
+require File.expand_path('../../lib/action_mailer_queue', __FILE__)
+
+clean_database!

No commit comments for this range

Something went wrong with that request. Please try again.