ar_mailer 1.2.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +16 -0
- data/README.txt +4 -1
- data/Rakefile +3 -1
- data/lib/action_mailer/ar_mailer.rb +0 -2
- data/lib/action_mailer/ar_sendmail.rb +77 -17
- data/test/action_mailer.rb +27 -3
- data/test/test_arsendmail.rb +110 -4
- metadata +13 -7
data/History.txt
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
= 1.3.1
|
2
|
+
|
3
|
+
* Fix bug #12530, gmail causes SSL errors. Submitted by Kyle Maxwell
|
4
|
+
and Alex Ostleitner.
|
5
|
+
* Try ActionMailer::Base::server_settings then ::smtp_settings. Fixes
|
6
|
+
bug #12516. Submitted by Alex Ostleitner.
|
7
|
+
|
8
|
+
= 1.3.0
|
9
|
+
|
10
|
+
* New Features
|
11
|
+
* Added automatic mail queue cleanup.
|
12
|
+
* MAY CAUSE LOSS OF DATA. If you haven't run ar_sendmail within
|
13
|
+
the expiry time, set it to 0.
|
14
|
+
* Bugs fixed
|
15
|
+
* Authentication errors are now handled by retrying once.
|
16
|
+
|
1
17
|
= 1.2.0
|
2
18
|
|
3
19
|
* Bugs fixed
|
data/README.txt
CHANGED
@@ -16,7 +16,7 @@ http://rubyforge.org/tracker/?func=add&group_id=1513&atid=5921
|
|
16
16
|
|
17
17
|
== About
|
18
18
|
|
19
|
-
Even
|
19
|
+
Even delivering email to the local machine may take too long when you have to
|
20
20
|
send hundreds of messages. ar_mailer allows you to store messages into the
|
21
21
|
database for later delivery by a separate process, ar_sendmail.
|
22
22
|
|
@@ -30,6 +30,9 @@ See ActionMailer::ARMailer for instructions on converting to ARMailer.
|
|
30
30
|
|
31
31
|
See ar_sendmail -h for options to ar_sendmail.
|
32
32
|
|
33
|
+
NOTE: You may need to delete an smtp_tls.rb file if you have one lying
|
34
|
+
around. ar_mailer supplies it own.
|
35
|
+
|
33
36
|
=== ar_sendmail on FreeBSD or NetBSD
|
34
37
|
|
35
38
|
An rc.d script is included in share/ar_sendmail.
|
data/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'hoe'
|
2
2
|
|
3
|
-
|
3
|
+
require './lib/action_mailer/ar_sendmail'
|
4
|
+
|
5
|
+
Hoe.new 'ar_mailer', ActionMailer::ARSendmail::VERSION do |s|
|
4
6
|
s.rubyforge_name = 'seattlerb'
|
5
7
|
s.summary = s.paragraphs_of('README.txt', 1).join(' ')
|
6
8
|
s.description = s.paragraphs_of('README.txt', 9).join(' ')
|
@@ -51,6 +51,16 @@ module ActionMailer; end # :nodoc:
|
|
51
51
|
|
52
52
|
class ActionMailer::ARSendmail
|
53
53
|
|
54
|
+
##
|
55
|
+
# The version of ActionMailer::ARSendmail you are running.
|
56
|
+
|
57
|
+
VERSION = '1.3.1'
|
58
|
+
|
59
|
+
##
|
60
|
+
# Maximum number of times authentication will be consecutively retried
|
61
|
+
|
62
|
+
MAX_AUTH_FAILURES = 2
|
63
|
+
|
54
64
|
##
|
55
65
|
# Email delivery attempts per run
|
56
66
|
|
@@ -61,6 +71,11 @@ class ActionMailer::ARSendmail
|
|
61
71
|
|
62
72
|
attr_accessor :delay
|
63
73
|
|
74
|
+
##
|
75
|
+
# Maximum age of emails in seconds before they are removed from the queue.
|
76
|
+
|
77
|
+
attr_accessor :max_age
|
78
|
+
|
64
79
|
##
|
65
80
|
# Be verbose
|
66
81
|
|
@@ -76,6 +91,11 @@ class ActionMailer::ARSendmail
|
|
76
91
|
|
77
92
|
attr_reader :once
|
78
93
|
|
94
|
+
##
|
95
|
+
# Times authentication has failed
|
96
|
+
|
97
|
+
attr_accessor :failed_auth_count
|
98
|
+
|
79
99
|
##
|
80
100
|
# Creates a new migration using +table_name+ and prints it on stdout.
|
81
101
|
|
@@ -94,7 +114,7 @@ class Add#{table_name.classify} < ActiveRecord::Migration
|
|
94
114
|
end
|
95
115
|
|
96
116
|
def self.down
|
97
|
-
drop_table
|
117
|
+
drop_table :#{table_name.tableize}
|
98
118
|
end
|
99
119
|
end
|
100
120
|
EOF
|
@@ -134,8 +154,8 @@ end
|
|
134
154
|
size = email.mail.length
|
135
155
|
total_size += size
|
136
156
|
|
137
|
-
create_timestamp = email.
|
138
|
-
email.
|
157
|
+
create_timestamp = email.created_on rescue
|
158
|
+
email.created_at rescue
|
139
159
|
Time.at(email.created_date) rescue # for Robot Co-op
|
140
160
|
nil
|
141
161
|
|
@@ -163,12 +183,13 @@ end
|
|
163
183
|
name = File.basename $0
|
164
184
|
|
165
185
|
options = {}
|
186
|
+
options[:Chdir] = '.'
|
166
187
|
options[:Daemon] = false
|
167
188
|
options[:Delay] = 60
|
189
|
+
options[:MaxAge] = 86400 * 7
|
168
190
|
options[:Once] = false
|
169
|
-
options[:TableName] = 'Email'
|
170
|
-
options[:Chdir] = '.'
|
171
191
|
options[:RailsEnv] = ENV['RAILS_ENV']
|
192
|
+
options[:TableName] = 'Email'
|
172
193
|
|
173
194
|
opts = OptionParser.new do |opts|
|
174
195
|
opts.banner = "Usage: #{name} [options]"
|
@@ -195,6 +216,14 @@ end
|
|
195
216
|
options[:Delay] = delay
|
196
217
|
end
|
197
218
|
|
219
|
+
opts.on( "--max-age MAX_AGE",
|
220
|
+
"Maxmimum age for an email. After this",
|
221
|
+
"it will be removed from the queue.",
|
222
|
+
"Set to 0 to disable queue cleanup.",
|
223
|
+
"Default: #{options[:MaxAge]} seconds", Integer) do |max_age|
|
224
|
+
options[:MaxAge] = max_age
|
225
|
+
end
|
226
|
+
|
198
227
|
opts.on("-o", "--once",
|
199
228
|
"Only check for new mail and deliver once",
|
200
229
|
"Default: #{options[:Once]}") do |once|
|
@@ -346,24 +375,42 @@ end
|
|
346
375
|
def initialize(options = {})
|
347
376
|
options[:Delay] ||= 60
|
348
377
|
options[:TableName] ||= 'Email'
|
378
|
+
options[:MaxAge] ||= 86400 * 7
|
349
379
|
|
350
380
|
@batch_size = options[:BatchSize]
|
351
381
|
@delay = options[:Delay]
|
352
382
|
@email_class = Object.path2class options[:TableName]
|
353
383
|
@once = options[:Once]
|
354
384
|
@verbose = options[:Verbose]
|
385
|
+
@max_age = options[:MaxAge]
|
386
|
+
|
387
|
+
@failed_auth_count = 0
|
388
|
+
end
|
389
|
+
|
390
|
+
##
|
391
|
+
# Removes emails that have lived in the queue for too long. If max_age is
|
392
|
+
# set to 0, no emails will be removed.
|
393
|
+
|
394
|
+
def cleanup
|
395
|
+
return if @max_age == 0
|
396
|
+
timeout = Time.now - @max_age
|
397
|
+
conditions = ['last_send_attempt > 0 and created_on < ?', timeout]
|
398
|
+
mail = @email_class.destroy_all conditions
|
399
|
+
|
400
|
+
log "expired #{mail.length} emails from the queue"
|
355
401
|
end
|
356
402
|
|
357
403
|
##
|
358
404
|
# Delivers +emails+ to ActionMailer's SMTP server and destroys them.
|
359
405
|
|
360
406
|
def deliver(emails)
|
361
|
-
user =
|
362
|
-
Net::SMTP.start
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
407
|
+
user = smtp_settings[:user] || smtp_settings[:user_name]
|
408
|
+
Net::SMTP.start smtp_settings[:address], smtp_settings[:port],
|
409
|
+
smtp_settings[:domain], user,
|
410
|
+
smtp_settings[:password],
|
411
|
+
smtp_settings[:authentication],
|
412
|
+
smtp_settings[:tls] do |smtp|
|
413
|
+
@failed_auth_count = 0
|
367
414
|
until emails.empty? do
|
368
415
|
email = emails.shift
|
369
416
|
begin
|
@@ -378,9 +425,9 @@ end
|
|
378
425
|
smtp.reset
|
379
426
|
rescue Net::SMTPServerBusy => e
|
380
427
|
log "server too busy, sleeping #{@delay} seconds"
|
381
|
-
sleep delay
|
428
|
+
sleep delay
|
382
429
|
return
|
383
|
-
rescue Net::
|
430
|
+
rescue Net::SMTPUnknownError, Net::SMTPSyntaxError, TimeoutError => e
|
384
431
|
email.last_send_attempt = Time.now.to_i
|
385
432
|
email.save rescue nil
|
386
433
|
log "error sending email %d: %p(%s):\n\t%s" %
|
@@ -389,7 +436,16 @@ end
|
|
389
436
|
end
|
390
437
|
end
|
391
438
|
end
|
392
|
-
rescue Net::
|
439
|
+
rescue Net::SMTPAuthenticationError => e
|
440
|
+
@failed_auth_count += 1
|
441
|
+
if @failed_auth_count >= MAX_AUTH_FAILURES then
|
442
|
+
log "authentication error, giving up: #{e.message}"
|
443
|
+
raise e
|
444
|
+
else
|
445
|
+
log "authentication error, retrying: #{e.message}"
|
446
|
+
end
|
447
|
+
sleep delay
|
448
|
+
rescue Net::SMTPServerBusy, SystemCallError, OpenSSL::SSL::SSLError
|
393
449
|
# ignore SMTPServerBusy/EPIPE/ECONNRESET from Net::SMTP.start's ensure
|
394
450
|
end
|
395
451
|
|
@@ -440,6 +496,7 @@ end
|
|
440
496
|
loop do
|
441
497
|
now = Time.now
|
442
498
|
begin
|
499
|
+
cleanup
|
443
500
|
deliver find_emails
|
444
501
|
rescue ActiveRecord::Transactions::TransactionError
|
445
502
|
end
|
@@ -449,12 +506,15 @@ end
|
|
449
506
|
end
|
450
507
|
|
451
508
|
##
|
452
|
-
# Proxy to ActionMailer::Base
|
509
|
+
# Proxy to ActionMailer::Base::smtp_settings. See
|
453
510
|
# http://api.rubyonrails.org/classes/ActionMailer/Base.html
|
454
511
|
# for instructions on how to configure ActionMailer's SMTP server.
|
512
|
+
#
|
513
|
+
# Falls back to ::server_settings if ::smtp_settings doesn't exist for
|
514
|
+
# backwards compatibility.
|
455
515
|
|
456
|
-
def
|
457
|
-
ActionMailer::Base.server_settings
|
516
|
+
def smtp_settings
|
517
|
+
ActionMailer::Base.smtp_settings rescue ActionMailer::Base.server_settings
|
458
518
|
end
|
459
519
|
|
460
520
|
end
|
data/test/action_mailer.rb
CHANGED
@@ -10,6 +10,8 @@ class Net::SMTP
|
|
10
10
|
|
11
11
|
@send_message_block = nil
|
12
12
|
|
13
|
+
@start_block = nil
|
14
|
+
|
13
15
|
class << self
|
14
16
|
|
15
17
|
attr_reader :deliveries
|
@@ -21,6 +23,7 @@ class Net::SMTP
|
|
21
23
|
end
|
22
24
|
|
23
25
|
def self.start(*args)
|
26
|
+
@start_block.call if @start_block
|
24
27
|
yield new(nil)
|
25
28
|
end
|
26
29
|
|
@@ -28,8 +31,13 @@ class Net::SMTP
|
|
28
31
|
@send_message_block = block
|
29
32
|
end
|
30
33
|
|
34
|
+
def self.on_start(&block)
|
35
|
+
@start_block = block
|
36
|
+
end
|
37
|
+
|
31
38
|
def self.reset
|
32
39
|
deliveries.clear
|
40
|
+
on_start
|
33
41
|
on_send_message
|
34
42
|
@reset_called = 0
|
35
43
|
end
|
@@ -105,11 +113,27 @@ class Email
|
|
105
113
|
class << self; attr_accessor :records, :id; end
|
106
114
|
|
107
115
|
def self.create(record)
|
108
|
-
record = new record[:from], record[:to], record[:mail]
|
116
|
+
record = new record[:from], record[:to], record[:mail],
|
117
|
+
record[:last_send_attempt]
|
109
118
|
records << record
|
110
119
|
return record
|
111
120
|
end
|
112
121
|
|
122
|
+
def self.destroy_all(conditions)
|
123
|
+
timeout = conditions.last
|
124
|
+
found = []
|
125
|
+
|
126
|
+
records.each do |record|
|
127
|
+
next if record.last_send_attempt == 0
|
128
|
+
next if record.created_on == 0
|
129
|
+
next unless record.created_on < timeout
|
130
|
+
record.destroy
|
131
|
+
found << record
|
132
|
+
end
|
133
|
+
|
134
|
+
found
|
135
|
+
end
|
136
|
+
|
113
137
|
def self.find(_, conditions = nil)
|
114
138
|
return records if conditions.nil?
|
115
139
|
now = Time.now.to_i - 300
|
@@ -123,13 +147,13 @@ class Email
|
|
123
147
|
records.clear
|
124
148
|
end
|
125
149
|
|
126
|
-
def initialize(from, to, mail)
|
150
|
+
def initialize(from, to, mail, last_send_attempt = nil)
|
127
151
|
@from = from
|
128
152
|
@to = to
|
129
153
|
@mail = mail
|
130
154
|
@id = self.class.id += 1
|
131
155
|
@created_on = START + @id
|
132
|
-
@last_send_attempt = 0
|
156
|
+
@last_send_attempt = last_send_attempt || 0
|
133
157
|
end
|
134
158
|
|
135
159
|
def destroy
|
data/test/test_arsendmail.rb
CHANGED
@@ -4,7 +4,13 @@ require 'action_mailer/ar_sendmail'
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'test/zentest_assertions'
|
6
6
|
|
7
|
-
|
7
|
+
class ActionMailer::ARSendmail
|
8
|
+
attr_accessor :slept
|
9
|
+
def sleep(secs)
|
10
|
+
@slept ||= []
|
11
|
+
@slept << secs
|
12
|
+
end
|
13
|
+
end
|
8
14
|
|
9
15
|
class TestARSendmail < Test::Unit::TestCase
|
10
16
|
|
@@ -42,7 +48,7 @@ class AddMail < ActiveRecord::Migration
|
|
42
48
|
end
|
43
49
|
|
44
50
|
def self.down
|
45
|
-
drop_table :
|
51
|
+
drop_table :mail
|
46
52
|
end
|
47
53
|
end
|
48
54
|
EOF
|
@@ -206,6 +212,17 @@ Last send attempt: Thu Aug 10 11:40:05 -0700 2006
|
|
206
212
|
assert_equal true, options[:MailQ]
|
207
213
|
end
|
208
214
|
|
215
|
+
def test_class_parse_args_max_age
|
216
|
+
options = ActionMailer::ARSendmail.process_args []
|
217
|
+
assert_equal 86400 * 7, options[:MaxAge]
|
218
|
+
|
219
|
+
argv = %w[--max-age 86400]
|
220
|
+
|
221
|
+
options = ActionMailer::ARSendmail.process_args argv
|
222
|
+
|
223
|
+
assert_equal 86400, options[:MaxAge]
|
224
|
+
end
|
225
|
+
|
209
226
|
def test_class_parse_args_migration
|
210
227
|
options = ActionMailer::ARSendmail.process_args []
|
211
228
|
deny_includes :Migration, options
|
@@ -318,6 +335,39 @@ Last send attempt: Thu Aug 10 11:40:05 -0700 2006
|
|
318
335
|
assert_equal "hi\n\nopts\n", err.string
|
319
336
|
end
|
320
337
|
|
338
|
+
def test_cleanup
|
339
|
+
e1 = Email.create :mail => 'body', :to => 'to', :from => 'from'
|
340
|
+
e1.created_on = Time.now
|
341
|
+
e2 = Email.create :mail => 'body', :to => 'to', :from => 'from'
|
342
|
+
e3 = Email.create :mail => 'body', :to => 'to', :from => 'from'
|
343
|
+
e3.last_send_attempt = Time.now
|
344
|
+
|
345
|
+
out, err = util_capture do
|
346
|
+
@sm.cleanup
|
347
|
+
end
|
348
|
+
|
349
|
+
assert_equal '', out.string
|
350
|
+
assert_equal "expired 1 emails from the queue\n", err.string
|
351
|
+
assert_equal 2, Email.records.length
|
352
|
+
|
353
|
+
assert_equal [e1, e2], Email.records
|
354
|
+
end
|
355
|
+
|
356
|
+
def test_cleanup_disabled
|
357
|
+
e1 = Email.create :mail => 'body', :to => 'to', :from => 'from'
|
358
|
+
e1.created_on = Time.now
|
359
|
+
e2 = Email.create :mail => 'body', :to => 'to', :from => 'from'
|
360
|
+
|
361
|
+
@sm.max_age = 0
|
362
|
+
|
363
|
+
out, err = util_capture do
|
364
|
+
@sm.cleanup
|
365
|
+
end
|
366
|
+
|
367
|
+
assert_equal '', out.string
|
368
|
+
assert_equal 2, Email.records.length
|
369
|
+
end
|
370
|
+
|
321
371
|
def test_deliver
|
322
372
|
email = Email.create :mail => 'body', :to => 'to', :from => 'from'
|
323
373
|
|
@@ -334,6 +384,61 @@ Last send attempt: Thu Aug 10 11:40:05 -0700 2006
|
|
334
384
|
assert_equal "sent email 00000000001 from from to to: \"queued\"\n", err.string
|
335
385
|
end
|
336
386
|
|
387
|
+
def test_deliver_auth_error
|
388
|
+
Net::SMTP.on_start do
|
389
|
+
e = Net::SMTPAuthenticationError.new 'try again'
|
390
|
+
e.set_backtrace %w[one two three]
|
391
|
+
raise e
|
392
|
+
end
|
393
|
+
|
394
|
+
now = Time.now.to_i
|
395
|
+
|
396
|
+
email = Email.create :mail => 'body', :to => 'to', :from => 'from'
|
397
|
+
|
398
|
+
out, err = util_capture do
|
399
|
+
@sm.deliver [email]
|
400
|
+
end
|
401
|
+
|
402
|
+
assert_equal 0, Net::SMTP.deliveries.length
|
403
|
+
assert_equal 1, Email.records.length
|
404
|
+
assert_equal 0, Email.records.first.last_send_attempt
|
405
|
+
assert_equal 0, Net::SMTP.reset_called
|
406
|
+
assert_equal 1, @sm.failed_auth_count
|
407
|
+
assert_equal [60], @sm.slept
|
408
|
+
|
409
|
+
assert_equal '', out.string
|
410
|
+
assert_equal "authentication error, retrying: try again\n", err.string
|
411
|
+
end
|
412
|
+
|
413
|
+
def test_deliver_auth_error_recover
|
414
|
+
email = Email.create :mail => 'body', :to => 'to', :from => 'from'
|
415
|
+
@sm.failed_auth_count = 1
|
416
|
+
|
417
|
+
out, err = util_capture do @sm.deliver [email] end
|
418
|
+
|
419
|
+
assert_equal 0, @sm.failed_auth_count
|
420
|
+
assert_equal 1, Net::SMTP.deliveries.length
|
421
|
+
end
|
422
|
+
|
423
|
+
def test_deliver_auth_error_twice
|
424
|
+
Net::SMTP.on_start do
|
425
|
+
e = Net::SMTPAuthenticationError.new 'try again'
|
426
|
+
e.set_backtrace %w[one two three]
|
427
|
+
raise e
|
428
|
+
end
|
429
|
+
|
430
|
+
@sm.failed_auth_count = 1
|
431
|
+
|
432
|
+
out, err = util_capture do
|
433
|
+
assert_raise Net::SMTPAuthenticationError do
|
434
|
+
@sm.deliver []
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
assert_equal 2, @sm.failed_auth_count
|
439
|
+
assert_equal "authentication error, giving up: try again\n", err.string
|
440
|
+
end
|
441
|
+
|
337
442
|
def test_deliver_4xx_error
|
338
443
|
Net::SMTP.on_send_message do
|
339
444
|
e = Net::SMTPSyntaxError.new 'try again'
|
@@ -422,6 +527,7 @@ Last send attempt: Thu Aug 10 11:40:05 -0700 2006
|
|
422
527
|
assert_equal 1, Email.records.length
|
423
528
|
assert_operator now, :>=, Email.records.first.last_send_attempt
|
424
529
|
assert_equal 0, Net::SMTP.reset_called, 'Reset connection on SyntaxError'
|
530
|
+
assert_equal [60], @sm.slept
|
425
531
|
|
426
532
|
assert_equal '', out.string
|
427
533
|
assert_equal "server too busy, sleeping 60 seconds\n", err.string
|
@@ -522,10 +628,10 @@ Last send attempt: Thu Aug 10 11:40:05 -0700 2006
|
|
522
628
|
assert_equal "found 3 emails to send\n", err.string
|
523
629
|
end
|
524
630
|
|
525
|
-
def
|
631
|
+
def test_smtp_settings
|
526
632
|
ActionMailer::Base.server_settings[:address] = 'localhost'
|
527
633
|
|
528
|
-
assert_equal 'localhost', @sm.
|
634
|
+
assert_equal 'localhost', @sm.smtp_settings[:address]
|
529
635
|
end
|
530
636
|
|
531
637
|
def nobody
|
metadata
CHANGED
@@ -1,22 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4
|
2
|
+
rubygems_version: 0.9.4.3
|
3
3
|
specification_version: 1
|
4
4
|
name: ar_mailer
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2007-
|
6
|
+
version: 1.3.1
|
7
|
+
date: 2007-07-31 00:00:00 -07:00
|
8
8
|
summary: A two-phase delivery agent for ActionMailer
|
9
9
|
require_paths:
|
10
10
|
- lib
|
11
11
|
email: drbrain@segment7.net
|
12
12
|
homepage: http://seattlerb.org/ar_mailer
|
13
13
|
rubyforge_project: seattlerb
|
14
|
-
description: Even
|
14
|
+
description: Even delivering email to the local machine may take too long when you have to send hundreds of messages. ar_mailer allows you to store messages into the database for later delivery by a separate process, ar_sendmail.
|
15
15
|
autorequire:
|
16
16
|
default_executable:
|
17
17
|
bindir: bin
|
18
18
|
has_rdoc: true
|
19
|
-
required_ruby_version: !ruby/object:Gem::
|
19
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
20
26
|
requirements:
|
21
27
|
- - ">"
|
22
28
|
- !ruby/object:Gem::Version
|
@@ -63,9 +69,9 @@ dependencies:
|
|
63
69
|
- !ruby/object:Gem::Dependency
|
64
70
|
name: hoe
|
65
71
|
version_requirement:
|
66
|
-
version_requirements: !ruby/object:Gem::
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
73
|
requirements:
|
68
74
|
- - ">="
|
69
75
|
- !ruby/object:Gem::Version
|
70
|
-
version: 1.2.
|
76
|
+
version: 1.2.2
|
71
77
|
version:
|