roda 2.22.0 → 2.23.0

Sign up to get free protection for your applications and to get access to all the features.
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