ar_mailer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+