padrino-mailer 0.16.2 → 0.17.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6765495811d426407ef6a517df0623de4cd979a957b4e8eb889ef8cf6b8e4c2
4
- data.tar.gz: 9fa8ad265be86fe4dbad73c31bbeb99824a45d16d8647be6fd2f4425a3a40c73
3
+ metadata.gz: 825e119978637255f86e4e8a3cbe59129020a951480750cd4d292731a112708c
4
+ data.tar.gz: 4cd8ae7354cfffabf9afa6a6b9b68158d6d37acf65b7053a79ca232ee208ad47
5
5
  SHA512:
6
- metadata.gz: 6e8048c51f82f9bd08609467bb9ef7679595101f00d5b22e9a2be687ab7af0c05e3cdc1bc5fdc57f800b5da8ab59a3a37376b4f9da45f6ceb6adc05db0589004
7
- data.tar.gz: cc6c06b2166b891aac8d1f24224b20bca3c05ddd159efc9082596d30cf7ee4a0998512c69f5720a5f7b8623d15abbfbabf568eb3b955981185dbd88bec329ff5
6
+ metadata.gz: ce4c8d3e8abb2b464939852ffc204a7fa1798daefb11b0119b7a642bd88b293d41f3c6b31d0fd357ce0b2e33c6688af2bdc96afea3e642611ff023283f9f8263
7
+ data.tar.gz: bca72b42a36c242cb4cd27662b5e386ffda7e027d46f2b0d14a762ac3477b22afb1530472716e7cf93e8024f4a1d07c10d84fa72c2246cfc1e4647684eaf8a24
@@ -5,18 +5,19 @@ module Mail # @private
5
5
  include Sinatra::Templates
6
6
  include Padrino::Rendering if defined?(Padrino::Rendering)
7
7
  include Padrino::Helpers::RenderHelpers if defined? Padrino::Helpers::RenderHelpers
8
+ include Padrino::Helpers if defined?(Padrino::Helpers)
8
9
  attr_reader :template_cache
9
10
  attr_accessor :mailer_name, :message_name
10
11
 
11
12
  def initialize_with_app(*args, &block)
12
- @template_cache = Tilt::Cache.new
13
+ @template_cache = Sinatra::TemplateCache.new
13
14
  # Check if we have an app passed into initialize
14
15
  if args[0].respond_to?(:views) && args[0].respond_to?(:reload_templates?)
15
16
  app = args.shift
16
- settings.views = File.join(app.views, 'mailers')
17
+ @_views = File.join(app.views, 'mailers')
17
18
  settings.reload_templates = app.reload_templates?
18
19
  else
19
- settings.views = File.expand_path('./mailers')
20
+ @_views = File.expand_path('./mailers')
20
21
  settings.reload_templates = true
21
22
  end
22
23
 
@@ -107,7 +108,7 @@ module Mail # @private
107
108
  def part(params = {}, &block)
108
109
  part_block = params.delete(:part_block)
109
110
  new_part = Mail::Part.new(params)
110
- new_part.settings.views = settings.views
111
+ new_part.views(@_views)
111
112
  new_part.settings.reload_templates = settings.reload_templates?
112
113
  new_part.instance_eval(&part_block) if part_block
113
114
  yield new_part if block_given?
@@ -137,14 +138,17 @@ module Mail # @private
137
138
  # Sinatra almost compatibility.
138
139
  #
139
140
  def self.set(name, value)
140
- self.class.instance_eval { define_method(name) { value } unless method_defined?(:erb) }
141
+ singleton_class.class_eval do
142
+ define_method(name) { value } unless method_defined?(name)
143
+ end
141
144
  end
142
145
 
143
146
  ##
144
147
  # Sets the message defined template path to the given view path.
145
148
  #
146
149
  def views(value)
147
- settings.views = value
150
+ @_views = value
151
+ @_mailer_views = false
148
152
  end
149
153
 
150
154
  ##
@@ -211,6 +215,21 @@ module Mail # @private
211
215
  @_defaults.each_pair { |k, v| default(k.to_sym, v) } if @_defaults.is_a?(Hash)
212
216
  end
213
217
 
218
+ ##
219
+ # Emails have no session to check a token against, so forms in mailer
220
+ # templates are built without a CSRF token.
221
+ #
222
+ def self.protect_from_csrf
223
+ false
224
+ end
225
+
226
+ ##
227
+ # The form builder form_for and fields_for use in mailer templates.
228
+ #
229
+ def self.default_builder
230
+ 'StandardFormBuilder'
231
+ end
232
+
214
233
  ##
215
234
  # Check if we can log.
216
235
  #
@@ -259,28 +278,55 @@ module Mail # @private
259
278
  locals = @_locals || {} if !options[:locals] && locals.empty?
260
279
  @template_cache.clear if settings.reload_templates?
261
280
 
281
+ # render(nil, 'template', ...) as RenderHelpers#partial calls it: shift the arguments
282
+ # the way Padrino::Rendering#render does, before defaulting to the message name.
283
+ engine, data, options = data, options, locals if engine.nil? && data && !data.is_a?(Hash)
262
284
  engine ||= message_name
263
285
 
264
286
  if mailer_name && !engine.to_s.index('/')
265
- settings.views += "/#{mailer_name}" unless settings.views.include?("/#{mailer_name}")
287
+ @_views = File.join(@_views, mailer_name.to_s) unless @_mailer_views
288
+ @_mailer_views = true
266
289
  engine = engine.to_s.sub(%r{^#{mailer_name}/}, '')
267
290
  end
268
291
 
269
- provides.each do |format|
270
- part do |p|
271
- p.content_type(format)
272
- p.send(:render, engine, data, options, locals, &block)
273
- add_multipart_alternate_header if html_part || provides.include?(:html)
274
- end
292
+ # Pass the views path per render: class-level settings are shared by every message.
293
+ if data.is_a?(Hash)
294
+ data = { views: @_views }.merge(data)
295
+ else
296
+ options = { views: @_views }.merge(options)
275
297
  end
276
298
 
277
- self.body = super(engine, data, options, locals, &block) if provides.empty?
299
+ # A render nested in a template (a partial) returns its output; only the outermost one sets the body.
300
+ return super(engine, data, options, locals, &block) if @_rendering
301
+
302
+ begin
303
+ @_rendering = true
304
+ provides.each do |format|
305
+ part do |p|
306
+ p.content_type(format)
307
+ p.send(:render, engine, data, options, locals, &block)
308
+ add_multipart_alternate_header if html_part || provides.include?(:html)
309
+ end
310
+ end
311
+
312
+ self.body = super(engine, data, options, locals, &block) if provides.empty?
313
+ ensure
314
+ @_rendering = false
315
+ end
278
316
  end
279
317
 
280
318
  alias original_partial partial if instance_methods.include?(:partial)
281
319
  def partial(template, options = {}, &block)
282
320
  raise "gem 'padrino-helpers' is required to render partials" unless respond_to?(:original_partial)
283
- self.body = original_partial(template, options, &block)
321
+ return original_partial(template, options, &block) if @_rendering
322
+
323
+ begin
324
+ @_rendering = true
325
+ output = original_partial(template, options, &block)
326
+ ensure
327
+ @_rendering = false
328
+ end
329
+ self.body = output
284
330
  end
285
331
 
286
332
  ##
@@ -37,12 +37,13 @@ class PadrinoApp < Padrino::Application
37
37
  end
38
38
 
39
39
  message :helper do |name|
40
- subject 'Welcome Helper!'
41
- to 'jim@fake.com'
42
- from 'noreply@custom.com'
43
- locals name: name
44
- via :test
45
- render 'sample/helper_message'
40
+ subject 'Welcome Helper!'
41
+ to 'jim@fake.com'
42
+ from 'noreply@custom.com'
43
+ content_type :html
44
+ locals name: name
45
+ via :test
46
+ render 'sample/helper_message'
46
47
  end
47
48
 
48
49
  message :default_mailer_name do |name|
data/test/test_email.rb CHANGED
@@ -158,4 +158,133 @@ describe 'Email' do
158
158
  end
159
159
  end
160
160
  end
161
+
162
+ describe 'the mailer views path' do
163
+ def mailer_views(root)
164
+ views = File.join(@tmp, root, 'views')
165
+ { 'alpha/hello.erb' => 'alpha hello', 'beta/hello.erb' => 'beta hello',
166
+ 'gamma/hello.plain.erb' => 'gamma plain', 'gamma/hello.html.erb' => 'gamma html' }.each do |path, content|
167
+ file = File.join(views, 'mailers', path)
168
+ FileUtils.mkdir_p(File.dirname(file))
169
+ File.write(file, content)
170
+ end
171
+ views
172
+ end
173
+
174
+ def mailer_app(views)
175
+ mock_app do
176
+ register Padrino::Mailer
177
+ set :views, views
178
+ set :delivery_method, :test
179
+ set :mailer_defaults, from: 'padrino@me.com', to: 'padrino@you.com'
180
+ mailer(:alpha) { email(:hello) { via :test; render :hello } }
181
+ mailer(:beta) { email(:hello) { via :test; render :hello } }
182
+ mailer(:gamma) { email(:hello) { via :test; provides :plain, :html; render :hello } }
183
+ end
184
+ end
185
+
186
+ def build_message(mailer_name)
187
+ message = @app.settings._padrino_mailer::Message.new(@app)
188
+ message.mailer_name = mailer_name
189
+ message
190
+ end
191
+
192
+ before { @tmp = Dir.mktmpdir }
193
+ after { FileUtils.rm_rf(@tmp) }
194
+
195
+ it 'should add the mailer name when the app path contains it' do
196
+ mailer_app(mailer_views('beta_site'))
197
+ email = @app.deliver(:beta, :hello)
198
+ assert_equal 'beta hello', email.body.to_s
199
+ end
200
+
201
+ it 'should not leak the mailer path between messages' do
202
+ mailer_app(mailer_views('site'))
203
+ alpha = build_message(:alpha)
204
+ beta = build_message(:beta)
205
+ assert_equal 'beta hello', beta.send(:render, :hello)
206
+ assert_equal 'alpha hello', alpha.send(:render, :hello)
207
+ end
208
+
209
+ it 'should not reuse a cached template from another mailer' do
210
+ views = mailer_views('site')
211
+ File.write(File.join(views, 'mailers', 'beta', 'greet.haml'), '%p beta greet')
212
+ File.write(File.join(views, 'mailers', 'alpha', 'greet.erb'), 'alpha greet')
213
+ mailer_app(views)
214
+ @app.set :reload_templates, false
215
+ @app.mailer(:alpha) { email(:greet) { via :test; render :greet } }
216
+ @app.mailer(:beta) { email(:greet) { via :test; render :greet } }
217
+ assert_equal 'alpha greet', @app.deliver(:alpha, :greet).body.to_s
218
+ assert_equal '<p>beta greet</p>', @app.deliver(:beta, :greet).body.to_s.strip
219
+ end
220
+
221
+ it 'should render a partial from a mailer template' do
222
+ views = mailer_views('site')
223
+ File.write(File.join(views, 'mailers', 'alpha', 'listing.erb'), "<%= partial 'item', object: 'one' %>")
224
+ File.write(File.join(views, 'mailers', 'alpha', '_item.erb'), 'item <%= item %>')
225
+ mailer_app(views)
226
+ @app.mailer(:alpha) { email(:listing) { via :test; render :listing } }
227
+ assert_equal 'item one', @app.deliver(:alpha, :listing).body.to_s.strip
228
+ end
229
+
230
+ it 'should render the message template with options when the engine is nil' do
231
+ views = mailer_views('site')
232
+ File.write(File.join(views, 'mailers', 'alpha', 'boxed.erb'), 'boxed')
233
+ File.write(File.join(views, 'mailers', 'alpha', 'box.erb'), '[<%= yield %>]')
234
+ mailer_app(views)
235
+ @app.mailer(:alpha) { email(:boxed) { via :test; render nil, layout: :box } }
236
+ assert_equal '[boxed]', @app.deliver(:alpha, :boxed).body.to_s.strip
237
+ end
238
+
239
+ it 'should not add a part per partial to a message with an attachment' do
240
+ views = mailer_views('site')
241
+ File.write(File.join(views, 'mailers', 'alpha', 'listing.erb'), "<%= partial 'item', collection: %w[a b] %>")
242
+ File.write(File.join(views, 'mailers', 'alpha', '_item.erb'), '<%= item %>')
243
+ mailer_app(views)
244
+ @app.mailer(:alpha) { email(:listing) { via :test; add_file filename: 'a.txt', content: 'file'; render :listing } }
245
+ email = @app.deliver(:alpha, :listing)
246
+ assert_equal(%w[ab file], email.parts.map { |part| part.body.to_s.strip })
247
+ end
248
+
249
+ it 'should not reuse a cached layout from another mailer' do
250
+ views = mailer_views('site')
251
+ File.write(File.join(views, 'mailers', 'alpha', 'box.erb'), 'A[<%= yield %>]')
252
+ FileUtils.mkdir_p(File.join(views, 'mailers', 'beta', 'layouts'))
253
+ File.write(File.join(views, 'mailers', 'beta', 'layouts', 'box.erb'), 'B[<%= yield %>]')
254
+ mailer_app(views)
255
+ @app.set :reload_templates, false
256
+ @app.mailer(:alpha) { email(:boxed) { via :test; render :hello, layout: :box } }
257
+ @app.mailer(:beta) { email(:boxed) { via :test; render :hello, layout: :box } }
258
+ assert_equal 'A[alpha hello]', @app.deliver(:alpha, :boxed).body.to_s.strip
259
+ assert_equal 'B[beta hello]', @app.deliver(:beta, :boxed).body.to_s.strip
260
+ end
261
+
262
+ MailerFormUser = Struct.new(:name) unless defined?(MailerFormUser)
263
+
264
+ it 'should build forms with form_for in a mailer template' do
265
+ views = mailer_views('site')
266
+ File.write(File.join(views, 'mailers', 'alpha', 'signup.erb'), "<% @user = MailerFormUser.new('Joe') %><%= form_for(:user, '/signup') { |f| f.text_field(:name) } %>")
267
+ mailer_app(views)
268
+ @app.mailer(:alpha) { email(:signup) { via :test; render :signup } }
269
+ body = @app.deliver(:alpha, :signup).body.to_s
270
+ assert_match(%r{<form[^>]*action="/signup"}, body)
271
+ assert_match(/<input type="text" name="mailer_form_user\[name\]" value="Joe"/, body)
272
+ end
273
+
274
+ it 'should build forms in a mailer template without a CSRF token' do
275
+ views = mailer_views('site')
276
+ File.write(File.join(views, 'mailers', 'alpha', 'confirm.erb'), "<%= button_to 'Confirm', '/confirm' %>")
277
+ mailer_app(views)
278
+ @app.mailer(:alpha) { email(:confirm) { via :test; render :confirm } }
279
+ body = @app.deliver(:alpha, :confirm).body.to_s
280
+ assert_match(%r{<form[^>]*action="/confirm"}, body)
281
+ refute_match(/authenticity_token/, body)
282
+ end
283
+
284
+ it 'should render each provided format from the mailer path' do
285
+ mailer_app(mailer_views('site'))
286
+ email = @app.deliver(:gamma, :hello)
287
+ assert_equal(['gamma plain', 'gamma html'], email.parts.map { |part| part.body.to_s })
288
+ end
289
+ end
161
290
  end
data/test/test_message.rb CHANGED
@@ -166,4 +166,27 @@ describe 'Message' do
166
166
  assert_equal "Object 1<br>\nObject 2<br>\nObject &lt;evil&gt;<br>\nObject <good><br>", message.body.to_s.chomp
167
167
  end
168
168
  end
169
+
170
+ describe 'Mail::Message.set' do
171
+ it 'should define accessor methods for settings' do
172
+ Mail::Message.set(:_test_custom_setting, 'custom_value')
173
+ message = Mail::Message.new
174
+ assert_equal 'custom_value', message.settings._test_custom_setting
175
+ ensure
176
+ if Mail::Message.singleton_class.method_defined?(:_test_custom_setting)
177
+ Mail::Message.singleton_class.send(:remove_method, :_test_custom_setting)
178
+ end
179
+ end
180
+
181
+ it 'should not redefine an already defined method' do
182
+ Mail::Message.set(:_test_another_setting, 'first')
183
+ Mail::Message.set(:_test_another_setting, 'second')
184
+ message = Mail::Message.new
185
+ assert_equal 'first', message.settings._test_another_setting
186
+ ensure
187
+ if Mail::Message.singleton_class.method_defined?(:_test_another_setting)
188
+ Mail::Message.singleton_class.send(:remove_method, :_test_another_setting)
189
+ end
190
+ end
191
+ end
169
192
  end
@@ -102,12 +102,11 @@ describe 'PadrinoMailer' do
102
102
  end
103
103
 
104
104
  it 'should be able to deliver a basic email using Padrino::Helpers' do
105
- skip # FIXME
106
105
  post '/deliver/helper'
107
106
  assert_equal 'mail delivered', body
108
107
  assert_email_sent(to: 'jim@fake.com', from: 'noreply@custom.com',
109
108
  content_type: 'text/html',
110
- subject: 'Welcome Helper!', body: '<a href="#">jim</a>')
109
+ subject: 'Welcome Helper!', body: '<a href="#">Jim</a>')
111
110
  end
112
111
 
113
112
  it 'should fail with proper message if mailer is not registered' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: padrino-mailer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.2
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Padrino Team
@@ -19,14 +19,14 @@ dependencies:
19
19
  requirements:
20
20
  - - '='
21
21
  - !ruby/object:Gem::Version
22
- version: 0.16.2
22
+ version: 0.17.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - '='
28
28
  - !ruby/object:Gem::Version
29
- version: 0.16.2
29
+ version: 0.17.0
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: mime-types
32
32
  requirement: !ruby/object:Gem::Requirement