scorched 0.23 → 0.24

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +0 -0
  3. data/CHANGES.md +6 -0
  4. data/Gemfile +0 -0
  5. data/LICENSE +0 -0
  6. data/README.md +10 -6
  7. data/Rakefile +0 -0
  8. data/TODO.md +0 -0
  9. data/comparison/common.rb +15 -0
  10. data/comparison/profile.rb +33 -0
  11. data/comparison/roda.ru +35 -0
  12. data/comparison/scorched.ru +33 -0
  13. data/comparison/sinatra.ru +32 -0
  14. data/comparison/views/artist.erb +5 -0
  15. data/comparison/views/index.erb +1 -0
  16. data/docs/01_preface.md +0 -0
  17. data/docs/02_fundamentals/01_the_controller.md +0 -0
  18. data/docs/02_fundamentals/02_configuration.md +0 -0
  19. data/docs/02_fundamentals/03_routing.md +0 -0
  20. data/docs/02_fundamentals/04_requests_and_responses.md +0 -0
  21. data/docs/02_fundamentals/05_filters.md +0 -0
  22. data/docs/02_fundamentals/06_middleware.md +0 -0
  23. data/docs/02_fundamentals/07_request_and_session_data.md +0 -0
  24. data/docs/02_fundamentals/08_views.md +0 -0
  25. data/docs/02_fundamentals/09_sharing_request_state.md +0 -0
  26. data/docs/03_further_reading/be_creative.md +0 -0
  27. data/docs/03_further_reading/code_reloading.md +0 -0
  28. data/docs/03_further_reading/live_console.md +0 -0
  29. data/docs/03_further_reading/running_unit_tests.md +0 -0
  30. data/examples/file_upload.ru +0 -0
  31. data/examples/less_simple.ru +0 -0
  32. data/examples/media_types.ru +0 -0
  33. data/examples/restful_controller.ru +0 -0
  34. data/examples/simple.ru +0 -0
  35. data/lib/scorched.rb +0 -0
  36. data/lib/scorched/collection.rb +0 -0
  37. data/lib/scorched/controller.rb +17 -14
  38. data/lib/scorched/dynamic_delegate.rb +0 -0
  39. data/lib/scorched/error.rb +0 -0
  40. data/lib/scorched/match.rb +0 -0
  41. data/lib/scorched/options.rb +20 -7
  42. data/lib/scorched/request.rb +0 -0
  43. data/lib/scorched/response.rb +4 -4
  44. data/lib/scorched/static.rb +9 -5
  45. data/lib/scorched/version.rb +1 -1
  46. data/scorched.gemspec +0 -0
  47. data/spec/collection_spec.rb +0 -0
  48. data/spec/controller_spec.rb +7 -5
  49. data/spec/helper.rb +0 -0
  50. data/spec/options_spec.rb +0 -0
  51. data/spec/public/static.txt +0 -0
  52. data/spec/request_spec.rb +0 -0
  53. data/spec/views/composer.erb +0 -0
  54. data/spec/views/layout.erb +0 -0
  55. data/spec/views/main.erb +0 -0
  56. data/spec/views/other.str +0 -0
  57. data/spec/views/partial.erb +0 -0
  58. metadata +10 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1007cc5234411333f49d1cacda15b2a384eb0a0c
4
- data.tar.gz: 41e412b90cddee3e91c4f8785fecc0b73a394977
3
+ metadata.gz: 7a269c05d699e75919261edd1b0fa291753e9cb9
4
+ data.tar.gz: 7b4789f2fd5f3c38e2004fcb1417a53591112326
5
5
  SHA512:
6
- metadata.gz: 604562e9030f0e52a95a56ff61a43094de84c519daa5ee9f7d40a35734528d8dddcf4f52fc2fd73437a0069661fadab49b3a1ca24d40346ba95fd3eea6dac5e3
7
- data.tar.gz: fbc6efbbd4508f04526d3910553a40136f037780d129f48298170eea136deeafb73b6cc7c28f8bef092413a82a44a992cd2643035c7eefdb9267bb8c4ee01d26
6
+ metadata.gz: 724e1aa350acae90d82c1be43567c251ed04127105996486383e01bf6ffa6154d4bc7385ade5816b8a926319cc8387fa68403f43122014603b9be9ade81091db
7
+ data.tar.gz: 43d90ab9f5e30d2b35c47aaa871d1794368f517a56f3ea5aa37f4e6ab8f8cfe4e320252867443a9da8ed05730e9db1d343c0039a272e775da4cd7bd658a183f9
data/.yardopts CHANGED
File without changes
data/CHANGES.md CHANGED
@@ -3,6 +3,12 @@ Changelog
3
3
 
4
4
  _Note that Scorched is yet to reach a v1.0 release. This means breaking changes may still be made. If upgrading the version of Scorched for your project, review this changelog carefully._
5
5
 
6
+ ### v0.24
7
+ * Query string is now preserved when stripping trailing slashes with `config[:strip_trailing_slash] = :redirect`, e.g. `/search/?query=cats` now becomes `/search?query=cats` instead of just `/search`.
8
+ * `absolute` method now returns as-is anything not starting with a forward slash, e.g. `absolute("./view") => "./view"`
9
+ * Improved performance by caching the Rack::Builder instance for each controller.
10
+ * Improved performance by caching data associated with Scorched::Options#to_hash.
11
+
6
12
  ### v0.23
7
13
  * Now using _scorched-accept_ for accept header parsing and logic, which fixes issues with the `:media_type` condition.
8
14
  * Exceptions caught by Scorched are now assigned to env['rack.exception'].
data/Gemfile CHANGED
File without changes
data/LICENSE CHANGED
File without changes
data/README.md CHANGED
@@ -37,6 +37,7 @@ $ rackup hello_world.ru
37
37
 
38
38
  Scorched requires Ruby 2.0 or above. If you've got Ruby 2.0.0-p195 and newer, you're good. Otherwise, you need to ensure that your version of Ruby 2.0 includes [changeset 39919](http://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39919) in order to avoid suffering from [random segmentation faults](http://bugs.ruby-lang.org/issues/8100).
39
39
 
40
+
40
41
  The Errors of Our Past (and Present!)
41
42
  ----------------------
42
43
  One of the mistakes made by a lot of other Ruby frameworks is to not leverage the power of the class. Consequently, this makes for some awkwardness. Helpers for example, are a classic reinvention of what classes and modules are already made to solve. Scorched implements Controllers as classes, which in addition to having their own DSL, allow you to define and call whatever you need as standard instance methods. The decision to allow developers to implement helpers and other common functionality as standard instance methods not only makes Controllers somewhat more predictable and familiar, but also allows for such helpers to be inheritable via plain old class inheritance.
@@ -63,33 +64,33 @@ First Impressions
63
64
 
64
65
  ```ruby
65
66
  class MyApp < Scorched::Controller
66
-
67
+
67
68
  # From the most simple route possible...
68
69
  get '/' do
69
70
  "Hello World"
70
71
  end
71
-
72
+
72
73
  # To something that gets the muscle's flexing
73
74
  route '/articles/:title/::opts', 2, method: ['GET', 'POST'], content_type: :json do
74
75
  # Do what you want in here. Note, the second argument is the optional route priority.
75
76
  end
76
-
77
+
77
78
  # Anonymous controllers allow for convenient route grouping to which filters and conditions can be applied
78
79
  controller conditions: {media_type: 'application/json'} do
79
80
  get '/articles/*' do |page|
80
81
  {title: 'Scorched Rocks', body: '...', created_at: '27/08/2012', created_by: 'Bob'}
81
82
  end
82
-
83
+
83
84
  after do
84
85
  response.body = response.body.to_json
85
86
  end
86
87
  end
87
-
88
+
88
89
  # The things you get for free by using Classes for Controllers (...that's directed at you Padrino)
89
90
  def my_little_helper
90
91
  # Do some crazy awesome stuff that no route can resist using.
91
92
  end
92
-
93
+
93
94
  # You can always avoid the routing helpers and add mappings manually. Anything that responds to #call is a valid
94
95
  # target, with the only minor exception being that proc's are instance_exec'd, not call'd.
95
96
  self << {pattern: '/admin', priority: 10, target: My3rdPartyAdminApp}
@@ -106,6 +107,9 @@ This API shouldn't look too foreign to anyone familiar with frameworks like Sina
106
107
  * Route priorities - Routes (referred to as mappings internally) can be assigned priorities. A priority can be any arbitrary number by which the routes are ordered. The higher the number, the higher the priority.
107
108
  * Conditions - Conditions are merely procs defined on the controller which are inherited (and can be overriden) by child controllers. When a request comes in, mappings that match the requested URL, first have their conditions evaluated in the context of the controller instance, before control is handed off to the target associated with that mapping. It's a very simple implementation that comes with a lot of flexibility.
108
109
 
110
+ Comparisons with other frameworks
111
+ ---------------------------------
112
+ Refer to the [comparisons](https://github.com/Wardrop/Scorched/tree/master/comparisons) directory in the repo to compare a simple example app written in frameworks similar to Scorched.
109
113
 
110
114
  Links
111
115
  -----
data/Rakefile CHANGED
File without changes
data/TODO.md CHANGED
File without changes
@@ -0,0 +1,15 @@
1
+ class Artist
2
+ def self.[](*args)
3
+ new
4
+ end
5
+
6
+ def method_missing(*args)
7
+ true
8
+ end
9
+ end
10
+
11
+ module Kernel
12
+ def check_access(bool)
13
+ bool
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ require 'ruby-prof'
2
+ require 'rack/mock'
3
+ require 'allocation_stats'
4
+ require 'scorched'
5
+ require 'sinatra/base'
6
+
7
+ scorched = Class.new(Scorched::Controller) do
8
+ get '/' do
9
+ 'Hello world'
10
+ end
11
+ end
12
+
13
+ sinatra = Class.new(Sinatra::Base) do
14
+ get '/' do
15
+ 'Hello world'
16
+ end
17
+ end
18
+
19
+
20
+
21
+ scorched_stats = AllocationStats.new(burn: 5).trace do
22
+ scorched.call(Rack::MockRequest.env_for('/'))
23
+ end
24
+
25
+ sinatra_stats = AllocationStats.new(burn: 5).trace do
26
+ sinatra.call(Rack::MockRequest.env_for('/'))
27
+ end
28
+
29
+ puts "Scorched Allocations: #{scorched_stats.allocations.all.size}"
30
+ puts "Scorched Memsize: #{scorched_stats.allocations.bytes.to_a.inject(&:+)}"
31
+
32
+ puts "Sinatra Allocations: #{sinatra_stats.allocations.all.size}"
33
+ puts "Sinatra Memsize: #{sinatra_stats.allocations.bytes.to_a.inject(&:+)}"
@@ -0,0 +1,35 @@
1
+ require 'roda'
2
+ require_relative './common'
3
+
4
+ class App < Roda
5
+ plugin :render
6
+ plugin :all_verbs
7
+ use Rack::MethodOverride
8
+
9
+ route do |r|
10
+ r.root do
11
+ render :index
12
+ end
13
+
14
+ r.is 'artist/:id' do |artist_id|
15
+ @artist = Artist[artist_id]
16
+ check_access(@artist)
17
+
18
+ r.get do
19
+ render :artist
20
+ end
21
+
22
+ r.post do
23
+ @artist.update(r['artist'])
24
+ r.redirect '?POST'
25
+ end
26
+
27
+ r.delete do
28
+ @artist.destroy
29
+ r.redirect '?DELETE'
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ run App
@@ -0,0 +1,33 @@
1
+ require 'scorched'
2
+ require_relative './common'
3
+
4
+ class App < Scorched::Controller
5
+ get '/' do
6
+ render :index
7
+ end
8
+
9
+ controller '/artist/:id' do
10
+ before do
11
+ @artist = Artist[captures[:id]]
12
+ check_access(@artist)
13
+ end
14
+
15
+ get '/' do
16
+ render :artist
17
+ end
18
+
19
+ post '/' do
20
+ @artist.update(request.POST)
21
+ end
22
+
23
+ delete '/' do
24
+ @artist.destroy
25
+ end
26
+
27
+ after method!: 'GET' do
28
+ redirect "?#{request.request_method}"
29
+ end
30
+ end
31
+ end
32
+
33
+ run App
@@ -0,0 +1,32 @@
1
+ require 'sinatra/base'
2
+ require_relative './common'
3
+
4
+ class App < Sinatra::Base
5
+ use Rack::MethodOverride
6
+
7
+ get '/' do
8
+ erb :index
9
+ end
10
+
11
+ get '/artist/:id' do
12
+ @artist = Artist[params[:id]]
13
+ check_access(@artist)
14
+ erb :artist
15
+ end
16
+
17
+ post '/artist/:id' do
18
+ @artist = Artist[params[:id]]
19
+ check_access(@artist)
20
+ @artist.update(params[:artist])
21
+ redirect(request.path_info + '?POST')
22
+ end
23
+
24
+ delete '/artist/:id' do
25
+ @artist = Artist[params[:id]]
26
+ check_access(@artist)
27
+ @artist.destory
28
+ redirect(request.path_info + '?DELETE')
29
+ end
30
+ end
31
+
32
+ run App
@@ -0,0 +1,5 @@
1
+ This is the artist.
2
+ <form method="post">
3
+ <input type="submit" name="_method" value="Post" />
4
+ <input type="submit" name="_method" value="Delete" />
5
+ </form>
@@ -0,0 +1 @@
1
+ This is the index. <a href="/artist/45">Go to artist...</a>
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -105,18 +105,18 @@ module Scorched
105
105
  end
106
106
 
107
107
  def call(env)
108
+ @instance_cache ||= {}
108
109
  loaded = env['scorched.middleware'] ||= Set.new
109
- app = lambda do |env|
110
- self.new(env).process
110
+ to_load = middleware.reject{ |v| loaded.include? v }
111
+ key = [loaded, to_load].map { |x| x.map &:object_id }
112
+ unless @instance_cache[key]
113
+ builder = Rack::Builder.new
114
+ to_load.each { |proc| builder.instance_exec(self, &proc) }
115
+ builder.run(lambda { |env| self.new(env).process })
116
+ @instance_cache[key] = builder.to_app
111
117
  end
112
-
113
- builder = Rack::Builder.new
114
- middleware.reject{ |v| loaded.include? v }.each do |proc|
115
- builder.instance_exec(self, &proc)
116
- loaded << proc
117
- end
118
- builder.run(app)
119
- builder.call(env)
118
+ loaded.merge(to_load)
119
+ @instance_cache[key].call(env)
120
120
  end
121
121
 
122
122
  # Generates and assigns mapping hash from the given arguments.
@@ -275,7 +275,8 @@ module Scorched
275
275
  begin
276
276
  catch(:halt) do
277
277
  if config[:strip_trailing_slash] == :redirect && request.path =~ %r{[^/]/+$}
278
- redirect(request.path.chomp('/'), 307)
278
+ query_string = request.query_string.empty? ? '' : '?' << request.query_string
279
+ redirect(request.path.chomp('/') + query_string, 307)
279
280
  end
280
281
  eligable_matches = matches.reject { |m| m.failed_condition }
281
282
  pass if config[:auto_pass] && eligable_matches.empty?
@@ -519,15 +520,17 @@ module Scorched
519
520
  end
520
521
 
521
522
  # Takes an optional path, relative to the applications root URL, and returns an absolute path.
523
+ # If relative path given (i.e. anything not starting with `/`), returns it as-is.
522
524
  # Example: absolute('/style.css') #=> /myapp/style.css
523
525
  def absolute(path = nil)
524
- return path if path && URI.parse(path).scheme
525
- return_path = if path
526
+ return path if path && path[0] != '/'
527
+ abs = if path
526
528
  [env['scorched.root_path'], path].join('/').gsub(%r{/+}, '/')
527
529
  else
528
530
  env['scorched.root_path']
529
531
  end
530
- return_path[0] == '/' ? return_path : return_path.insert(0, '/')
532
+ abs.insert(0, '/') unless abs[0] == '/'
533
+ abs
531
534
  end
532
535
 
533
536
  # We always want this filter to run at the end-point controller, hence we include the conditions within the body of
File without changes
File without changes
File without changes
@@ -6,24 +6,38 @@ module Scorched
6
6
  delegate 'to_hash', *Hash.instance_methods(false).reject { |m|
7
7
  [:[]=, :clear, :delete, :delete_if, :merge!, :replace, :shift, :store].include? m
8
8
  }
9
-
9
+
10
10
  alias_method :<<, :_merge!
11
-
11
+
12
12
  # sets parent Options object and returns self
13
13
  def parent!(parent)
14
14
  @parent = parent
15
+ @cache = {}
15
16
  self
16
17
  end
17
-
18
+
18
19
  def to_hash(inherit = true)
19
- (inherit && Hash === @parent) ? @parent.to_hash.merge(self) : {}.merge(self)
20
+ @cache ||= {}
21
+ unless @cache[:self] == self
22
+ @cache[:self] = self._to_h
23
+ @cache[:merged] = nil
24
+ end
25
+ if inherit && Hash === @parent
26
+ unless @cache[:parent] == @parent.to_hash
27
+ @cache[:parent] = @parent.to_hash
28
+ @cache[:merged] = nil
29
+ end
30
+ @cache[:merged] ||= @cache[:parent].merge(@cache[:self])
31
+ else
32
+ @cache[:self]
33
+ end
20
34
  end
21
-
35
+
22
36
  def inspect
23
37
  "#<#{self.class}: local#{_inspect}, merged#{to_hash.inspect}>"
24
38
  end
25
39
  end
26
-
40
+
27
41
  class << self
28
42
  def Options(accessor_name)
29
43
  m = Module.new
@@ -51,4 +65,3 @@ module Scorched
51
65
  end
52
66
  end
53
67
  end
54
-
File without changes
@@ -11,14 +11,14 @@ module Scorched
11
11
  self.header.merge!(response[1])
12
12
  self
13
13
  end
14
-
14
+
15
15
  # Automatically wraps the assigned value in an array if it doesn't respond to ``each``.
16
16
  # Also filters out non-true values and empty strings.
17
17
  def body=(value)
18
18
  value = [] if !value || value == ''
19
19
  super(value.respond_to?(:each) ? value : [value.to_s])
20
20
  end
21
-
21
+
22
22
  # Override finish to avoid using BodyProxy
23
23
  def finish(*args, &block)
24
24
  self['Content-Type'] ||= 'text/html;charset=utf-8'
@@ -32,8 +32,8 @@ module Scorched
32
32
  [status.to_i, header, body]
33
33
  end
34
34
  end
35
-
35
+
36
36
  alias :to_a :finish
37
37
  alias :to_ary :finish
38
38
  end
39
- end
39
+ end
@@ -1,14 +1,18 @@
1
1
  module Scorched
2
2
  class Static
3
3
  def initialize(app, dir = 'public')
4
- @app = app
5
- @file_server = Rack::File.new(dir)
4
+ @app, @dir = app, dir
6
5
  end
7
6
 
8
7
  def call(env)
9
- @file_server.call(env)
10
- response = @file_server.call(env)
11
- response[0] >= 400 ? @app.call(env) : response
8
+ response = file_server.call(env)
9
+ response[0] >= 400 ? @app.call(env) : response
10
+ end
11
+
12
+ protected
13
+
14
+ def file_server
15
+ @file_server ||= Rack::File.new(@dir)
12
16
  end
13
17
  end
14
18
  end
@@ -1,3 +1,3 @@
1
1
  module Scorched
2
- VERSION = '0.23'
2
+ VERSION = '0.24'
3
3
  end
File without changes
File without changes
@@ -810,6 +810,7 @@ module Scorched
810
810
  response = rt.get('/test/')
811
811
  response.status.should == 307
812
812
  response['Location'].should == '/test'
813
+ rt.get('/test/?hello=world')['Location'].should == '/test?hello=world'
813
814
  end
814
815
 
815
816
  it "can be set to ignore trailing slash while pattern matching" do
@@ -1198,14 +1199,15 @@ module Scorched
1198
1199
  end
1199
1200
 
1200
1201
  it "can append an optional path" do
1201
- my_app.get('/absolute') { absolute('hello') }
1202
+ my_app.get('/absolute') { absolute('/hello') }
1202
1203
  rt.get('http://scorchedrb.com/myapp/absolute?something=true').body.should == '/myapp/hello'
1203
1204
  end
1204
1205
 
1205
- it "returns the given URL if scheme detected" do
1206
- test_url = 'http://google.com/blah'
1207
- my_app.get('/') { absolute(test_url) }
1208
- rt.get('/myapp').body.should == test_url
1206
+ it "returns the given path if it doesn't begin with a forward-slash" do
1207
+ my_app.get('/url') { absolute('http://google.com/about') }
1208
+ my_app.get('/relative') { absolute('./about') }
1209
+ rt.get('/myapp/url').body.should == 'http://google.com/about'
1210
+ rt.get('/myapp/relative').body.should == './about'
1209
1211
  end
1210
1212
 
1211
1213
  it "returns an absolute URL path for subcontroller defined with controller helper" do
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scorched
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.23'
4
+ version: '0.24'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Wardrop
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-06 00:00:00.000000000 Z
11
+ date: 2015-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -122,6 +122,13 @@ files:
122
122
  - README.md
123
123
  - Rakefile
124
124
  - TODO.md
125
+ - comparison/common.rb
126
+ - comparison/profile.rb
127
+ - comparison/roda.ru
128
+ - comparison/scorched.ru
129
+ - comparison/sinatra.ru
130
+ - comparison/views/artist.erb
131
+ - comparison/views/index.erb
125
132
  - docs/01_preface.md
126
133
  - docs/02_fundamentals/01_the_controller.md
127
134
  - docs/02_fundamentals/02_configuration.md
@@ -184,7 +191,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
191
  version: '0'
185
192
  requirements: []
186
193
  rubyforge_project:
187
- rubygems_version: 2.4.5
194
+ rubygems_version: 2.4.3
188
195
  signing_key:
189
196
  specification_version: 4
190
197
  summary: Light-weight, DRY as a desert, web framework for Ruby