roda 2.22.0 → 2.23.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
  SHA1:
3
- metadata.gz: 6bf01ef06d46bc9ee153b2b737b44a19b6dc8e3f
4
- data.tar.gz: 32979a928ec7cc15994b5c1a937b8601946f6451
3
+ metadata.gz: c091dcf6b8bb44694c2ea16a6244e3534662e2ed
4
+ data.tar.gz: cff4ce05dd629994658d4470bf2d3f339b6c11c7
5
5
  SHA512:
6
- metadata.gz: d730f1511a4c437b8f5bc8e3df705a2286703277ab22a985e9d331de934c8c30810f657fe06510b928f731086d824fdbd2b1996833751085fef898ecd540507d
7
- data.tar.gz: 39d8d2b3c9b26f610d212b00ce087973030d17a1aadc0867baf0a613a1dfb9d1f54a8bc1792dad30802dcc305b1850cfab8a67052591c5699926389ab90ca591
6
+ metadata.gz: 4571d9602f0a17a330110bfcb4c319ff1c36476b16f8a488c0c081c5e651ddcbd8fe2df7c6d01f667dcc30445946f91131820e48fc22340c32a3465e21d5b756
7
+ data.tar.gz: d7d6fc717405752f1b4eabd14fc23587f4cf2d9c25db32ab6f9425f1a67cda19ea0cb84a0c42173f07cdf722b0c8ab0b962d74fb898fc47fd5ae9382406c7995
data/CHANGELOG CHANGED
@@ -1,3 +1,17 @@
1
+ = 2.23.0 (2017-02-24)
2
+
3
+ * Add :inherit_cache render plugin option, to create a copy of the cache for subclasses, instead of using an empty cache (jeremyevans)
4
+
5
+ * In development mode, default to :explicit_cache=>true, :cache=>true instead of :cache=>false (jeremyevans)
6
+
7
+ * Add :explicit_cache render plugin option, to only cache templates if the :cache option is given to render/view (jeremyevans)
8
+
9
+ * Add error_email_content method to error_email plugin (jeremyevans)
10
+
11
+ * Make error_email method in error_email plugin support non-exception arguments (jeremyevans)
12
+
13
+ * Make Roda.freeze in the static_routing plugin return self (jeremyevans)
14
+
1
15
  = 2.22.0 (2017-01-20)
2
16
 
3
17
  * Add support for :verbatim_string_matcher option, for making all string matchers match verbatim (jeremyevans)
data/doc/conventions.rdoc CHANGED
@@ -23,11 +23,11 @@ For a small application, the following directory layout is recommended:
23
23
  views/
24
24
 
25
25
  +app_name.rb+ should contain the Roda application, and should reflect the name of your application.
26
- So if your application is named +FooBar+, you should use +foo_bar.rb+.
26
+ So, if your application is named +FooBar+, you should use +foo_bar.rb+.
27
27
 
28
28
  +views/+ should contain your template files. This assumes you are using the +render+ plugin
29
29
  and server-side rendering. If you are creating a single page application and just serving
30
- JSON, then you won't need a +views+ directory. For small applications, all view files should
30
+ JSON, then you won't need a +views+ directory. For small applications, all view files should be
31
31
  in the +views+ directory.
32
32
 
33
33
  +public/+ should contain any static files that should be served directly by the webserver.
@@ -0,0 +1,29 @@
1
+ = New Features
2
+
3
+ * An :explicit_cache option has been added to the render plugin.
4
+ This is similar to the :cache=>false option, but instead of
5
+ disabling caching completely, this disables caching by default but
6
+ allows for explicit caching of templates by providing the :cache
7
+ option to view/render.
8
+
9
+ In development mode, Roda now defaults to :explicit_cache=>true
10
+ instead of :cache=>false.
11
+
12
+ * An :inherit_cache option has been added to the render plugin,
13
+ making subclasses of that class start with a dup of the template
14
+ cache, instead of starting with an empty template cache. This can
15
+ result in less memory used.
16
+
17
+ * Roda#error_email in the error_email plugin now accepts non-Exception
18
+ arguments (such as strings). This can be useful in conditions that
19
+ are errors you may want to notify about, where an exception hasn't
20
+ been raised.
21
+
22
+ * Roda#error_email_content has been added to the error_email plugin.
23
+ This can be used to create the email message, which can be delivered
24
+ via another mechanism, and may make testing easier.
25
+
26
+ = Other Improvements
27
+
28
+ * Roda.freeze in the static_routing plugin now returns self, fixing
29
+ code such as Roda.freeze.app.
data/lib/roda.rb CHANGED
@@ -29,6 +29,16 @@ class Roda
29
29
  def []=(key, value)
30
30
  @mutex.synchronize{@hash[key] = value}
31
31
  end
32
+
33
+ private
34
+
35
+ # Create a copy of the cache with a separate mutex.
36
+ def initialize_copy(other)
37
+ @mutex = Mutex.new
38
+ other.instance_variable_get(:@mutex).synchronize do
39
+ @hash = other.instance_variable_get(:@hash).dup
40
+ end
41
+ end
32
42
  end
33
43
 
34
44
  # Base class used for Roda requests. The instance methods for this
@@ -25,6 +25,8 @@ class Roda
25
25
  # The subject of the error email shows the exception class and message.
26
26
  # The body of the error email shows the backtrace of the error and the
27
27
  # request environment, as well the request params and session variables (if any).
28
+ # You can also call error_email with a plain string instead of an exception,
29
+ # in which case the string is used as the subject, and no backtrace is included.
28
30
  #
29
31
  # Note that emailing on every error as shown above is only appropriate
30
32
  # for low traffic web applications. For high traffic web applications,
@@ -38,7 +40,12 @@ class Roda
38
40
  :emailer=>lambda{|h| Net::SMTP.start(h[:host]){|s| s.send_message(h[:message], h[:from], h[:to])}},
39
41
  # :nocov:
40
42
  :default_headers=>lambda do |h, e|
41
- {'From'=>h[:from], 'To'=>h[:to], 'Subject'=>"#{h[:prefix]}#{e.class}: #{e.message}"}
43
+ subject = if e.respond_to?(:message)
44
+ "#{e.class}: #{e.message}"
45
+ else
46
+ e.to_s
47
+ end
48
+ {'From'=>h[:from], 'To'=>h[:to], 'Subject'=>"#{h[:prefix]}#{subject}"}
42
49
  end,
43
50
  :body=>lambda do |s, e|
44
51
  format = lambda{|h| h.map{|k, v| "#{k.inspect} => #{v.inspect}"}.sort.join("\n")}
@@ -47,14 +54,21 @@ class Roda
47
54
  message << <<END
48
55
  Path: #{s.request.path}
49
56
 
57
+ END
58
+ if e.respond_to?(:backtrace)
59
+ message << <<END
50
60
  Backtrace:
51
61
 
52
62
  #{e.backtrace.join("\n")}
63
+ END
64
+ end
53
65
 
66
+ message << <<END
54
67
  ENV:
55
68
 
56
69
  #{format[s.env]}
57
70
  END
71
+
58
72
  unless s.request.params.empty?
59
73
  message << <<END
60
74
 
@@ -91,15 +105,24 @@ END
91
105
  end
92
106
 
93
107
  module InstanceMethods
94
- # Send an email for the given error.
95
- def error_email(e)
108
+ # Send an email for the given error. +exception+ is usually an exception
109
+ # instance, but it can be a plain string which is used as the subject for
110
+ # the email.
111
+ def error_email(exception)
96
112
  email_opts = self.class.opts[:error_email].dup
97
- headers = email_opts[:default_headers].call(email_opts, e)
113
+ email_opts[:message] = error_email_content(exception)
114
+ email_opts[:emailer].call(email_opts)
115
+ end
116
+
117
+ # The content of the email to send, include the headers and the body.
118
+ # Takes the same argument as #error_email.
119
+ def error_email_content(exception)
120
+ email_opts = self.class.opts[:error_email]
121
+ headers = email_opts[:default_headers].call(email_opts, exception)
98
122
  headers = Hash[headers].merge!(email_opts[:headers])
99
123
  headers = headers.map{|k,v| "#{k}: #{v.gsub(/\r?\n/m, "\r\n ")}"}.sort.join("\r\n")
100
- body = email_opts[:body].call(self, e)
101
- email_opts[:message] = "#{headers}\r\n\r\n#{body}"
102
- email_opts[:emailer].call(email_opts)
124
+ body = email_opts[:body].call(self, exception)
125
+ "#{headers}\r\n\r\n#{body}"
103
126
  end
104
127
  end
105
128
  end
@@ -9,8 +9,8 @@ class Roda
9
9
  # and if the named route does handle the request, the response returned by
10
10
  # the named route will be returned.
11
11
  #
12
- # In addition, this also adds the +r.multi_route+ method, which will assume
13
- # check if the first segment in the path matches a named route, and dispatch
12
+ # In addition, this plugin adds the +r.multi_route+ method, which will check
13
+ # if the first segment in the path matches a named route, and dispatch
14
14
  # to that named route.
15
15
  #
16
16
  # Example:
@@ -48,9 +48,7 @@ class Roda
48
48
  #
49
49
  # :allowed_paths :: Set the template paths to allow if +:check_paths+ is true.
50
50
  # Defaults to the +:views+ directory.
51
- # :cache :: nil/false to not cache templates (useful for development), defaults
52
- # to true unless RACK_ENV is development to automatically use the
53
- # default template cache.
51
+ # :cache :: nil/false to disallow template caching completely.
54
52
  # :cache_class :: A class to use as the template cache instead of the default.
55
53
  # :check_paths :: Check template paths start with one of the entries in +:allowed_paths+,
56
54
  # and raise a RodaError if the path doesn't.
@@ -64,6 +62,11 @@ class Roda
64
62
  # :escaper :: Object used for escaping output of <tt><%= %></tt>, when :escape=>true is used,
65
63
  # overriding the default. If given, object should respond to +escape_xml+ with
66
64
  # a single argument and return an output string.
65
+ # :explicit_cache :: Only use the template cache if the :cache option is provided when rendering
66
+ # (useful for development). Defaults to true if RACK_ENV is development, allowing explicit
67
+ # caching of specific templates, but not caching by default.
68
+ # :inherit_cache :: Whether to create a dup of the cache in subclasses. The default is false, which
69
+ # starts subclasses with an empty cache.
67
70
  # :layout :: The base name of the layout file, defaults to 'layout'. This can be provided as a hash
68
71
  # with the :template or :inline options.
69
72
  # :layout_opts :: The options to use when rendering the layout, if different
@@ -165,7 +168,7 @@ class Roda
165
168
  opts[:allowed_paths] ||= [opts[:views]].freeze
166
169
  opts[:allowed_paths] = opts[:allowed_paths].map{|f| ::File.expand_path(f)}.uniq.freeze
167
170
 
168
- if opts.fetch(:cache, ENV['RACK_ENV'] != 'development')
171
+ if opts.fetch(:cache, true)
169
172
  if cache_class = opts[:cache_class]
170
173
  opts[:cache] = cache_class.new
171
174
  else
@@ -173,6 +176,8 @@ class Roda
173
176
  end
174
177
  end
175
178
 
179
+ opts[:explicit_cache] = ENV['RACK_ENV'] == 'development' unless opts.has_key?(:explicit_cache)
180
+
176
181
  opts[:layout_opts] = (opts[:layout_opts] || {}).dup
177
182
  opts[:layout_opts][:_is_layout] = true
178
183
 
@@ -231,7 +236,9 @@ class Roda
231
236
  opts = subclass.opts[:render] = subclass.opts[:render].dup
232
237
 
233
238
  if opts[:cache]
234
- if cache_class = opts[:cache_class]
239
+ opts[:cache] = if opts[:inherit_cache]
240
+ opts[:cache] = opts[:cache].dup
241
+ elsif cache_class = opts[:cache_class]
235
242
  opts[:cache] = cache_class.new
236
243
  else
237
244
  opts[:cache] = thread_safe_cache
@@ -285,7 +292,7 @@ class Roda
285
292
  # If caching templates, attempt to retrieve the template from the cache. Otherwise, just yield
286
293
  # to get the template.
287
294
  def cached_template(opts, &block)
288
- if (cache = render_opts[:cache]) && (key = opts[:cache_key])
295
+ if (!render_opts[:explicit_cache] || opts[:cache]) && (cache = render_opts[:cache]) && (key = opts[:cache_key])
289
296
  unless template = cache[key]
290
297
  template = cache[key] = yield
291
298
  end
@@ -56,9 +56,9 @@ class Roda
56
56
  module ClassMethods
57
57
  # Freeze the static route metadata when freezing the app.
58
58
  def freeze
59
- super
60
59
  opts[:static_routes].freeze
61
60
  opts[:static_routes].each_value(&:freeze)
61
+ super
62
62
  end
63
63
 
64
64
  # Duplicate static route metadata in subclass.
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 2
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 22
7
+ RodaMinorVersion = 23
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -7,7 +7,8 @@ describe "error_email plugin" do
7
7
  plugin :error_email, {:to=>'t', :from=>'f', :emailer=>lambda{|h| emails << h}}.merge(opts)
8
8
 
9
9
  route do |r|
10
- raise ArgumentError rescue error_email($!)
10
+ r.get('noerror'){error_email("Problem"); 'g'}
11
+ raise ArgumentError, 'bad foo' rescue error_email($!)
11
12
  'e'
12
13
  end
13
14
  end
@@ -23,10 +24,30 @@ describe "error_email plugin" do
23
24
  email[:to].must_equal 't'
24
25
  email[:from].must_equal 'f'
25
26
  email[:host].must_equal 'localhost'
26
- email[:message].must_match(/^Subject: ArgumentError/)
27
+ email[:message].must_match(/^Subject: ArgumentError: bad foo/)
27
28
  email[:message].must_match(/^Backtrace:$.+^ENV:$.+^"rack\.input" => .+^Params:$\s+^"b" => "c"$\s+^Session:$\s+^"d" => "e"$/m)
28
29
  end
29
30
 
31
+ it "have error_email method support string arguments" do
32
+ app
33
+ body('/noerror', 'rack.input'=>StringIO.new, 'QUERY_STRING'=>'b=c', 'rack.session'=>{'d'=>'e'}).must_equal 'g'
34
+ email[:to].must_equal 't'
35
+ email[:from].must_equal 'f'
36
+ email[:host].must_equal 'localhost'
37
+ email[:message].must_match(/^Subject: Problem/)
38
+ email[:message].must_match(/^ENV:$.+^"rack\.input" => .+^Params:$\s+^"b" => "c"$\s+^Session:$\s+^"d" => "e"$/m)
39
+ email[:message].wont_include('Backtrace')
40
+ end
41
+
42
+ it "supports error_email_content for the content of the email" do
43
+ app.route do |r|
44
+ raise ArgumentError, 'bad foo' rescue error_email_content($!)
45
+ end
46
+ b = body('rack.input'=>StringIO.new, 'QUERY_STRING'=>'b=c', 'rack.session'=>{'d'=>'e'})
47
+ b.must_match(/^Subject: ArgumentError: bad foo/)
48
+ b.must_match(/^Backtrace:$.+^ENV:$.+^"rack\.input" => .+^Params:$\s+^"b" => "c"$\s+^Session:$\s+^"d" => "e"$/m)
49
+ end
50
+
30
51
  it "uses :host option" do
31
52
  app(:host=>'foo.bar.com')
32
53
  body('rack.input'=>StringIO.new).must_equal 'e'
@@ -353,13 +353,13 @@ describe "render plugin" do
353
353
  body('/c').must_equal "3"
354
354
  end
355
355
 
356
- it "Default to :cache=>false in development mode" do
356
+ it "Default to :explicit_cache=>true in development mode" do
357
357
  with_rack_env('development') do
358
358
  app(:render){}
359
359
  end
360
- app.render_opts[:cache].must_be_nil
360
+ app.render_opts[:explicit_cache].must_equal true
361
361
  app(:render){}
362
- app.render_opts[:cache].wont_equal nil
362
+ app.render_opts[:explicit_cache].must_equal false
363
363
  end
364
364
 
365
365
  it "Support :cache=>false option to disable template caching" do
@@ -379,6 +379,57 @@ describe "render plugin" do
379
379
  app.render_opts[:cache][File.expand_path('spec/views/iv.erb')].wont_equal nil
380
380
  end
381
381
 
382
+ it "Support :cache=>false option to disable template caching even when :cache_key is given" do
383
+ app(:bare) do
384
+ plugin :render, :views=>"./spec/views"
385
+
386
+ route do |r|
387
+ @a = 'a'
388
+ r.is('a'){render('iv', :cache=>false, :cache_key=>:foo)}
389
+ render('iv', :cache_key=>:foo)
390
+ end
391
+ end
392
+
393
+ body('/a').strip.must_equal "a"
394
+ app.render_opts[:cache][:foo].must_be_nil
395
+ body('/b').strip.must_equal "a"
396
+ app.render_opts[:cache][:foo].wont_equal nil
397
+ end
398
+
399
+ it "Support :explicit_cache option to disable caching by default, but still allow caching on a per-call basis" do
400
+ app(:bare) do
401
+ plugin :render, :views=>"./spec/views", :explicit_cache=>true
402
+
403
+ route do |r|
404
+ @a = 'a'
405
+ r.is('a'){render('iv')}
406
+ render('iv', :cache=>true)
407
+ end
408
+ end
409
+
410
+ body('/a').strip.must_equal "a"
411
+ app.render_opts[:cache][File.expand_path('spec/views/iv.erb')].must_be_nil
412
+ body('/b').strip.must_equal "a"
413
+ app.render_opts[:cache][File.expand_path('spec/views/iv.erb')].wont_equal nil
414
+ end
415
+
416
+ it "Support :explicit_cache plugin option with :cache_key render option" do
417
+ app(:bare) do
418
+ plugin :render, :views=>"./spec/views", :explicit_cache=>true
419
+
420
+ route do |r|
421
+ @a = 'a'
422
+ r.is('a'){render('iv', :cache_key=>:foo)}
423
+ render('iv', :cache=>true, :cache_key=>:foo)
424
+ end
425
+ end
426
+
427
+ body('/a').strip.must_equal "a"
428
+ app.render_opts[:cache][:foo].must_be_nil
429
+ body('/b').strip.must_equal "a"
430
+ app.render_opts[:cache][:foo].wont_equal nil
431
+ end
432
+
382
433
  it "Support :cache=>true option to enable template caching when :template_block is used" do
383
434
  c = Class.new do
384
435
  def initialize(path, *, &block)
@@ -437,23 +488,36 @@ describe "render plugin" do
437
488
  body.must_equal "1-2"
438
489
  end
439
490
 
440
- it "render_opts inheritance" do
491
+ it "should dup render_opts when subclasses, including an empty cache" do
441
492
  c = Class.new(Roda)
442
493
  c.plugin :render
494
+ c.render_opts[:cache][:foo] = 1
495
+ sc = Class.new(c)
496
+
497
+ c.render_opts.wont_be_same_as(sc.render_opts)
498
+ c.render_opts[:cache].wont_be_same_as(sc.render_opts[:cache])
499
+ sc.render_opts[:cache][:foo].must_be_nil
500
+ end
501
+
502
+ it "should use same render_opts as superclass when inheriting if :inherit_cache option is used" do
503
+ c = Class.new(Roda)
504
+ c.plugin :render, :inherit_cache=>true
505
+ c.render_opts[:cache][:foo] = 1
443
506
  sc = Class.new(c)
444
507
 
445
508
  c.render_opts.wont_be_same_as(sc.render_opts)
446
509
  c.render_opts[:cache].wont_be_same_as(sc.render_opts[:cache])
510
+ sc.render_opts[:cache][:foo].must_equal 1
447
511
  end
448
512
 
449
- it "render plugin call should not override options" do
513
+ it "render plugin call should not override existing options" do
450
514
  c = Class.new(Roda)
451
515
  c.plugin :render, :layout=>:foo
452
516
  c.plugin :render
453
517
  c.render_opts[:layout].must_equal :foo
454
518
  end
455
519
 
456
- it "with caching disabled" do
520
+ it "should not use cache in subclass if caching disabled in superclass" do
457
521
  app(:bare) do
458
522
  plugin :render, :views=>"./spec/views", :cache=>false
459
523
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.22.0
4
+ version: 2.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-20 00:00:00.000000000 Z
11
+ date: 2017-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -202,6 +202,7 @@ extra_rdoc_files:
202
202
  - doc/release_notes/2.20.0.txt
203
203
  - doc/release_notes/2.21.0.txt
204
204
  - doc/release_notes/2.22.0.txt
205
+ - doc/release_notes/2.23.0.txt
205
206
  files:
206
207
  - CHANGELOG
207
208
  - MIT-LICENSE
@@ -228,6 +229,7 @@ files:
228
229
  - doc/release_notes/2.20.0.txt
229
230
  - doc/release_notes/2.21.0.txt
230
231
  - doc/release_notes/2.22.0.txt
232
+ - doc/release_notes/2.23.0.txt
231
233
  - doc/release_notes/2.3.0.txt
232
234
  - doc/release_notes/2.4.0.txt
233
235
  - doc/release_notes/2.5.0.txt