sinatra-sinatra 0.10.0 → 0.10.1

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