ar_mailer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright 2006, Eric Hodel, The Robot Co-op. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions
5
+ are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright
10
+ notice, this list of conditions and the following disclaimer in the
11
+ documentation and/or other materials provided with the distribution.
12
+ 3. Neither the names of the authors nor the names of their contributors
13
+ may be used to endorse or promote products derived from this software
14
+ without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
17
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
20
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
21
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
22
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
23
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
25
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
26
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ Manifest.txt
3
+ README
4
+ Rakefile
5
+ bin/ar_sendmail
6
+ lib/action_mailer/ar_mailer.rb
7
+ lib/action_mailer/ar_sendmail.rb
8
+ test/action_mailer.rb
9
+ test/test_armailer.rb
10
+ test/test_arsendmail.rb
data/README ADDED
@@ -0,0 +1,26 @@
1
+ = ar_mailer
2
+
3
+ Rubyforge Project:
4
+
5
+ http://rubyforge.org/projects/rctools
6
+
7
+ Documentation:
8
+
9
+ http://dev.robotcoop.com/Libraries/ar_mailer
10
+
11
+ == About
12
+
13
+ Even deliviring email to the local machine may take too long when you have to
14
+ send hundreds of messages. ar_mailer allows you to store messages into the
15
+ database for later delivery by a separate process, ar_sendmail.
16
+
17
+ == Installing ar_mailer
18
+
19
+ Just install the gem:
20
+
21
+ $ sudo gem install ar_mailer
22
+
23
+ See ActionMailer::ARMailer for instructions on converting to ARMailer.
24
+
25
+ See ar_sendmail -h for options to ar_sendmail.
26
+
@@ -0,0 +1,67 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+
7
+ $VERBOSE = nil
8
+
9
+ spec = Gem::Specification.new do |s|
10
+ s.name = 'ar_mailer'
11
+ s.version = '1.0.0'
12
+ s.summary = 'A two-phase deliver agent for ActionMailer'
13
+ s.description = 'Queues emails from ActionMailer in the database and uses a separate process to send them. Reduces sending overhead when sending hundreds of emails.'
14
+ s.author = 'Eric Hodel'
15
+ s.email = 'eric@robotcoop.com'
16
+
17
+ s.has_rdoc = true
18
+ s.files = File.read('Manifest.txt').split($/)
19
+ s.require_path = 'lib'
20
+
21
+ s.executables = ['ar_sendmail']
22
+ end
23
+
24
+ desc 'Run tests'
25
+ task :default => [ :test ]
26
+
27
+ Rake::TestTask.new('test') do |t|
28
+ t.libs << 'test'
29
+ t.pattern = 'test/test_*.rb'
30
+ t.verbose = true
31
+ end
32
+
33
+ desc 'Update Manifest.txt'
34
+ task :update_manifest do
35
+ sh "find . -type f | sed -e 's%./%%' | egrep -v 'svn|swp|~' | egrep -v '^(doc|pkg)/' | sort > Manifest.txt"
36
+ end
37
+
38
+ desc 'Generate RDoc'
39
+ Rake::RDocTask.new :rdoc do |rd|
40
+ rd.rdoc_dir = 'doc'
41
+ rd.rdoc_files.add 'lib', 'README', 'LICENSE'
42
+ rd.main = 'README'
43
+ rd.options << '-d' if `which dot` =~ /\/dot/
44
+ rd.options << '-t ar_mailer'
45
+ end
46
+
47
+ desc 'Generate RDoc for dev.robotcoop.com'
48
+ Rake::RDocTask.new :dev_rdoc do |rd|
49
+ rd.rdoc_dir = '../../../www/trunk/dev/html/Tools/ar_mailer'
50
+ rd.rdoc_files.add 'lib', 'README', 'LICENSE'
51
+ rd.main = 'README'
52
+ rd.options << '-d' if `which dot` =~ /\/dot/
53
+ rd.options << '-t ar_mailer'
54
+ end
55
+
56
+ desc 'Build Gem'
57
+ Rake::GemPackageTask.new spec do |pkg|
58
+ pkg.need_tar = true
59
+ end
60
+
61
+ desc 'Clean up'
62
+ task :clean => [ :clobber_rdoc, :clobber_package ]
63
+
64
+ desc 'Clean up'
65
+ task :clobber => [ :clean ]
66
+
67
+ # vim: syntax=Ruby
@@ -0,0 +1,11 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ begin
4
+ require 'config/environment'
5
+ rescue LoadError
6
+ # process_args will handle with sane message
7
+ end
8
+ require 'action_mailer/ar_sendmail'
9
+
10
+ ActionMailer::ARSendmail.run
11
+
@@ -0,0 +1,66 @@
1
+ require 'action_mailer'
2
+
3
+ ##
4
+ # Adds sending email through an ActiveRecord table as a delivery method for
5
+ # ActionMailer.
6
+ #
7
+ # == Converting to ActionMailer::ARMailer
8
+ #
9
+ # Go to your Rails project:
10
+ #
11
+ # $ cd your_rails_project
12
+ #
13
+ # Create a new migration:
14
+ #
15
+ # $ ar_sendmail --create-migration
16
+ #
17
+ # You'll need to redirect this into a file. If you want a different name
18
+ # provide the --table-name option.
19
+ #
20
+ # Create a new model:
21
+ #
22
+ # $ ar_sendmail --create-model
23
+ #
24
+ # You'll need to redirect this into a file. If you want a different name
25
+ # provide the --table-name option.
26
+ #
27
+ # Change your email classes to inherit from ActionMailer::ARMailer instead of
28
+ # ActionMailer::Base:
29
+ #
30
+ # --- app/model/emailer.rb.orig 2006-08-10 13:16:33.000000000 -0700
31
+ # +++ app/model/emailer.rb 2006-08-10 13:16:43.000000000 -0700
32
+ # @@ -1,4 +1,4 @@
33
+ # -class Emailer < ActionMailer::Base
34
+ # +class Emailer < ActionMailer::ARMailer
35
+ #
36
+ # def comment_notification(comment)
37
+ # from comment.author.email
38
+ #
39
+ # Edit config/environment/production.rb and set the delivery agent:
40
+ #
41
+ # $ ActionMailer::Base.delivery_method = :activerecord
42
+ #
43
+ # Run ar_sendmail:
44
+ #
45
+ # $ ar_sendmail
46
+ #
47
+ # You can also run it from cron with -o, or as a daemon with -d.
48
+ #
49
+ # See <tt>ar_sendmail -h</tt> for full details.
50
+
51
+ class ActionMailer::ARMailer < ActionMailer::Base
52
+
53
+ ##
54
+ # Adds +mail+ to the Email table. Only the first From address for +mail+ is
55
+ # used.
56
+
57
+ def perform_delivery_activerecord(mail)
58
+ mail.destinations.each do |destination|
59
+ Email.create :mail => mail.encoded,
60
+ :to => destination,
61
+ :from => mail.from.first
62
+ end
63
+ end
64
+
65
+ end
66
+
@@ -0,0 +1,351 @@
1
+ require 'optparse'
2
+ require 'rubygems'
3
+ require 'action_mailer'
4
+
5
+ class Object # :nodoc:
6
+ unless respond_to? :path2class then
7
+ def self.path2class(path)
8
+ path.split(/::/).inject self do |k,n| k.const_get n end
9
+ end
10
+ end
11
+ end
12
+
13
+ ##
14
+ # ActionMailer::ARSendmail delivers email from the email table to the
15
+ # configured SMTP server.
16
+ #
17
+ # See ar_sendmail -h for the full list of supported options.
18
+ #
19
+ # The interesting options are:
20
+ # * --daemon
21
+ # * --mailq
22
+ # * --create-migration
23
+ # * --create-model
24
+ # * --table-name
25
+
26
+ class ActionMailer::ARSendmail
27
+
28
+ ##
29
+ # Email delivery attempts per run
30
+
31
+ attr_accessor :batch_size
32
+
33
+ ##
34
+ # Seconds to delay between runs
35
+
36
+ attr_accessor :delay
37
+
38
+ ##
39
+ # Be verbose
40
+
41
+ attr_accessor :verbose
42
+
43
+ ##
44
+ # ActiveRecord class that holds emails
45
+
46
+ attr_reader :email_class
47
+
48
+ ##
49
+ # True if only one delivery attempt will be made per call to run
50
+
51
+ attr_reader :once
52
+
53
+ ##
54
+ # Creates a new migration using +table_name+ and prints it on $stdout.
55
+
56
+ def self.create_migration(table_name)
57
+ migration = <<-EOF
58
+ class Add#{table_name.classify} < ActiveRecord::Migration
59
+ def self.up
60
+ create_table :#{table_name.tableize} do |t|
61
+ t.column :from, :string
62
+ t.column :to, :string
63
+ t.column :last_send_attempt, :integer, :default => 0
64
+ t.column :mail, :text
65
+ end
66
+ end
67
+
68
+ def self.down
69
+ drop_table :email
70
+ end
71
+ end
72
+ EOF
73
+
74
+ $stdout.puts migration
75
+ end
76
+
77
+ ##
78
+ # Creates a new model using +table_name+ and prints it on $stdout.
79
+
80
+ def self.create_model(table_name)
81
+ model = <<-EOF
82
+ class #{table_name.classify} < ActiveRecord::Base
83
+ end
84
+ EOF
85
+
86
+ $stdout.puts model
87
+ end
88
+
89
+ ##
90
+ # Prints a list of unsent emails and the last delivery attempt, if any.
91
+ #
92
+ # If ActiveRecord::Timestamp is not being used the arrival time will not be
93
+ # known. See http://api.rubyonrails.org/classes/ActiveRecord/Timestamp.html
94
+ # to learn how to enable ActiveRecord::Timestamp.
95
+
96
+ def self.mailq
97
+ emails = Email.find :all
98
+
99
+ if emails.empty? then
100
+ $stdout.puts "Mail queue is empty"
101
+ return
102
+ end
103
+
104
+ total_size = 0
105
+
106
+ $stdout.puts "-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------"
107
+ emails.each do |email|
108
+ size = email.mail.length
109
+ total_size += size
110
+
111
+ create_timestamp = email.created_at rescue
112
+ email.created_on rescue
113
+ Time.at(email.created_date) rescue # for Robot Co-op
114
+ nil
115
+
116
+ created = if create_timestamp.nil? then
117
+ ' Unknown'
118
+ else
119
+ create_timestamp.strftime '%a %b %d %H:%M:%S'
120
+ end
121
+
122
+ $stdout.puts "%10d %8d %s %s" % [email.id, size, created, email.from]
123
+ if email.last_send_attempt then
124
+ $stdout.puts "Last send attempt: #{email.last_send_attempt}"
125
+ end
126
+ $stdout.puts " #{email.to}"
127
+ $stdout.puts
128
+ end
129
+
130
+ $stdout.puts "-- #{total_size/1024} Kbytes in #{emails.length} Requests."
131
+ end
132
+
133
+ ##
134
+ # Processes command line options in +args+
135
+
136
+ def self.process_args(args)
137
+ name = File.basename $0
138
+
139
+ options = {}
140
+ options[:Daemon] = false
141
+ options[:Delay] = 60
142
+ options[:Once] = false
143
+ options[:TableName] = 'Email'
144
+
145
+ opts = OptionParser.new do |opts|
146
+ opts.banner = "Usage: #{name} [options]"
147
+ opts.separator ''
148
+
149
+ opts.separator "#{name} scans the email table for new messages and sends them to the"
150
+ opts.separator "website's configured SMTP host."
151
+ opts.separator ''
152
+ opts.separator "#{name} must be run from the application's root."
153
+
154
+ opts.separator ''
155
+ opts.separator 'Sendmail options:'
156
+
157
+ opts.on("-b", "--batch-size BATCH_SIZE",
158
+ "Maximum number of emails to send per delay",
159
+ "Default: Deliver all available emails", Integer) do |batch_size|
160
+ options[:BatchSize] = batch_size
161
+ end
162
+
163
+ opts.on( "--delay DELAY",
164
+ "Delay between checks for new mail",
165
+ "in the database",
166
+ "Default: #{options[:Delay]}", Integer) do |delay|
167
+ options[:Delay] = delay
168
+ end
169
+
170
+ opts.on("-o", "--once",
171
+ "Only check for new mail and deliver once",
172
+ "Default: #{options[:Once]}") do |once|
173
+ options[:Once] = once
174
+ end
175
+
176
+ opts.on("-d", "--daemonize",
177
+ "Run as a daemon process",
178
+ "Default: #{options[:Daemon]}") do |daemon|
179
+ options[:Daemon] = true
180
+ end
181
+
182
+ opts.on( "--mailq",
183
+ "Display a list of emails waiting to be sent") do |mailq|
184
+ options[:MailQ] = true
185
+ end
186
+
187
+ opts.separator ''
188
+ opts.separator 'Setup Options:'
189
+
190
+ opts.on( "--create-migration",
191
+ "Prints a migration to add an Email table",
192
+ "to $stdout") do |create|
193
+ options[:Migrate] = true
194
+ end
195
+
196
+ opts.on( "--create-model",
197
+ "Prints a model for an Email ActiveRecord",
198
+ "object to $stdout") do |create|
199
+ options[:Model] = true
200
+ end
201
+
202
+ opts.separator ''
203
+ opts.separator 'Generic Options:'
204
+
205
+ opts.on("-t", "--table-name TABLE_NAME",
206
+ "Name of table holding emails",
207
+ "Used for both sendmail and",
208
+ "migration creation",
209
+ "Default: #{options[:TableName]}") do |name|
210
+ options[:TableName] = name
211
+ end
212
+
213
+ opts.on("-v", "--[no-]verbose",
214
+ "Be verbose",
215
+ "Default: #{options[:Verbose]}") do |verbose|
216
+ options[:Verbose] = verbose
217
+ end
218
+
219
+ opts.on("-h", "--help",
220
+ "You're looking at it") do
221
+ $stderr.puts opts
222
+ exit 1
223
+ end
224
+
225
+ opts.separator ''
226
+ end
227
+
228
+ opts.parse! args
229
+
230
+ unless options.include? :Migrate or options.include? :Model or
231
+ not $".grep(/config\/environment.rb/).empty? then
232
+ $stderr.puts "#{name} must be run from a Rails application's root to deliver email."
233
+ $stderr.puts
234
+ $stderr.puts opts
235
+ exit 1
236
+ end
237
+
238
+ return options
239
+ end
240
+
241
+ ##
242
+ # Processes +args+ and runs as appropriate
243
+
244
+ def self.run(args = ARGV)
245
+ options = process_args args
246
+
247
+ if options.include? :Migrate then
248
+ create_migration options[:TableName]
249
+ exit
250
+ elsif options.include? :Model then
251
+ create_model options[:TableName]
252
+ exit
253
+ elsif options.include? :Mailq then
254
+ mailq
255
+ exit
256
+ end
257
+
258
+ if options[:Daemon] then
259
+ require 'webrick/server'
260
+ WEBrick::Daemon.start
261
+ end
262
+
263
+ new(options).run
264
+ end
265
+
266
+ ##
267
+ # Creates a new ARSendmail.
268
+ #
269
+ # Valid options are:
270
+ # <tt>:BatchSize</tt>:: Maximum number of emails to send per delay
271
+ # <tt>:Delay</tt>:: Delay between deliver attempts
272
+ # <tt>:TableName</tt>:: Table name that stores the emails
273
+ # <tt>:Once</tt>:: Only attempt to deliver emails once when run is called
274
+ # <tt>:Verbose</tt>:: Be verbose.
275
+
276
+ def initialize(options = {})
277
+ options[:Delay] ||= 60
278
+ options[:TableName] ||= 'Email'
279
+
280
+ @batch_size = options[:BatchSize]
281
+ @delay = options[:Delay]
282
+ @email_class = Object.path2class options[:TableName]
283
+ @once = options[:Once]
284
+ @verbose = options[:Verbose]
285
+ end
286
+
287
+ ##
288
+ # Delivers +emails+ to ActionMailer's SMTP server and destroys them.
289
+
290
+ def deliver(emails)
291
+ Net::SMTP.start server_settings[:address], server_settings[:port],
292
+ server_settings[:domain], server_settings[:user],
293
+ server_settings[:password],
294
+ server_settings[:authentication] do |smtp|
295
+ emails.each do |email|
296
+ begin
297
+ res = smtp.send_message email.mail, email.to, email.from
298
+ email.destroy
299
+ log "sent email from %s to %s: %p" % [email.from, email.to, res]
300
+ rescue Net::SMTPServerBusy, Net::SMTPFatalError,
301
+ Net::SMTPUnknownError, TimeoutError
302
+ email.last_send_attempt = Time.now.to_i
303
+ end
304
+ end
305
+ end
306
+ end
307
+
308
+ ##
309
+ # Returns emails in email_class that haven't had a delivery attempt in the
310
+ # last 300 seconds.
311
+
312
+ def find_emails
313
+ options = { :conditions => ['last_send_attempt < ?', Time.now.to_i - 300] }
314
+ options[:limit] = batch_size unless batch_size.nil?
315
+ mail = @email_class.find :all, options
316
+
317
+ log "found #{mail.length} emails to send"
318
+ mail
319
+ end
320
+
321
+ ##
322
+ # Logs +message+ if verbose
323
+
324
+ def log(message)
325
+ $stderr.puts message if @verbose
326
+ ActionMailer::Base.logger.info "ar_sendmail: #{message}"
327
+ end
328
+
329
+ ##
330
+ # Scans for emails and delivers them every delay seconds. Only returns if
331
+ # once is true.
332
+
333
+ def run
334
+ loop do
335
+ deliver find_emails
336
+ break if @once
337
+ sleep @delay
338
+ end
339
+ end
340
+
341
+ ##
342
+ # Proxy to ActionMailer::Base#server_settings. See
343
+ # http://api.rubyonrails.org/classes/ActionMailer/Base.html
344
+ # for instructions on how to configure ActionMailer's SMTP server.
345
+
346
+ def server_settings
347
+ ActionMailer::Base.server_settings
348
+ end
349
+
350
+ end
351
+
@@ -0,0 +1,131 @@
1
+ require 'net/smtp'
2
+ require 'time'
3
+
4
+ class Net::SMTP
5
+
6
+ @deliveries = []
7
+
8
+ @send_message_block = nil
9
+
10
+ class << self
11
+
12
+ attr_reader :deliveries
13
+ attr_reader :send_message_block
14
+
15
+ alias old_start start
16
+
17
+ end
18
+
19
+ def self.start(*args)
20
+ yield new(nil)
21
+ end
22
+
23
+ def self.on_send_message(&block)
24
+ @send_message_block = block
25
+ end
26
+
27
+ alias old_send_message send_message
28
+
29
+ def send_message(mail, to, from)
30
+ return self.class.send_message_block.call(mail, to, from) unless
31
+ self.class.send_message_block.nil?
32
+ self.class.deliveries << [mail, to, from]
33
+ end
34
+
35
+ end
36
+
37
+ ##
38
+ # Stub for ActionMailer::Base
39
+
40
+ module ActionMailer; end
41
+
42
+ class ActionMailer::Base
43
+
44
+ @server_settings = {}
45
+
46
+ def self.logger
47
+ o = Object.new
48
+ def o.info(arg) end
49
+ return o
50
+ end
51
+
52
+ def self.method_missing(meth, *args)
53
+ meth.to_s =~ /deliver_(.*)/
54
+ super unless $1
55
+ new($1, *args).deliver!
56
+ end
57
+
58
+ def self.server_settings
59
+ @server_settings
60
+ end
61
+
62
+ def initialize(meth = nil)
63
+ send meth if meth
64
+ end
65
+
66
+ def deliver!
67
+ perform_delivery_activerecord @mail
68
+ end
69
+
70
+ end
71
+
72
+ ##
73
+ # Stub for an ActiveRecord model
74
+
75
+ class Email
76
+
77
+ START = Time.parse 'Thu Aug 10 11:19:48'
78
+
79
+ attr_accessor :from, :to, :mail, :last_send_attempt, :created_on, :id
80
+
81
+ @records = []
82
+ @id = 0
83
+
84
+ class << self; attr_accessor :records, :id; end
85
+
86
+ def self.create(record)
87
+ record = new record[:from], record[:to], record[:mail]
88
+ records << record
89
+ return record
90
+ end
91
+
92
+ def self.find(_, conditions = nil)
93
+ return records if conditions.nil?
94
+ now = Time.now.to_i - 300
95
+ return records.select do |r|
96
+ r.last_send_attempt.nil? or r.last_send_attempt < now
97
+ end
98
+ end
99
+
100
+ def initialize(from, to, mail)
101
+ @from = from
102
+ @to = to
103
+ @mail = mail
104
+ @id = self.class.id += 1
105
+ @created_on = START + @id
106
+ end
107
+
108
+ def destroy
109
+ self.class.records.delete self
110
+ self.freeze
111
+ end
112
+
113
+ def ==(other)
114
+ other.from == from and
115
+ other.to == to and
116
+ other.mail == mail
117
+ end
118
+
119
+ end
120
+
121
+ class String
122
+ def classify
123
+ self
124
+ end
125
+
126
+ def tableize
127
+ self.downcase
128
+ end
129
+
130
+ end
131
+
@@ -0,0 +1,39 @@
1
+ require 'test/unit'
2
+ require 'action_mailer'
3
+ require 'action_mailer/ar_mailer'
4
+
5
+ ##
6
+ # Pretend mailer
7
+
8
+ class Mailer < ActionMailer::ARMailer
9
+
10
+ def mail
11
+ @mail = Object.new
12
+ def @mail.encoded() 'email' end
13
+ def @mail.from() ['nobody@example.com'] end
14
+ def @mail.destinations() %w[user1@example.com user2@example.com] end
15
+ end
16
+
17
+ end
18
+
19
+ class TestARMailer < Test::Unit::TestCase
20
+
21
+ def setup
22
+ Email.records.clear
23
+ end
24
+
25
+ def test_perform_delivery_activerecord
26
+ Mailer.deliver_mail
27
+
28
+ assert_equal 2, Email.records.length
29
+
30
+ record = Email.records.first
31
+ assert_equal 'email', record.mail
32
+ assert_equal 'user1@example.com', record.to
33
+ assert_equal 'nobody@example.com', record.from
34
+
35
+ assert_equal 'user2@example.com', Email.records.last.to
36
+ end
37
+
38
+ end
39
+
@@ -0,0 +1,367 @@
1
+ require 'test/unit'
2
+ require 'action_mailer'
3
+ require 'action_mailer/ar_sendmail'
4
+ require 'rubygems'
5
+ require 'test/zentest_assertions'
6
+
7
+ class TestARSendmail < Test::Unit::TestCase
8
+
9
+ def setup
10
+ ActionMailer::Base.server_settings.clear
11
+ Email.id = 0
12
+ Email.records.clear
13
+ Net::SMTP.deliveries.clear
14
+ Net::SMTP.on_send_message # reset
15
+
16
+ @sm = ActionMailer::ARSendmail.new
17
+
18
+ @include_c_e = ! $".grep(/config\/environment.rb/).empty?
19
+ $" << 'config/environment.rb' unless @include_c_e
20
+ end
21
+
22
+ def teardown
23
+ $".delete 'config/environment.rb' unless @include_c_e
24
+ end
25
+
26
+ def test_class_create_migration
27
+ out, = util_capture do
28
+ ActionMailer::ARSendmail.create_migration 'Email'
29
+ end
30
+
31
+ expected = <<-EOF
32
+ class AddEmail < ActiveRecord::Migration
33
+ def self.up
34
+ create_table :email do |t|
35
+ t.column :from, :string
36
+ t.column :to, :string
37
+ t.column :last_send_attempt, :integer, :default => 0
38
+ t.column :mail, :text
39
+ end
40
+ end
41
+
42
+ def self.down
43
+ drop_table :email
44
+ end
45
+ end
46
+ EOF
47
+
48
+ assert_equal expected, out.string
49
+ end
50
+
51
+ def test_class_create_migration_table_name
52
+ out, = util_capture do
53
+ ActionMailer::ARSendmail.create_migration 'Mail'
54
+ end
55
+
56
+ expected = <<-EOF
57
+ class AddMail < ActiveRecord::Migration
58
+ def self.up
59
+ create_table :mail do |t|
60
+ t.column :from, :string
61
+ t.column :to, :string
62
+ t.column :last_send_attempt, :integer, :default => 0
63
+ t.column :mail, :text
64
+ end
65
+ end
66
+
67
+ def self.down
68
+ drop_table :email
69
+ end
70
+ end
71
+ EOF
72
+
73
+ assert_equal expected, out.string
74
+ end
75
+
76
+ def test_class_create_model
77
+ out, = util_capture do
78
+ ActionMailer::ARSendmail.create_model 'Email'
79
+ end
80
+
81
+ expected = <<-EOF
82
+ class Email < ActiveRecord::Base
83
+ end
84
+ EOF
85
+
86
+ assert_equal expected, out.string
87
+ end
88
+
89
+ def test_class_create_model_table_name
90
+ out, = util_capture do
91
+ ActionMailer::ARSendmail.create_model 'Mail'
92
+ end
93
+
94
+ expected = <<-EOF
95
+ class Mail < ActiveRecord::Base
96
+ end
97
+ EOF
98
+
99
+ assert_equal expected, out.string
100
+ end
101
+
102
+ def test_class_mailq
103
+ Email.create :from => nobody, :to => 'recip@h1.example.com',
104
+ :mail => 'body0'
105
+ Email.create :from => nobody, :to => 'recip@h1.example.com',
106
+ :mail => 'body1'
107
+ last = Email.create :from => nobody, :to => 'recip@h2.example.com',
108
+ :mail => 'body2'
109
+
110
+ last.last_send_attempt = Time.parse 'Thu Aug 10 11:40:05'
111
+
112
+ out, err = util_capture do
113
+ ActionMailer::ARSendmail.mailq
114
+ end
115
+
116
+ expected = <<-EOF
117
+ -Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------
118
+ 1 5 Thu Aug 10 11:19:49 nobody@example.com
119
+ recip@h1.example.com
120
+
121
+ 2 5 Thu Aug 10 11:19:50 nobody@example.com
122
+ recip@h1.example.com
123
+
124
+ 3 5 Thu Aug 10 11:19:51 nobody@example.com
125
+ Last send attempt: Thu Aug 10 11:40:05 -0700 2006
126
+ recip@h2.example.com
127
+
128
+ -- 0 Kbytes in 3 Requests.
129
+ EOF
130
+
131
+ assert_equal expected, out.string
132
+ end
133
+
134
+ def test_class_mailq_empty
135
+ out, err = util_capture do
136
+ ActionMailer::ARSendmail.mailq
137
+ end
138
+
139
+ assert_equal "Mail queue is empty\n", out.string
140
+ end
141
+
142
+ def test_class_new
143
+ assert_equal 60, @sm.delay
144
+ assert_equal Email, @sm.email_class
145
+ assert_equal nil, @sm.once
146
+ assert_equal nil, @sm.verbose
147
+ assert_equal nil, @sm.batch_size
148
+
149
+ @sm = ActionMailer::ARSendmail.new :Delay => 75, :Verbose => true,
150
+ :TableName => 'Object', :Once => true,
151
+ :BatchSize => 1000
152
+
153
+ assert_equal 75, @sm.delay
154
+ assert_equal Object, @sm.email_class
155
+ assert_equal true, @sm.once
156
+ assert_equal true, @sm.verbose
157
+ assert_equal 1000, @sm.batch_size
158
+ end
159
+
160
+ def test_class_parse_args_batch_size
161
+ options = ActionMailer::ARSendmail.process_args %w[-b 500]
162
+
163
+ assert_equal 500, options[:BatchSize]
164
+
165
+ options = ActionMailer::ARSendmail.process_args %w[--batch-size 500]
166
+
167
+ assert_equal 500, options[:BatchSize]
168
+ end
169
+
170
+ def test_class_parse_args_daemon
171
+ argv = %w[-d]
172
+
173
+ options = ActionMailer::ARSendmail.process_args argv
174
+
175
+ assert_equal true, options[:Daemon]
176
+
177
+ argv = %w[--daemon]
178
+
179
+ options = ActionMailer::ARSendmail.process_args argv
180
+
181
+ assert_equal true, options[:Daemon]
182
+ end
183
+
184
+ def test_class_parse_args_delay
185
+ argv = %w[--delay 75]
186
+
187
+ options = ActionMailer::ARSendmail.process_args argv
188
+
189
+ assert_equal 75, options[:Delay]
190
+ end
191
+
192
+ def test_class_parse_args_mailq
193
+ options = ActionMailer::ARSendmail.process_args []
194
+ deny_includes options, :MailQ
195
+
196
+ argv = %w[--mailq]
197
+
198
+ options = ActionMailer::ARSendmail.process_args argv
199
+
200
+ assert_equal true, options[:MailQ]
201
+ end
202
+
203
+ def test_class_parse_args_migration
204
+ options = ActionMailer::ARSendmail.process_args []
205
+ deny_includes options, :Migrate
206
+
207
+ argv = %w[--create-migration]
208
+
209
+ options = ActionMailer::ARSendmail.process_args argv
210
+
211
+ assert_equal true, options[:Migrate]
212
+ end
213
+
214
+ def test_class_parse_args_model
215
+ options = ActionMailer::ARSendmail.process_args []
216
+ deny_includes options, :Model
217
+
218
+ argv = %w[--create-model]
219
+
220
+ options = ActionMailer::ARSendmail.process_args argv
221
+
222
+ assert_equal true, options[:Model]
223
+ end
224
+
225
+ def test_class_parse_args_no_config_environment
226
+ $".delete 'config/environment.rb'
227
+
228
+ assert_raise SystemExit do
229
+ out, err = util_capture do
230
+ ActionMailer::ARSendmail.process_args []
231
+ end
232
+ end
233
+
234
+ ensure
235
+ $" << 'config/environment.rb' if @include_c_e
236
+ end
237
+
238
+ def test_class_parse_args_no_config_environment_migrate
239
+ $".delete 'config/environment.rb'
240
+
241
+ out, err = util_capture do
242
+ ActionMailer::ARSendmail.process_args %w[--create-migration]
243
+ end
244
+
245
+ assert true # count
246
+
247
+ ensure
248
+ $" << 'config/environment.rb' if @include_c_e
249
+ end
250
+
251
+ def test_class_parse_args_no_config_environment_model
252
+ $".delete 'config/environment.rb'
253
+
254
+ out, err = util_capture do
255
+ ActionMailer::ARSendmail.process_args %w[--create-model]
256
+ end
257
+
258
+ assert true # count
259
+
260
+ rescue SystemExit
261
+ flunk 'Should not exit'
262
+
263
+ ensure
264
+ $" << 'config/environment.rb' if @include_c_e
265
+ end
266
+
267
+ def test_class_parse_args_once
268
+ argv = %w[-o]
269
+
270
+ options = ActionMailer::ARSendmail.process_args argv
271
+
272
+ assert_equal true, options[:Once]
273
+
274
+ argv = %w[--once]
275
+
276
+ options = ActionMailer::ARSendmail.process_args argv
277
+
278
+ assert_equal true, options[:Once]
279
+ end
280
+
281
+ def test_class_parse_args_table_name
282
+ argv = %w[-t Email]
283
+
284
+ options = ActionMailer::ARSendmail.process_args argv
285
+
286
+ assert_equal 'Email', options[:TableName]
287
+
288
+ argv = %w[--table-name=Email]
289
+
290
+ options = ActionMailer::ARSendmail.process_args argv
291
+
292
+ assert_equal 'Email', options[:TableName]
293
+ end
294
+
295
+ def test_deliver
296
+ email = Email.create :mail => 'body', :to => 'to', :from => 'from'
297
+
298
+ @sm.deliver [email]
299
+
300
+ assert_equal 1, Net::SMTP.deliveries.length
301
+ assert_equal ['body', 'to', 'from'], Net::SMTP.deliveries.first
302
+ assert_equal 0, Email.records.length
303
+ end
304
+
305
+ def test_deliver_500
306
+ Net::SMTP.on_send_message do
307
+ raise Net::SMTPServerBusy
308
+ end
309
+
310
+ now = Time.now.to_i
311
+
312
+ email = Email.create :mail => 'body', :to => 'to', :from => 'from'
313
+
314
+ @sm.deliver [email]
315
+
316
+ assert_equal 0, Net::SMTP.deliveries.length
317
+ assert_equal 1, Email.records.length
318
+ assert_operator now, :<=, Email.records.first.last_send_attempt
319
+ end
320
+
321
+ def test_log
322
+ @sm = ActionMailer::ARSendmail.new :Verbose => true
323
+
324
+ out, err = util_capture do
325
+ @sm.log 'hi'
326
+ end
327
+
328
+ assert_equal "hi\n", err.string
329
+ end
330
+
331
+ def test_find_emails
332
+ emails = [
333
+ { :mail => 'body0', :to => 'recip@h1.example.com', :from => nobody },
334
+ { :mail => 'body1', :to => 'recip@h1.example.com', :from => nobody },
335
+ { :mail => 'body2', :to => 'recip@h2.example.com', :from => nobody },
336
+ ]
337
+
338
+ emails.each do |email| Email.create email end
339
+
340
+ tried = Email.create :mail => 'body3', :to => 'recip@h3.example.com',
341
+ :from => nobody
342
+
343
+ tried.last_send_attempt = Time.now.to_i - 258
344
+
345
+ found_emails = @sm.find_emails
346
+
347
+ expected = [
348
+ Email.new(nobody, 'recip@h1.example.com', 'body0'),
349
+ Email.new(nobody, 'recip@h1.example.com', 'body1'),
350
+ Email.new(nobody, 'recip@h2.example.com', 'body2'),
351
+ ]
352
+
353
+ assert_equal expected, found_emails
354
+ end
355
+
356
+ def test_server_settings
357
+ ActionMailer::Base.server_settings[:address] = 'localhost'
358
+
359
+ assert_equal 'localhost', @sm.server_settings[:address]
360
+ end
361
+
362
+ def nobody
363
+ 'nobody@example.com'
364
+ end
365
+
366
+ end
367
+
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.99
3
+ specification_version: 1
4
+ name: ar_mailer
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.0
7
+ date: 2006-08-10 00:00:00 -07:00
8
+ summary: A two-phase deliver agent for ActionMailer
9
+ require_paths:
10
+ - lib
11
+ email: eric@robotcoop.com
12
+ homepage:
13
+ rubyforge_project:
14
+ description: Queues emails from ActionMailer in the database and uses a separate process to send them. Reduces sending overhead when sending hundreds of emails.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Eric Hodel
31
+ files:
32
+ - LICENSE
33
+ - Manifest.txt
34
+ - README
35
+ - Rakefile
36
+ - bin/ar_sendmail
37
+ - lib/action_mailer/ar_mailer.rb
38
+ - lib/action_mailer/ar_sendmail.rb
39
+ - test/action_mailer.rb
40
+ - test/test_armailer.rb
41
+ - test/test_arsendmail.rb
42
+ test_files: []
43
+
44
+ rdoc_options: []
45
+
46
+ extra_rdoc_files: []
47
+
48
+ executables:
49
+ - ar_sendmail
50
+ extensions: []
51
+
52
+ requirements: []
53
+
54
+ dependencies: []
55
+