sinatra-sinatra 0.10.0 → 0.10.1

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.
data/CHANGES CHANGED
@@ -1,3 +1,12 @@
1
+ = 1.0 / unreleased
2
+
3
+ * Route handlers, before filters, templates, error mappings, and
4
+ middleware are now resolved dynamically up the inheritance hierarchy
5
+ when needed instead of duplicating the superclass's version when
6
+ a new Sinatra::Base subclass is created. This should fix a variety
7
+ of issues with extensions that need to add any of these things
8
+ to the base class.
9
+
1
10
  = 0.9.2 / 2009-05-18
2
11
 
3
12
  * This version is compatible with Rack 1.0. [Rein Henrichs]
@@ -470,6 +470,56 @@ recommended:
470
470
  NOTE: The built-in Sinatra::Test module and Sinatra::TestHarness class
471
471
  are deprecated as of the 0.9.2 release.
472
472
 
473
+ == Sinatra::Base - Middleware, Libraries, and Modular Apps
474
+
475
+ Defining your app at the top-level works well for micro-apps but has
476
+ considerable drawbacks when building reuseable components such as Rack
477
+ middleware, Rails metal, simple libraries with a server component, or
478
+ even Sinatra extensions. The top-level DSL pollutes the Object namespace
479
+ and assumes a micro-app style configuration (e.g., a single application
480
+ file, ./public and ./views directories, logging, exception detail page,
481
+ etc.). That's where Sinatra::Base comes into play:
482
+
483
+ require 'sinatra/base'
484
+
485
+ class MyApp < Sinatra::Base
486
+ set :sessions, true
487
+ set :foo, 'bar'
488
+
489
+ get '/' do
490
+ 'Hello world!'
491
+ end
492
+ end
493
+
494
+ The MyApp class is an independent Rack component that can act as
495
+ Rack middleware, a Rack application, or Rails metal. You can +use+ or
496
+ +run+ this class from a rackup +config.ru+ file; or, control a server
497
+ component shipped as a library:
498
+
499
+ MyApp.run! :host => 'localhost', :port => 9090
500
+
501
+ The methods available to Sinatra::Base subclasses are exactly as those
502
+ available via the top-level DSL. Most top-level apps can be converted to
503
+ Sinatra::Base components with two modifications:
504
+
505
+ * Your file should require +sinatra/base+ instead of +sinatra+;
506
+ otherwise, all of Sinatra's DSL methods are imported into the main
507
+ namespace.
508
+ * Put your app's routes, error handlers, filters, and options in a subclass
509
+ of Sinatra::Base.
510
+
511
+ +Sinatra::Base+ is a blank slate. Most options are disabled by default,
512
+ including the built-in server. See {Options and Configuration}[http://sinatra.github.com/configuration.html]
513
+ for details on available options and their behavior.
514
+
515
+ SIDEBAR: Sinatra's top-level DSL is implemented using a simple delegation
516
+ system. The +Sinatra::Application+ class -- a special subclass of
517
+ Sinatra::Base -- receives all :get, :put, :post, :delete, :before,
518
+ :error, :not_found, :configure, and :set messages sent to the
519
+ top-level. Have a look at the code for yourself: here's the
520
+ {Sinatra::Delegator mixin}[http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064]
521
+ being {included into the main namespace}[http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb#L25].
522
+
473
523
  == Command line
474
524
 
475
525
  Sinatra applications can be run directly:
@@ -6,7 +6,7 @@ require 'rack/builder'
6
6
  require 'sinatra/showexceptions'
7
7
 
8
8
  module Sinatra
9
- VERSION = '0.10.0'
9
+ VERSION = '0.10.1'
10
10
 
11
11
  # The request object. See Rack::Request for more info:
12
12
  # http://rack.rubyforge.org/doc/classes/Rack/Request.html
@@ -270,23 +270,33 @@ module Sinatra
270
270
  def lookup_template(engine, template, views_dir, filename = nil, line = nil)
271
271
  case template
272
272
  when Symbol
273
- if cached = self.class.templates[template]
274
- lookup_template(engine, cached[:template], views_dir, cached[:filename], cached[:line])
275
- else
276
- path = ::File.join(views_dir, "#{template}.#{engine}")
277
- [ ::File.read(path), path, 1 ]
278
- end
273
+ load_template(engine, template, views_dir, options)
279
274
  when Proc
280
275
  filename, line = self.class.caller_locations.first if filename.nil?
281
- [ template.call, filename, line.to_i ]
276
+ [template.call, filename, line.to_i]
282
277
  when String
283
278
  filename, line = self.class.caller_locations.first if filename.nil?
284
- [ template, filename, line.to_i ]
279
+ [template, filename, line.to_i]
285
280
  else
286
281
  raise ArgumentError
287
282
  end
288
283
  end
289
284
 
285
+ def load_template(engine, template, views_dir, options={})
286
+ base = self.class
287
+ while base.respond_to?(:templates)
288
+ if cached = base.templates[template]
289
+ return lookup_template(engine, cached[:template], views_dir, cached[:filename], cached[:line])
290
+ else
291
+ base = base.superclass
292
+ end
293
+ end
294
+
295
+ # If no template exists in the cache, try loading from disk.
296
+ path = ::File.join(views_dir, "#{template}.#{engine}")
297
+ [ ::File.read(path), path, 1 ]
298
+ end
299
+
290
300
  def lookup_layout(engine, template, views_dir)
291
301
  lookup_template(engine, template, views_dir)
292
302
  rescue Errno::ENOENT
@@ -357,7 +367,7 @@ module Sinatra
357
367
  @env = env
358
368
  @request = Request.new(env)
359
369
  @response = Response.new
360
- @params = nil
370
+ @params = indifferent_params(@request.params)
361
371
 
362
372
  invoke { dispatch! }
363
373
  invoke { error_block!(response.status) }
@@ -405,21 +415,15 @@ module Sinatra
405
415
  end
406
416
 
407
417
  private
408
- # Run before filters and then locate and run a matching route.
409
- def route!
410
- # enable nested params in Rack < 1.0; allow indifferent access
411
- @params =
412
- if Rack::Utils.respond_to?(:parse_nested_query)
413
- indifferent_params(@request.params)
414
- else
415
- nested_params(@request.params)
416
- end
417
-
418
- # before filters
419
- self.class.filters.each { |block| instance_eval(&block) }
418
+ # Run before filters defined on the class and all superclasses.
419
+ def filter!(base=self.class)
420
+ filter!(base.superclass) if base.superclass.respond_to?(:filters)
421
+ base.filters.each { |block| instance_eval(&block) }
422
+ end
420
423
 
421
- # routes
422
- if routes = self.class.routes[@request.request_method]
424
+ # Run routes defined on the class and all superclasses.
425
+ def route!(base=self.class)
426
+ if routes = base.routes[@request.request_method]
423
427
  original_params = @params
424
428
  path = unescape(@request.path_info)
425
429
 
@@ -451,6 +455,14 @@ module Sinatra
451
455
  end
452
456
  end
453
457
  end
458
+
459
+ @params = original_params
460
+ end
461
+
462
+ # Run routes defined in superclass.
463
+ if base.superclass.respond_to?(:routes)
464
+ route! base.superclass
465
+ return
454
466
  end
455
467
 
456
468
  route_missing
@@ -474,6 +486,19 @@ module Sinatra
474
486
  end
475
487
  end
476
488
 
489
+ # Attempt to serve static files from public directory. Throws :halt when
490
+ # a matching file is found, returns nil otherwise.
491
+ def static!
492
+ return if (public_dir = options.public).nil?
493
+ public_dir = File.expand_path(public_dir)
494
+
495
+ path = File.expand_path(public_dir + unescape(request.path_info))
496
+ return if path[0, public_dir.length] != public_dir
497
+ return unless File.file?(path)
498
+
499
+ send_file path, :disposition => nil
500
+ end
501
+
477
502
  # Enable string or symbol key access to the nested params hash.
478
503
  def indifferent_params(params)
479
504
  params = indifferent_hash.merge(params)
@@ -483,23 +508,6 @@ module Sinatra
483
508
  end
484
509
  end
485
510
 
486
- # Recursively replace the params hash with a nested indifferent
487
- # hash. Rack 1.0 has a built in implementation of this method - remove
488
- # this once Rack 1.0 is required.
489
- def nested_params(params)
490
- return indifferent_hash.merge(params) if !params.keys.join.include?('[')
491
- params.inject indifferent_hash do |res, (key,val)|
492
- if key.include?('[')
493
- head = key.split(/[\]\[]+/)
494
- last = head.pop
495
- head.inject(res){ |hash,k| hash[k] ||= indifferent_hash }[last] = val
496
- else
497
- res[key] = val
498
- end
499
- res
500
- end
501
- end
502
-
503
511
  def indifferent_hash
504
512
  Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
505
513
  end
@@ -539,6 +547,8 @@ module Sinatra
539
547
 
540
548
  # Dispatch a request with error handling.
541
549
  def dispatch!
550
+ filter!
551
+ static! if options.static? && (request.get? || request.head?)
542
552
  route!
543
553
  rescue NotFound => boom
544
554
  handle_not_found!(boom)
@@ -565,11 +575,16 @@ module Sinatra
565
575
 
566
576
  # Find an custom error block for the key(s) specified.
567
577
  def error_block!(*keys)
568
- errmap = self.class.errors
569
578
  keys.each do |key|
570
- if block = errmap[key]
571
- res = instance_eval(&block)
572
- return res
579
+ base = self.class
580
+ while base.respond_to?(:errors)
581
+ if block = base.errors[key]
582
+ # found a handler, eval and return result
583
+ res = instance_eval(&block)
584
+ return res
585
+ else
586
+ base = base.superclass
587
+ end
573
588
  end
574
589
  end
575
590
  nil
@@ -591,18 +606,37 @@ module Sinatra
591
606
  }.map! { |line| line.gsub(/^\.\//, '') }
592
607
  end
593
608
 
594
- @routes = {}
595
- @filters = []
596
- @conditions = []
597
- @templates = {}
598
- @middleware = []
599
- @errors = {}
600
- @prototype = nil
601
- @extensions = []
602
-
603
609
  class << self
604
- attr_accessor :routes, :filters, :conditions, :templates,
605
- :middleware, :errors
610
+ attr_reader :routes, :filters, :templates, :errors
611
+
612
+ def reset!
613
+ @conditions = []
614
+ @routes = {}
615
+ @filters = []
616
+ @templates = {}
617
+ @errors = {}
618
+ @middleware = []
619
+ @prototype = nil
620
+ @extensions = []
621
+ end
622
+
623
+ # Extension modules registered on this class and all superclasses.
624
+ def extensions
625
+ if superclass.respond_to?(:extensions)
626
+ (@extensions + superclass.extensions).uniq
627
+ else
628
+ @extensions
629
+ end
630
+ end
631
+
632
+ # Middleware used in this class and all superclasses.
633
+ def middleware
634
+ if superclass.respond_to?(:middleware)
635
+ superclass.middleware + @middleware
636
+ else
637
+ @middleware
638
+ end
639
+ end
606
640
 
607
641
  # Sets an option to the given value. If the value is a proc,
608
642
  # the proc will be called every time the option is accessed.
@@ -770,7 +804,7 @@ module Sinatra
770
804
 
771
805
  invoke_hook(:route_added, verb, path, block)
772
806
 
773
- (routes[verb] ||= []).
807
+ (@routes[verb] ||= []).
774
808
  push([pattern, keys, conditions, block]).last
775
809
  end
776
810
 
@@ -813,10 +847,6 @@ module Sinatra
813
847
  include(*extensions) if extensions.any?
814
848
  end
815
849
 
816
- def extensions
817
- (@extensions + (superclass.extensions rescue [])).uniq
818
- end
819
-
820
850
  def register(*extensions, &block)
821
851
  extensions << Module.new(&block) if block_given?
822
852
  @extensions += extensions
@@ -875,8 +905,8 @@ module Sinatra
875
905
  builder.use Rack::CommonLogger if logging?
876
906
  builder.use Rack::MethodOverride if methodoverride?
877
907
  builder.use ShowExceptions if show_exceptions?
908
+ middleware.each { |c,a,b| builder.use(c, *a, &b) }
878
909
 
879
- @middleware.each { |c,a,b| builder.use(c, *a, &b) }
880
910
  builder.run super
881
911
  builder.to_app
882
912
  end
@@ -885,25 +915,6 @@ module Sinatra
885
915
  synchronize { prototype.call(env) }
886
916
  end
887
917
 
888
- def reset!(base=superclass)
889
- @routes = base.dupe_routes
890
- @templates = base.templates.dup
891
- @conditions = []
892
- @filters = base.filters.dup
893
- @errors = base.errors.dup
894
- @middleware = base.middleware.dup
895
- @prototype = nil
896
- @extensions = []
897
- end
898
-
899
- protected
900
- def dupe_routes
901
- routes.inject({}) do |hash,(request_method,routes)|
902
- hash[request_method] = routes.dup
903
- hash
904
- end
905
- end
906
-
907
918
  private
908
919
  def detect_rack_handler
909
920
  servers = Array(self.server)
@@ -918,7 +929,7 @@ module Sinatra
918
929
  end
919
930
 
920
931
  def inherited(subclass)
921
- subclass.reset! self
932
+ subclass.reset!
922
933
  super
923
934
  end
924
935
 
@@ -942,7 +953,7 @@ module Sinatra
942
953
  /\(.*\)/, # generated code
943
954
  /custom_require\.rb$/, # rubygems require hacks
944
955
  /active_support/, # active_support require hacks
945
- ] unless self.const_defined?('CALLERS_TO_IGNORE')
956
+ ]
946
957
 
947
958
  # add rubinius (and hopefully other VM impls) ignore patterns ...
948
959
  CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS)
@@ -961,10 +972,12 @@ module Sinatra
961
972
  end
962
973
  end
963
974
 
975
+ reset!
976
+
964
977
  set :raise_errors, true
965
978
  set :dump_errors, false
966
979
  set :clean_trace, true
967
- set :show_exceptions, Proc.new { development? }
980
+ set :show_exceptions, false
968
981
  set :sessions, false
969
982
  set :logging, false
970
983
  set :methodoverride, false
@@ -982,16 +995,6 @@ module Sinatra
982
995
  set :public, Proc.new { root && File.join(root, 'public') }
983
996
  set :lock, false
984
997
 
985
- # static files route
986
- get(/.*[^\/]$/) do
987
- pass unless options.static? && options.public?
988
- public_dir = File.expand_path(options.public)
989
- path = File.expand_path(public_dir + unescape(request.path_info))
990
- pass if path[0, public_dir.length] != public_dir
991
- pass unless File.file?(path)
992
- send_file path, :disposition => nil
993
- end
994
-
995
998
  error ::Exception do
996
999
  response.status = 500
997
1000
  content_type 'text/html'
@@ -1035,6 +1038,7 @@ module Sinatra
1035
1038
  # Base class for classic style (top-level) applications.
1036
1039
  class Default < Base
1037
1040
  set :raise_errors, Proc.new { test? }
1041
+ set :show_exceptions, Proc.new { development? }
1038
1042
  set :dump_errors, true
1039
1043
  set :sessions, false
1040
1044
  set :logging, Proc.new { ! test? }
@@ -3,13 +3,13 @@ Gem::Specification.new do |s|
3
3
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
4
 
5
5
  s.name = 'sinatra'
6
- s.version = '0.10.0'
7
- s.date = '2009-06-05'
6
+ s.version = '0.10.1'
7
+ s.date = '2009-06-07'
8
8
 
9
9
  s.description = "Classy web-development dressed in a DSL"
10
10
  s.summary = "Classy web-development dressed in a DSL"
11
11
 
12
- s.authors = ["Blake Mizerany"]
12
+ s.authors = ["Blake Mizerany", "Ryan Tomayko", "Simon Rozet"]
13
13
  s.email = "sinatrarb@googlegroups.com"
14
14
 
15
15
  # = MANIFEST =
@@ -71,7 +71,7 @@ Gem::Specification.new do |s|
71
71
 
72
72
  s.extra_rdoc_files = %w[README.rdoc LICENSE]
73
73
  s.add_dependency 'rack', '>= 1.0'
74
- s.add_development_dependency 'shotgun', '>= 0.2', '< 1.0'
74
+ s.add_development_dependency 'shotgun', '>= 0.3', '< 1.0'
75
75
  s.add_development_dependency 'rack-test', '>= 0.3.0'
76
76
 
77
77
  s.has_rdoc = true
@@ -96,4 +96,16 @@ class FilterTest < Test::Unit::TestCase
96
96
  assert ok?
97
97
  assert_equal 'cool', body
98
98
  end
99
+
100
+ it "runs filters defined in superclasses" do
101
+ base = Class.new(Sinatra::Base)
102
+ base.before { @foo = 'hello from superclass' }
103
+
104
+ mock_app(base) {
105
+ get('/foo') { @foo }
106
+ }
107
+
108
+ get '/foo'
109
+ assert_equal 'hello from superclass', body
110
+ end
99
111
  end
@@ -101,6 +101,37 @@ class MappedErrorTest < Test::Unit::TestCase
101
101
  assert_equal 404, status
102
102
  assert_equal "Lost, are we?", body
103
103
  end
104
+
105
+ it 'inherits error mappings from base class' do
106
+ base = Class.new(Sinatra::Base)
107
+ base.error(FooError) { 'base class' }
108
+
109
+ mock_app(base) {
110
+ set :raise_errors, false
111
+ get '/' do
112
+ raise FooError
113
+ end
114
+ }
115
+
116
+ get '/'
117
+ assert_equal 'base class', body
118
+ end
119
+
120
+ it 'overrides error mappings in base class' do
121
+ base = Class.new(Sinatra::Base)
122
+ base.error(FooError) { 'base class' }
123
+
124
+ mock_app(base) {
125
+ set :raise_errors, false
126
+ error(FooError) { 'subclass' }
127
+ get '/' do
128
+ raise FooError
129
+ end
130
+ }
131
+
132
+ get '/'
133
+ assert_equal 'subclass', body
134
+ end
104
135
  end
105
136
 
106
137
  describe 'Custom Error Pages' do
@@ -183,10 +183,15 @@ class OptionsTest < Test::Unit::TestCase
183
183
  end
184
184
 
185
185
  describe 'show_exceptions' do
186
+ %w[development test production none].each do |environment|
187
+ it "is disabled on Base in #{environment} environments" do
188
+ @base.set(:environment, environment)
189
+ assert ! @base.show_exceptions?
190
+ end
191
+ end
192
+
186
193
  it 'is enabled on Default only in development' do
187
194
  @base.set(:environment, :development)
188
- assert @base.show_exceptions?
189
-
190
195
  assert @default.development?
191
196
  assert @default.show_exceptions?
192
197
 
@@ -781,4 +781,39 @@ class RoutingTest < Test::Unit::TestCase
781
781
  end
782
782
 
783
783
  end
784
+
785
+ it "matches routes defined in superclasses" do
786
+ base = Class.new(Sinatra::Base)
787
+ base.get('/foo') { 'foo in baseclass' }
788
+
789
+ mock_app(base) {
790
+ get('/bar') { 'bar in subclass' }
791
+ }
792
+
793
+ get '/foo'
794
+ assert ok?
795
+ assert_equal 'foo in baseclass', body
796
+
797
+ get '/bar'
798
+ assert ok?
799
+ assert_equal 'bar in subclass', body
800
+ end
801
+
802
+ it "matches routes in subclasses before superclasses" do
803
+ base = Class.new(Sinatra::Base)
804
+ base.get('/foo') { 'foo in baseclass' }
805
+ base.get('/bar') { 'bar in baseclass' }
806
+
807
+ mock_app(base) {
808
+ get('/foo') { 'foo in subclass' }
809
+ }
810
+
811
+ get '/foo'
812
+ assert ok?
813
+ assert_equal 'foo in subclass', body
814
+
815
+ get '/bar'
816
+ assert ok?
817
+ assert_equal 'bar in baseclass', body
818
+ end
784
819
  end
@@ -34,6 +34,13 @@ class StaticTest < Test::Unit::TestCase
34
34
  assert response.headers.include?('Last-Modified')
35
35
  end
36
36
 
37
+ %w[POST PUT DELETE].each do |verb|
38
+ it "does not serve #{verb} requests" do
39
+ send verb.downcase, "/#{File.basename(__FILE__)}"
40
+ assert_equal 404, status
41
+ end
42
+ end
43
+
37
44
  it 'serves files in preference to custom routes' do
38
45
  @app.get("/#{File.basename(__FILE__)}") { 'Hello World' }
39
46
  get "/#{File.basename(__FILE__)}"
@@ -1,8 +1,8 @@
1
1
  require File.dirname(__FILE__) + '/helper'
2
2
 
3
3
  class TemplatesTest < Test::Unit::TestCase
4
- def render_app(&block)
5
- mock_app {
4
+ def render_app(base=Sinatra::Base, &block)
5
+ mock_app(base) {
6
6
  def render_test(template, data, options, locals, &block)
7
7
  inner = block ? block.call : ''
8
8
  data + inner
@@ -107,6 +107,25 @@ class TemplatesTest < Test::Unit::TestCase
107
107
  assert ok?
108
108
  assert_equal 'Hello Mike!<p>content</p>', body
109
109
  end
110
+
111
+ it 'loads templates defined in subclasses' do
112
+ base = Class.new(Sinatra::Base)
113
+ base.template(:foo) { 'bar' }
114
+ render_app(base) { render :test, :foo }
115
+ assert ok?
116
+ assert_equal 'bar', body
117
+ end
118
+
119
+ it 'uses templates in superclasses before subclasses' do
120
+ base = Class.new(Sinatra::Base)
121
+ base.template(:foo) { 'template in superclass' }
122
+ render_app(base) { render :test, :foo }
123
+ @app.template(:foo) { 'template in subclass' }
124
+
125
+ get '/'
126
+ assert ok?
127
+ assert_equal 'template in subclass', body
128
+ end
110
129
  end
111
130
 
112
131
  # __END__ : this is not the real end of the script.
metadata CHANGED
@@ -1,15 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-sinatra
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Blake Mizerany
8
+ - Ryan Tomayko
9
+ - Simon Rozet
8
10
  autorequire:
9
11
  bindir: bin
10
12
  cert_chain: []
11
13
 
12
- date: 2009-06-05 00:00:00 -07:00
14
+ date: 2009-06-07 00:00:00 -07:00
13
15
  default_executable:
14
16
  dependencies:
15
17
  - !ruby/object:Gem::Dependency
@@ -30,7 +32,7 @@ dependencies:
30
32
  requirements:
31
33
  - - ">="
32
34
  - !ruby/object:Gem::Version
33
- version: "0.2"
35
+ version: "0.3"
34
36
  - - <
35
37
  - !ruby/object:Gem::Version
36
38
  version: "1.0"