mail_form 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +105 -51
- data/Rakefile +1 -0
- data/lib/mail_form.rb +48 -32
- data/lib/mail_form/base.rb +6 -124
- data/lib/mail_form/delivery.rb +239 -0
- data/lib/mail_form/shim.rb +50 -0
- data/lib/mail_form/version.rb +1 -1
- data/lib/views/mail_form/contact.erb +38 -0
- data/test/mail_form_test.rb +190 -0
- data/test/{base_test.rb → resource_test.rb} +31 -7
- data/test/test_helper.rb +25 -8
- metadata +7 -9
- data/lib/mail_form/dsl.rb +0 -171
- data/lib/mail_form/errors.rb +0 -59
- data/lib/mail_form/notifier.rb +0 -48
- data/test/errors_test.rb +0 -85
- data/test/notifier_test.rb +0 -183
- data/views/mail_form/notifier/contact.erb +0 -30
@@ -0,0 +1,239 @@
|
|
1
|
+
module MailForm::Delivery
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
ACCESSORS = [ :mail_attributes, :mail_subject, :mail_captcha,
|
5
|
+
:mail_attachments, :mail_recipients, :mail_sender,
|
6
|
+
:mail_headers, :mail_template, :mail_appendable ]
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_inheritable_reader *ACCESSORS
|
10
|
+
protected *ACCESSORS
|
11
|
+
|
12
|
+
# Initialize arrays and hashes
|
13
|
+
write_inheritable_array :mail_captcha, []
|
14
|
+
write_inheritable_array :mail_appendable, []
|
15
|
+
write_inheritable_array :mail_attributes, []
|
16
|
+
write_inheritable_array :mail_attachments, []
|
17
|
+
|
18
|
+
headers({})
|
19
|
+
sender {|c| c.email }
|
20
|
+
subject{|c| c.class.model_name.human }
|
21
|
+
template 'contact'
|
22
|
+
|
23
|
+
before_create :not_spam?
|
24
|
+
after_create :deliver!
|
25
|
+
|
26
|
+
attr_accessor :request
|
27
|
+
alias :deliver :create
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
# Declare your form attributes. All attributes declared here will be appended
|
32
|
+
# to the e-mail, except the ones captcha is true.
|
33
|
+
#
|
34
|
+
# == Options
|
35
|
+
#
|
36
|
+
# * :validate - A hook to validates_*_of. When true is given, validates the
|
37
|
+
# presence of the attribute. When a regexp, validates format. When array,
|
38
|
+
# validates the inclusion of the attribute in the array.
|
39
|
+
#
|
40
|
+
# Whenever :validate is given, the presence is automatically checked. Give
|
41
|
+
# :allow_blank => true to override.
|
42
|
+
#
|
43
|
+
# Finally, when :validate is a symbol, the method given as symbol will be
|
44
|
+
# called. Then you can add validations as you do in ActiveRecord (errors.add).
|
45
|
+
#
|
46
|
+
# * <tt>:attachment</tt> - When given, expects a file to be sent and attaches
|
47
|
+
# it to the e-mail. Don't forget to set your form to multitype.
|
48
|
+
#
|
49
|
+
# * <tt>:captcha</tt> - When true, validates the attributes must be blank
|
50
|
+
# This is a simple way to avoid spam
|
51
|
+
#
|
52
|
+
# == Examples
|
53
|
+
#
|
54
|
+
# class ContactForm < MailForm
|
55
|
+
# attributes :name, :validate => true
|
56
|
+
# attributes :email, :validate => /^([^@]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
|
57
|
+
# attributes :type, :validate => ["General", "Interface bug"]
|
58
|
+
# attributes :message
|
59
|
+
# attributes :screenshot, :attachment => true, :validate => :interface_bug?
|
60
|
+
# attributes :nickname, :captcha => true
|
61
|
+
#
|
62
|
+
# def interface_bug?
|
63
|
+
# if type == 'Interface bug' && screenshot.nil?
|
64
|
+
# self.errors.add(:screenshot, "can't be blank when you are reporting an interface bug")
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
def attribute(*accessors)
|
70
|
+
options = accessors.extract_options!
|
71
|
+
attr_accessor *(accessors - instance_methods.map(&:to_sym))
|
72
|
+
|
73
|
+
if options[:attachment]
|
74
|
+
write_inheritable_array(:mail_attachments, accessors)
|
75
|
+
elsif options[:captcha]
|
76
|
+
write_inheritable_array(:mail_captcha, accessors)
|
77
|
+
else
|
78
|
+
write_inheritable_array(:mail_attributes, accessors)
|
79
|
+
end
|
80
|
+
|
81
|
+
validation = options.delete(:validate)
|
82
|
+
return unless validation
|
83
|
+
|
84
|
+
accessors.each do |accessor|
|
85
|
+
case validation
|
86
|
+
when Symbol, Class
|
87
|
+
validate validation
|
88
|
+
break
|
89
|
+
when Regexp
|
90
|
+
validates_format_of accessor, :with => validation, :allow_blank => true
|
91
|
+
when Array
|
92
|
+
validates_inclusion_of accessor, :in => validation, :allow_blank => true
|
93
|
+
when Range
|
94
|
+
validates_length_of accessor, :within => validation, :allow_blank => true
|
95
|
+
end
|
96
|
+
|
97
|
+
validates_presence_of accessor unless options[:allow_blank] == true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
alias :attributes :attribute
|
101
|
+
|
102
|
+
# Declares contact email sender. It can be a string or a proc or a symbol.
|
103
|
+
#
|
104
|
+
# When a symbol is given, it will call a method on the form object with
|
105
|
+
# the same name as the symbol. As a proc, it receives a simple form
|
106
|
+
# instance. By default is the class human name.
|
107
|
+
#
|
108
|
+
# == Examples
|
109
|
+
#
|
110
|
+
# class ContactForm < MailForm
|
111
|
+
# subject "My Contact Form"
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
def subject(duck=nil, &block)
|
115
|
+
write_inheritable_attribute(:mail_subject, duck || block)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Declares contact email sender. It can be a string or a proc or a symbol.
|
119
|
+
#
|
120
|
+
# When a symbol is given, it will call a method on the form object with
|
121
|
+
# the same name as the symbol. As a proc, it receives a simple form
|
122
|
+
# instance. By default is:
|
123
|
+
#
|
124
|
+
# sender{ |c| c.email }
|
125
|
+
#
|
126
|
+
# This requires that your MailForm object have an email attribute.
|
127
|
+
#
|
128
|
+
# == Examples
|
129
|
+
#
|
130
|
+
# class ContactForm < MailForm
|
131
|
+
# # Change sender to include also the name
|
132
|
+
# sender { |c| %{"#{c.name}" <#{c.email}>} }
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
def sender(duck=nil, &block)
|
136
|
+
write_inheritable_attribute(:mail_sender, duck || block)
|
137
|
+
end
|
138
|
+
alias :from :sender
|
139
|
+
|
140
|
+
# Who will receive the e-mail. Can be a string or array or a symbol or a proc.
|
141
|
+
#
|
142
|
+
# When a symbol is given, it will call a method on the form object with
|
143
|
+
# the same name as the symbol. As a proc, it receives a simple form instance.
|
144
|
+
#
|
145
|
+
# Both the proc and the symbol must return a string or an array. By default
|
146
|
+
# is nil.
|
147
|
+
#
|
148
|
+
# == Examples
|
149
|
+
#
|
150
|
+
# class ContactForm < MailForm
|
151
|
+
# recipients [ "first.manager@domain.com", "second.manager@domain.com" ]
|
152
|
+
# end
|
153
|
+
#
|
154
|
+
def recipients(duck=nil, &block)
|
155
|
+
write_inheritable_attribute(:mail_recipients, duck || block)
|
156
|
+
end
|
157
|
+
alias :to :recipients
|
158
|
+
|
159
|
+
# Additional headers to your e-mail.
|
160
|
+
#
|
161
|
+
# == Examples
|
162
|
+
#
|
163
|
+
# class ContactForm < MailForm
|
164
|
+
# headers { :content_type => 'text/html' }
|
165
|
+
# end
|
166
|
+
#
|
167
|
+
def headers(hash)
|
168
|
+
write_inheritable_hash(:mail_headers, hash)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Customized template for your e-mail, if you don't want to use default
|
172
|
+
# 'contact' template or need more than one contact form with different
|
173
|
+
# template layouts.
|
174
|
+
#
|
175
|
+
# When a symbol is given, it will call a method on the form object with
|
176
|
+
# the same name as the symbol. As a proc, it receives a simple form
|
177
|
+
# instance. Both method and proc must return a string with the template
|
178
|
+
# name. Defaults to 'contact'.
|
179
|
+
#
|
180
|
+
# == Examples
|
181
|
+
#
|
182
|
+
# class ContactForm < MailForm
|
183
|
+
# # look for a template in views/mail_form/notifier/my_template.erb
|
184
|
+
# template 'my_template'
|
185
|
+
# end
|
186
|
+
#
|
187
|
+
def template(new_template)
|
188
|
+
write_inheritable_attribute(:mail_template, new_template)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Values from request object to be appended to the contact form.
|
192
|
+
# Whenever used, you have to send the request object when initializing the object:
|
193
|
+
#
|
194
|
+
# @contact_form = ContactForm.new(params[:contact_form], request)
|
195
|
+
#
|
196
|
+
# You can get the values to be appended from the AbstractRequest
|
197
|
+
# documentation (http://api.rubyonrails.org/classes/ActionController/AbstractRequest.html)
|
198
|
+
#
|
199
|
+
# == Examples
|
200
|
+
#
|
201
|
+
# class ContactForm < MailForm
|
202
|
+
# append :remote_ip, :user_agent, :session, :cookies
|
203
|
+
# end
|
204
|
+
#
|
205
|
+
def append(*values)
|
206
|
+
write_inheritable_array(:mail_appendable, values)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# In development, raises an error if the captcha field is not blank. This is
|
211
|
+
# is good to remember that the field should be hidden with CSS and shown only
|
212
|
+
# to robots.
|
213
|
+
#
|
214
|
+
# In test and in production, it returns true if all captcha fields are blank,
|
215
|
+
# returns false otherwise.
|
216
|
+
#
|
217
|
+
def spam?
|
218
|
+
mail_captcha.each do |field|
|
219
|
+
next if send(field).blank?
|
220
|
+
|
221
|
+
if defined?(Rails) && Rails.env.development?
|
222
|
+
raise ScriptError, "The captcha field #{field} was supposed to be blank"
|
223
|
+
else
|
224
|
+
return true
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
false
|
229
|
+
end
|
230
|
+
|
231
|
+
def not_spam?
|
232
|
+
!spam?
|
233
|
+
end
|
234
|
+
|
235
|
+
# Deliver the resource without checking any condition.
|
236
|
+
def deliver!
|
237
|
+
MailForm.contact(self).deliver
|
238
|
+
end
|
239
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
# This the module which makes any class behave like ActiveModel.
|
4
|
+
module MailForm::Shim
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
extend ActiveModel::Naming
|
8
|
+
extend ActiveModel::Translation
|
9
|
+
extend ActiveModel::Callbacks
|
10
|
+
include ActiveModel::Validations
|
11
|
+
include ActiveModel::Conversion
|
12
|
+
|
13
|
+
extend MailForm::Shim::ClassMethods
|
14
|
+
define_model_callbacks :create
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def i18n_scope
|
20
|
+
:mail_form
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Initialize assigning the parameters given as hash (just as in ActiveRecord).
|
25
|
+
def initialize(params={})
|
26
|
+
params.each_pair do |attr, value|
|
27
|
+
self.send(:"#{attr}=", value)
|
28
|
+
end unless params.blank?
|
29
|
+
end
|
30
|
+
|
31
|
+
# Always return true so when using form_for, the default method will be post.
|
32
|
+
def new_record?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
# Always return nil so when using form_for, the default method will be post.
|
37
|
+
def id
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create just check validity, and if so, trigger callbacks.
|
42
|
+
def create
|
43
|
+
if valid?
|
44
|
+
_run_create_callbacks { true }
|
45
|
+
else
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
alias :save :create
|
50
|
+
end
|
data/lib/mail_form/version.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
<h4 style="text-decoration:underline"><%= mailer.subject %></h4>
|
2
|
+
|
3
|
+
<% @resource.class.mail_attributes.each do |attribute|
|
4
|
+
value = @resource.send(attribute)
|
5
|
+
next if value.blank? %>
|
6
|
+
|
7
|
+
<p><b><%= @resource.class.human_attribute_name(attribute) %>:</b>
|
8
|
+
<%= case value
|
9
|
+
when /\n/
|
10
|
+
raw(simple_format(h(value)))
|
11
|
+
when Time, DateTime, Date
|
12
|
+
I18n.l value
|
13
|
+
else
|
14
|
+
value
|
15
|
+
end
|
16
|
+
%></p>
|
17
|
+
<% end %>
|
18
|
+
|
19
|
+
<% unless @resource.class.mail_appendable.blank? %>
|
20
|
+
<br /><h4 style="text-decoration:underline"><%= I18n.t :title, :scope => [ :mail_form, :request ], :default => 'Request information' %></h4>
|
21
|
+
|
22
|
+
<% @resource.class.mail_appendable.each do |attribute|
|
23
|
+
value = @resource.request.send(attribute)
|
24
|
+
|
25
|
+
value = if value.is_a?(Hash) && !value.empty?
|
26
|
+
content_tag(:ul, value.to_a.map{|k,v| content_tag(:li, h("#{k}: #{v.inspect}")) }.join("\n"), :style => "list-style:none;")
|
27
|
+
elsif value.is_a?(String)
|
28
|
+
h(value)
|
29
|
+
else
|
30
|
+
h(value.inspect)
|
31
|
+
end
|
32
|
+
%>
|
33
|
+
|
34
|
+
<p><b><%= I18n.t attribute, :scope => [ :mail_form, :request ], :default => attribute.to_s.humanize %>:</b>
|
35
|
+
<%= value.include?("\n") ? raw(simple_format(value)) : value %></p>
|
36
|
+
<% end %>
|
37
|
+
<br />
|
38
|
+
<% end %>
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MailFormNotifierTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@form = ContactForm.new(:name => 'José', :email => 'my.email@my.domain.com', :message => 'Cool')
|
7
|
+
|
8
|
+
@request = ActionController::TestRequest.new
|
9
|
+
@valid_attributes = { :name => 'José', :email => 'my.email@my.domain.com', :message => "Cool\nno?" }
|
10
|
+
@advanced = AdvancedForm.new(@valid_attributes)
|
11
|
+
@advanced.request = @request
|
12
|
+
|
13
|
+
test_file = Rack::Test::UploadedFile.new(File.join(File.dirname(__FILE__), 'test_file.txt'))
|
14
|
+
@with_file = FileForm.new(:name => 'José', :email => 'my.email@my.domain.com', :message => "Cool", :file => test_file)
|
15
|
+
|
16
|
+
@template = TemplateForm.new(@valid_attributes)
|
17
|
+
|
18
|
+
ActionMailer::Base.deliveries = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_email_is_sent
|
22
|
+
@form.deliver
|
23
|
+
assert_equal 1, ActionMailer::Base.deliveries.size
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_subject_defaults_to_class_human_name
|
27
|
+
@form.deliver
|
28
|
+
assert_equal 'Contact form', first_delivery.subject
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_subject_is_a_string
|
32
|
+
@advanced.deliver
|
33
|
+
assert_equal 'My Advanced Form', first_delivery.subject
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_sender_defaults_to_form_email
|
37
|
+
@form.deliver
|
38
|
+
assert_equal ['my.email@my.domain.com'], first_delivery.from
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_error_is_raised_when_recipients_is_nil
|
42
|
+
assert_raise ScriptError do
|
43
|
+
NullRecipient.new.deliver
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_recipients_is_a_string
|
48
|
+
@form.deliver
|
49
|
+
assert_equal ['my.email@my.domain.com'], first_delivery.to
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_recipients_is_an_array
|
53
|
+
@advanced.deliver
|
54
|
+
assert_equal ['my.first@email.com', 'my.second@email.com'], first_delivery.to
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_recipients_is_a_symbold
|
58
|
+
@with_file.deliver
|
59
|
+
assert_equal ['contact_file@my.domain.com'], first_delivery.to
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_headers_is_a_hash
|
63
|
+
@advanced.deliver
|
64
|
+
assert_equal 'mypath', first_delivery.header['return-path'].to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_body_contains_subject
|
68
|
+
@form.deliver
|
69
|
+
assert_match /Contact form/, first_delivery.body.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_body_contains_attributes_values
|
73
|
+
@form.deliver
|
74
|
+
assert_match /José/, first_delivery.body.to_s
|
75
|
+
assert_match /my.email@my.domain.com/, first_delivery.body.to_s
|
76
|
+
assert_match /Cool/, first_delivery.body.to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_body_contains_attributes_names
|
80
|
+
@form.deliver
|
81
|
+
assert_match /Name:/, first_delivery.body.to_s
|
82
|
+
assert_match /Email:/, first_delivery.body.to_s
|
83
|
+
assert_match /Message:/, first_delivery.body.to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_body_contains_localized_attributes_names
|
87
|
+
I18n.backend.store_translations(:en, :mail_form => { :attributes => { :contact_form => { :message => 'Sent message' } } })
|
88
|
+
@form.deliver
|
89
|
+
assert_match /Sent message:/, first_delivery.body.to_s
|
90
|
+
assert_no_match /Message:/, first_delivery.body.to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_body_mail_format_messages_with_break_line
|
94
|
+
@form.deliver
|
95
|
+
assert_no_match /<p>Cool/, first_delivery.body.to_s
|
96
|
+
|
97
|
+
@advanced.deliver
|
98
|
+
assert_match /<p>Cool/, last_delivery.body.to_s
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_body_mail_format_dates_with_i18n
|
102
|
+
@form.deliver
|
103
|
+
assert_no_match /I18n.l(Date.today)/, first_delivery.body.to_s
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_body_does_not_append_request_if_append_is_not_called
|
107
|
+
@form.deliver
|
108
|
+
assert_no_match /Request information/, first_delivery.body.to_s
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_body_does_append_request_if_append_is_called
|
112
|
+
@advanced.deliver
|
113
|
+
assert_match /Request information/, last_delivery.body.to_s
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_request_title_is_localized
|
117
|
+
I18n.backend.store_translations(:en, :mail_form => { :request => { :title => 'Information about the request' } })
|
118
|
+
@advanced.deliver
|
119
|
+
assert_no_match /Request information/, last_delivery.body.to_s
|
120
|
+
assert_match /Information about the request/, last_delivery.body.to_s
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_request_info_attributes_are_printed
|
124
|
+
@advanced.deliver
|
125
|
+
assert_match /Remote ip/, last_delivery.body.to_s
|
126
|
+
assert_match /User agent/, last_delivery.body.to_s
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_request_info_attributes_are_localized
|
130
|
+
I18n.backend.store_translations(:en, :mail_form => { :request => { :remote_ip => 'IP Address' } })
|
131
|
+
@advanced.deliver
|
132
|
+
assert_match /IP Address/, last_delivery.body.to_s
|
133
|
+
assert_no_match /Remote ip/, last_delivery.body.to_s
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_request_info_values_are_printed
|
137
|
+
@advanced.deliver
|
138
|
+
assert_match /0\.0\.0\.0/, last_delivery.body.to_s
|
139
|
+
assert_match /Rails Testing/, last_delivery.body.to_s
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_request_info_hashes_are_print_inside_lis
|
143
|
+
@request.session = { :my => :session, :user => "data" }
|
144
|
+
@advanced.deliver
|
145
|
+
assert_match /<li>my: :session<\/li>/, last_delivery.body.to_s
|
146
|
+
assert_match /<li>user: "data"<\/li>/, last_delivery.body.to_s
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_error_is_raised_when_append_is_given_but_no_request_is_given
|
150
|
+
assert_raise ScriptError do
|
151
|
+
@advanced.request = nil
|
152
|
+
@advanced.deliver
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_form_with_file_includes_an_attachment
|
157
|
+
@with_file.deliver
|
158
|
+
assert_equal 1, first_delivery.attachments.size
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_form_with_file_does_not_output_attachment_as_attribute
|
162
|
+
@with_file.deliver
|
163
|
+
assert_no_match /File:/, first_delivery.body.to_s
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_form_with_customized_template_render_correct_template
|
167
|
+
begin
|
168
|
+
previous_view_path = MailForm.view_paths
|
169
|
+
MailForm.prepend_view_path File.join(File.dirname(__FILE__), 'views')
|
170
|
+
@template.deliver
|
171
|
+
assert_match 'Hello from my custom template!', last_delivery.body.to_s
|
172
|
+
ensure
|
173
|
+
MailForm.view_paths = previous_view_path
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
protected
|
178
|
+
|
179
|
+
def first_delivery
|
180
|
+
ActionMailer::Base.deliveries.first
|
181
|
+
end
|
182
|
+
|
183
|
+
def last_delivery
|
184
|
+
ActionMailer::Base.deliveries.last
|
185
|
+
end
|
186
|
+
|
187
|
+
def teardown
|
188
|
+
I18n.reload!
|
189
|
+
end
|
190
|
+
end
|