scorched 0.5.1 → 0.5.2

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: dc95df5b8180579cc08c6d839b56dc64ad9105b1
4
- data.tar.gz: 0a445e9f72ee7aaf30c7f0aa8a9cc93ec5c919ee
3
+ metadata.gz: b0950b3928b9921a50ec7af0cb01869412172f09
4
+ data.tar.gz: b698623e4b6e40a64cf56ac47b7d20be50df4c2f
5
5
  SHA512:
6
- metadata.gz: 2a46ef33d42014a33f3d0abc9d0009714f088caeaaba24633e72c1d25ad033838b5a693c53bfdf5dbaf9b1ae6c0dd714d5d8f52c17e0dcceadc9773dad558ac9
7
- data.tar.gz: 2446404ea7aa8a28d140921d66142fd292f51b492fabb7029f7a3d632ba97a1707425589b11ecf46b58b1e35563f2f7d6e530d88ae26ca04858d24bba7890033
6
+ metadata.gz: e528fe6a63bfdd08127ff405e83541a5a6032fa6db1746be18c50a0e9488de6eba591b720fb218fda4378e42b244eba4246be71fb4bc8c384921cdd9c2c2bca8
7
+ data.tar.gz: 00f71b40921a073e043a985e693e8a2924c93f16a4221a2f5efcf72588d90515846c67151302cd931d1af7fe112884c17f2424c068bbd82e83170de954f1ce12
@@ -3,6 +3,9 @@ Milestones
3
3
 
4
4
  Changelog
5
5
  ---------
6
+ ### v0.5.2
7
+ * Response content-type now defaults to "text/html;charset=utf-8", rather than empty.
8
+
6
9
  ### v0.5.1
7
10
  * Added URL helpers, #absolute and #url
8
11
  * Render helper now loads files itself as Tilt still has issues with UTF-8 files.
data/README.md CHANGED
@@ -1,20 +1,39 @@
1
- Scorched
2
- ========
1
+ [Simple, Powerful, Scorched](http://scorchedrb.com)
2
+ ==========================
3
3
 
4
- *Light-weight, DRY as a desert, web framework for Ruby. Inspired by Sinatra, this framework is my vision of the next evolutionary step in light-weight ruby web frameworks.*
4
+ Scorched is a generic, unopinionated, DRY, light-weight web framework for Ruby. It provides a generic yet powerful set of constructs for processing HTTP requests, with which websites and applications of almost any scale can be built.
5
5
 
6
- Scorched honours the patrons of the past. It's not a complete reinvention of the lightweight web framework, but rather what I hope is an evolutionary enhancement. Most of the concepts are carried forward from predecessors. Scorched merely enhances those concepts in an attempt to extract their full potential, as well as offering up to scrutiny some entirely new idioms. All with the intention to make developing lightweight web apps in Ruby even more enjoyable.
6
+ If you've used a light-weight DSL-based Ruby web framework before, such as a Sinatra, it should look familiar. Scorched is a true evolutionary enhancement of Sinatra, with more power, focus, and less clutter.
7
7
 
8
- The name 'Scorched' is inspired by the main goal of the project, which is to DRY-up what the likes of Sinatra and Padrino left moist.
8
+ Getting Started
9
+ ---------------
9
10
 
11
+ Install the canister...
10
12
 
11
- The Errors of Our Past (aka. Areas of Moisty-ness)
12
- --------------------------------------------------
13
- I think the biggest mistake made by the predecessors of Scorched such as Sinatra/Padrino, was to not leverage the power
14
- of the class. The consequences of this made for some awkwardness. Helpers are a classical reinvention of what
15
- classes and modules were already made to solve. Scorched implements Controllers as Classes, which in addition to having their own DSL, allow defining and calling traditional class methods. Allowing developers to implement helpers and other common functionality as proper methods not only makes them more predictable and familiar, but of course allow such helpers to be inheritable via plain-old Class inheritance.
13
+ gem install scorched
16
14
 
17
- Perhaps another error (or area of sogginess, if you will) has been a lack of consideration for the hierarchical nature of websites, and the fact that sub-directories are often expected to inherit attributes of their parents. Scorched supports sub-controllers to any arbitrary depth, with each controllers filters and route conditions applied along the way. This can assist many areas of web development, including security, restful interfaces, and interchangeable output formats.
15
+ Open the valve...
16
+
17
+ # ruby
18
+ # hello_world.ru
19
+ require 'scorched'
20
+ class App < Scorched::Controller
21
+ get '/' do
22
+ 'hello world'
23
+ end
24
+ end
25
+ run App
26
+
27
+ And light the flame...
28
+
29
+ rackup hello_world.ru
30
+
31
+
32
+ The Errors of Our Past
33
+ ----------------------
34
+ One of the big mistakes made by a lot of other Ruby frameworks, was to not leverage the power of the class. The consequences of this made for some awkwardness. Helpers for example, are a classical reinvention of what classes and modules were already made to solve. Scorched implements Controllers as Classes, which in addition to having their own DSL, allow defining and calling traditional class methods. The decision to allow developers to implement helpers and other common functionality as proper methods not only makes Controllers somewhat more predictable and familiar, but of course allow such helpers to be inheritable via plain-old Class inheritance.
35
+
36
+ Perhaps another design oversight of other frameworks, has been the lack of consideration for the hierarchical nature of websites, and the fact that sub-directories are often expected to inherit attributes of their parents. Scorched supports sub-controllers to any arbitrary depth, with each controllers configuration, filters, route conditions, etc, applied along the way. This can assist many areas of web development, including security, restful interfaces, and interchangeable content types.
18
37
 
19
38
 
20
39
  Design Philosophy
@@ -33,28 +52,28 @@ Part of what keeps Scorched lightweight, is that unlike other lightweight web fr
33
52
 
34
53
  First Impressions
35
54
  -----------------
36
- Below I present a sample of the API as it currently stands:
37
55
 
56
+ # ruby
38
57
  class MyApp < Scorched::Controller
39
-
58
+
40
59
  # From the most simple route possible...
41
60
  get '/' do
42
61
  "Hello World"
43
62
  end
44
63
 
45
- # To something that gets the muscle's flexing a little
64
+ # To something that gets the muscle's flexing
46
65
  route '/articles/:title/::opts', 2, methods: ['GET', 'POST'], content_type: :json do
47
66
  # Do what you want in here. Note, the second argument is the optional route priority.
48
67
  end
49
68
 
50
69
  # Anonymous controllers allow for convenient route grouping to which filters and conditions can be applied
51
- controller conditions: {content_type: :json} do
70
+ controller conditions: {media_type: 'application/json'} do
52
71
  get '/articles/*' do |page|
53
72
  {title: 'Scorched Rocks', body: '...', created_at: '27/08/2012', created_by: 'Bob'}
54
73
  end
55
74
 
56
75
  after do
57
- response.to_json
76
+ response.body = response.body.to_json
58
77
  end
59
78
  end
60
79
 
@@ -63,21 +82,20 @@ Below I present a sample of the API as it currently stands:
63
82
  # Do some crazy awesome stuff that no route can resist using.
64
83
  end
65
84
 
66
- # You can always avoid the routing helpers and add mappings manually. Anything that responds to #call is a valid target, with the only minor exception being that proc's are instance_exec'd, not #call'd.
67
- self << {url: '/admin', priority: 10, target: My3rdPartyAdminApp}
68
- self << {url: '**', conditions: {maintenance_mode: true}, target: proc { |env|
85
+ # You can always avoid the routing helpers and add mappings manually. Anything that responds to #call is a valid
86
+ # target, with the only minor exception being that proc's are instance_exec'd, not call'd.
87
+ self << {pattern: '/admin', priority: 10, target: My3rdPartyAdminApp}
88
+ self << {pattern: '**', conditions: {maintenance_mode: true}, target: proc { |env|
69
89
  @request.body << 'Maintenance underway, please be patient.'
70
90
  }}
71
91
  end
72
-
92
+
73
93
  This API shouldn't look too foreign to anyone familiar with frameworks like Sinatra, and the potential power at hand should be obvious. The `route` method demonstrates a few minor features of Scorched:
74
94
 
75
95
  * Multi-method routes - Because sometimes the difference between a GET and POST can be a single line of code. If no methods are provided, the route receives all HTTP methods.
76
96
  * Named Wildcards - Not an original idea, but you may note the named wildcard with the double colon. This maps to the '**' glob directive, which will span forward-slashes while matching. The single asterisk (or colon) behaves like the single asterisk glob directive, and will not match forward-slashes.
77
97
  * 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.
78
- * 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 potential.
79
-
80
- That should hopefully give you an example of how the core of Scorched is shaping up. This demonstrates only a subset of what Scorched will offer.
98
+ * 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.
81
99
 
82
100
 
83
101
  Development Progress
@@ -0,0 +1,6 @@
1
+ Preface
2
+ =======
3
+
4
+ _This is where I share with you all my infinite wisdom. It will come. In the mean time, know that the documentation is currently incomplete, and likely the victim of quite a few typos._
5
+
6
+ _As an early adopter, feel free to use the Github issue tracker to ask questions and request assistance._
@@ -0,0 +1,19 @@
1
+ The Controller
2
+ ==============
3
+
4
+ Scorched consists almost entirely of the ``Scorched::Controller``. The Controller is the class from which your application class inherits. All the code examples provided in the documentation are assumed to be wrapped within a controller class.
5
+
6
+ # ruby
7
+ class MyApp < Scorched::Controller
8
+ # We are now within the controller class.
9
+ # Most examples are assumed to be within this context.
10
+ end
11
+
12
+ Your application's root controller (named ``MyApp`` in the example above), should be configured as the _run_ target in your rackup file:
13
+
14
+ # ruby
15
+ # config.ru
16
+ require './myapp.rb'
17
+ run MyApp
18
+
19
+ The rest of the documentation will detail the Controller more thoroughly.
@@ -0,0 +1,29 @@
1
+ Configuration
2
+ =============
3
+
4
+ Scorched includes a few configurable options out of the box. These all have common defaults, or are otherwise left intentionally blank to ensure the developer opts-in to any potentially undesirable or surprising behaviour.
5
+
6
+ There are two sets of configurables. Those which apply to views, and everything else. Each set of configuration options is a ``Scorched::Options`` instance. This allows configuration options to be inherited and subsequently overriden by child classes. This is handy in many instances, but a common requirement might be to change the view directory or default layout of some sub-controller.
7
+
8
+ Options
9
+ -------
10
+
11
+ Each configuration is listed below, with the default value of each included.
12
+
13
+ * ``config[:strip_trailing_slash] = :redirect``
14
+ Controls how trailing forward slashes in requests are handled.
15
+ * ``:redirect`` - Strips and redirects URL's ending in a forward slash
16
+ * ``:ignore`` - Internally ignores trailing slash
17
+ * ``false`` - Does nothing. Respects the presence of a trailing forward flash.
18
+ * ``config[:static_dir] = 'public'``
19
+ The directory Scorched should serve static files from. Should be set to false if the web server or some other middleware is serving static files.
20
+ * ``config[:logger] = Logger.new(STDOUT)`` - Currently does nothing until logging is added to Scorched.
21
+
22
+ The follow view configuration options can all be overriden when calling ``render``.
23
+
24
+ * ``view_config[:dir] = 'views'``
25
+ The directory containing all the view templates, relative to the current working directory.
26
+ * ``view_config[:layout] = false``
27
+ The default layout to use when rendering views.
28
+ * ``view_config[:engine] = :erb``
29
+ The default rendering engine. This is used when ``render`` is given a filename with no extension, or a string.
@@ -0,0 +1,92 @@
1
+ Routing
2
+ =======
3
+
4
+ When Scorched receives a request, the first thing it does is iterate over it's internal mapping hash, looking for the any URL pattern that matches the current URL. If it finds an appropriate match, it invokes the ``call`` method on the target defined for that mapping, unless the target is a ``Proc``, in which case it's invoked via ``instance_exec`` to run it within the context of the controller instance.
5
+
6
+ Mappings can be defined manually using the ``map`` class method, also aliased as ``<<``. Besides the required URL pattern and target elements, a mapping can also define a priority, and one or more conditions. The example below demonstrates the use of all of them.
7
+
8
+ # ruby
9
+ map pattern: '/', priority: -99, conditions: {methods: ['POST', 'PUT', 'DELETE']}, target: proc { |env|
10
+ [200, {}, 'Bugger off']
11
+ }
12
+
13
+ The position the new mapping is inserted into the mapping hash is determined by it's priority, and the priority of the mappings already defined. This avoids re-sorting the mapping hash every time it's added to. This isn't a performance consideration, but is required to maintain the natural insert order of the mappings which have identical priorities (such as the default 0).
14
+
15
+ A ``mapping`` method is also provided as means to access all defined mappings on a controller, but it should be considered read-only for the reasons just stated.
16
+
17
+ Route Helpers
18
+ -------------
19
+ Adding mappings manually can be a little verbose and painful, which is why Scorched includes a bunch of route helpers which are used in most code examples.
20
+
21
+ The main route helper which all others delegate to, is the ``route`` class method. Here's what it looks like in both it's simple and advance form:
22
+
23
+ # ruby
24
+ route '/' do
25
+ 'Well hello there'
26
+ end
27
+
28
+ route '/*', 5, methods: ['POST', 'PUT', 'DELETE'] do |capture|
29
+ "Hmm trying to change #{capture} I see"
30
+ end
31
+
32
+ You can see pretty clearly how these examples correspond to the pattern, priority, conditions and target options of a manual mapping. The pattern, priority and conditions behave exactly as they do for a manual mapping, with a couple of exceptions.
33
+
34
+ The first exception is that the pattern must match to the end of the request path. This is mentioned in the _pattern matching_ section below.
35
+
36
+ The other more notable exception is in how the given block is treated. The block given to the route helper is wrapped in another proc. The wrapping proc does a couple of things. It first sends all the captures in the url pattern as argument to the given block, this is shown in the example above. The other thing it does is takes care of assigning the return value to the body of the response.
37
+
38
+ In the latter of the two examples above, a ``:methods`` condition defines what methods the route is intended to process. The first example has no such condition, so it accepts all HTTP methods. Typically however, a route will handle a single HTTP method, which is why Scorched also provides the convenience helpers: ``get``, ``post``, ``put``, ``delete``, ``head``, ``options``, and ``patch``. These methods automatically define the corresponding ``:method`` condition, with the ``get`` helper also including ``head`` as an accepted HTTP method.
39
+
40
+ Pattern Matching
41
+ ----------------
42
+ All patterns attempt to match the remaining unmatched portion of the _request path_; the _request path_ being Rack's
43
+ ``path_info`` request variable. The unmatched path will always begin with a forward slash if the previously matched portion of the path ended immediately before, or included as the last character, a forward slash. As an example, if the request was to "/article/21", then both "/article/" => "/21" and "/article" => "/21" would match.
44
+
45
+ All patterns must match from the beginning of the path. So even though the pattern "article" would match "/article/21", it wouldn't count as a match because the match didn't start at a non-zero offset.
46
+
47
+ If a pattern contains named captures, unnamed captures will be lost - this is how named regex captures work in Ruby. So if you name one capture, make sure you name any other captures you may want to access.
48
+
49
+ Patterns can be defined as either a String or Regexp.
50
+
51
+ ###String Patterns
52
+ String patterns are compiled into Regexp patterns corresponding to the following rules:
53
+
54
+ * `*` - Matches all characters excluding the forward slash.
55
+ * `**` - Matches all characters including the forward slash.
56
+ * `:param` - Same as `*` except the capture is named to whatever the string following the single-colon is.
57
+ * `::param` - Same as `**` except the capture is named to whatever the string following the double-colon is.
58
+ * `$` - If placed at the end of a pattern, the pattern only matches if it matches the entire path. For patterns defined using the route helpers, e.g. ``Controller.route``, ``Controller.get``, this is implied.
59
+
60
+ ###Regex Patterns
61
+ Regex patterns offer more power and flexibility than string patterns (naturally). The rules for Regex patterns are identical to String patterns, e.g. they must match from the beginning of the path, etc.
62
+
63
+
64
+ Conditions
65
+ ----------
66
+ Conditions are essentially just pre-requisites that must be met before a mapping is invoked to handle the current request. They're implemented as ``Proc`` objects which take a single argument, and return true if the condition is satisfied, or false otherwise. Scorched comes with a number of pre-defined conditions included, many of which are provided by _rack-accept_ - one of the few dependancies of Scorched.
67
+
68
+ * ``:charset`` - Character sets accepted by the client.
69
+ * ``:encoding`` - Encodings accepted by the client.
70
+ * ``:host`` - The host name (i.e. domain name) used in the request.
71
+ * ``:language`` - Languages accepted by the client.
72
+ * ``:media_type`` - Media types (i.e. content types) accepted by the client.
73
+ * ``:methods`` - The request method used, e.g. GET, POST, PUT, ...
74
+ * ``:user_agent`` - The user agent string provided with the request. Takes a Regexp or String.
75
+ * ``:status`` - The response status of the request. Intended for use by _after_ filters.
76
+
77
+ Like configuration options, conditions are implemented using the ``Scorched::Options`` class, so they're inherited and be overridable by child classes. You may easily add your own conditions as the example below demonstrates.
78
+
79
+ # ruby
80
+ condition[:has_permission] = proc { |v|
81
+ user.has_permission == v
82
+ }
83
+
84
+ get '/', has_permission: true do
85
+ 'Welcome'
86
+ end
87
+
88
+ get '/', has_permission: false do
89
+ 'Forbidden'
90
+ end
91
+
92
+ Each of the built-in conditions can take a single value, or an array of values, with the exception of the ``:host`` and ``:user_agent`` conditions which support Regexp patterns.
@@ -0,0 +1,30 @@
1
+ Requests and Responses
2
+ ======================
3
+ One of the first things a controller does when it instantiates itself, is make the Rack environment hash accessible via the ``env`` helper, as well as make available a ``Scorched::Request`` and ``Scorched::Response`` object under the respective ``request`` and ``response`` methods.
4
+
5
+ The ``Scorched::Request`` and ``Scorched::Response`` classes are children of the corresponding _Rack_ request and response classes, with a little extra functionality tacked on.
6
+
7
+ The _request_ object makes accessible all the information associated with the current request, such as the GET and POST data, server and environment information, request headers, and so on. The _response_ is much the same, but in reverse. You'll use the _response_ object to set response headers and manipulate the body of the response.
8
+
9
+ Refer to the _Rack_ documentation for more information on the ``Rack::Request`` and ``Rack::Response`` classes.
10
+
11
+
12
+ Scorched Extras
13
+ ---------------
14
+ As mentioned, Scorched tacks a few extras onto it's ``Scorched::Request`` and ``Scorched::Response`` classes. Most of these extras were added as a requirement of the Scorched controller, but they're just as useful to other developers.
15
+
16
+ Refer to the generated API documentation for ``Scorched::Request`` and ``Scorched::Response``.
17
+
18
+
19
+ Halting Requests
20
+ ----------------
21
+ There may be instances we're you want to shortcut out-of processing the current request. The ``halt`` method allows you to do this, though it's worth clarifying its behaviour.
22
+
23
+ When ``halt`` is called within a route, it simply exists out of that route, and begins processing any _after_ filters. Halt can also be used within a _before_ or _after_ filter, in which case any remaining filters in the current controller are skipped.
24
+
25
+ Calls to ``halt`` don't propagate up the controller chain. They're local to the controller. A call to ``halt`` is equivalent to doing a ``throw :halt``. Calling ``halt`` is often preferred though because as well as being shorter, it can take an optional argument to set the response status, which is something you typically want to do when halting a request.
26
+
27
+
28
+ Redirections
29
+ ------------
30
+ A common requirement of many applications is to redirect requests to another URL based on some kind of condition. Scorched offers the very simple ``redirect`` method which takes one argument - the URL to redirect to. Like ``halt`` it's mostly a convenience method. It sets the _Location_ header of the response before halting the request.
@@ -0,0 +1,45 @@
1
+ Filters
2
+ =======
3
+ Filters serve as a handy place to put functionality and behaviour that's common to a set of routes, or for that matter, a whole website or application. Filters are executed in the context of the controller; the same context as routes. Filters are also inheritable, meaning sub-classes inherit the filters of their parent - this inheritance is enabled through the use of the ``Scorched::Collection`` class, and is implemented such that each filter will only run once per-request.
4
+
5
+ There are currently two types of filter in Scorched, both of which are documented below.
6
+
7
+
8
+ Before and After Filters
9
+ ------------------------
10
+ Before and After filters allow pre- and post-processing of requests. They are executed before and after each request, respectively.
11
+
12
+ # ruby
13
+ before do
14
+ raise Error, "Must be logged in to access this site" unless session[:logged_in] == true
15
+ end
16
+
17
+ Like routes, filters can have conditions defined on them, for example:
18
+
19
+ # ruby
20
+ after media_type: 'application/json' do
21
+ response.body.to_json!
22
+ end
23
+
24
+ Before and after filters run even if no route within the controller matches. This makes them suitable for handling 404 errors for example.
25
+
26
+ # ruby
27
+ after status: 404 do
28
+ response.body = render(:not_found)
29
+ end
30
+
31
+ Your imagination is the only limitation.
32
+
33
+
34
+ Error Filters
35
+ -------------
36
+ Error filters are processed like regular filters, except they're called whenever an exception occurs. If an error filter returns false, it's assumed to be unhandled, and the next error filter is called. This continues until one of the following is true, 1) an error filter returns true, in which case the exception is assumed to be handled, 2) an error filter raises an exception itself, or 3) there are no more error handlers defined on the current controller, in which case the exception is re-raised.
37
+
38
+ Error filters can handle exceptions raised from within the request target, as well as those raised within _before_ and _after_ filters. The way error filters have been implemented, allows exceptions raised within the request target to be handled before running the _after_ filters. This means that _after_ filters are still run as long as exceptions that occurred within the request target are handled.
39
+
40
+ Error filters can target only specific types of exception class, in much the same way as a typical Ruby rescue block.
41
+
42
+ # ruby
43
+ error PermissionError do |e|
44
+ flash[:error] = "You do not have the appropriate permission to perform that action: #{e.message}"
45
+ end
@@ -0,0 +1,18 @@
1
+ Middleware
2
+ ==========
3
+
4
+ While middleware can be added in your _rackup_ file to wrap your Scorched application, it can be more desirable to add the middleware at the controller level. Scorched itself requires this as it needs to include a set of Rack middleware out-of-the-box. Developers can't be expected to manually add these to their rackup file for every Scorched application.
5
+
6
+ Like _filters_, middleware is inheritable thanks to it's use of the ``Scorched::Collection`` class. Also like _filters_, middleware proc's are only run once per request, which prevents unintended double-loading of middleware.
7
+
8
+ Adding middleware to a Scorched controller involves pushing a proc onto the end of the middleware collection, accessible via the ``middleware`` accessor method. The given proc is ``instance_exec``'d in the context of a Rack builder object, and so can be used for more than just loading middleware.
9
+
10
+ # ruby
11
+ middleware << proc do
12
+ use Rack::Session::Cookie, secret: 'blah'
13
+ # Stolen from Rack's own documentation...
14
+ map "/lobster" do
15
+ use Rack::Lint
16
+ run Rack::Lobster.new
17
+ end
18
+ end
@@ -0,0 +1,89 @@
1
+ Request and Session Data
2
+ ========================
3
+
4
+ GET and POST Data
5
+ -----------------
6
+ Many ruby frameworks provide helpers for accessing GET and POST data via some kind of generic accessor, such as a ``params`` method, but Rack already provides this functionality out of the box, and more.
7
+
8
+ # ruby
9
+ post '/' do
10
+ request.GET['view'] # Key/value pairs submitted in the query string of the URL.
11
+ request.POST['username'] # Key/value pairs submitted in the payload of a POST request.
12
+ request.params[:username] # A merged hash of GET and POST data.
13
+ request[:username] # Shortcut to merged hash of GET and POST data.
14
+ end
15
+
16
+ Uploaded files are also accessible as ordinary fields, except the associated value is a hash of properties, instead of a string. An example of an application that accepts file uploads is included in the "examples" directory of the Scorched git repository.
17
+
18
+ Cookies
19
+ -------
20
+ While Rack provides a relatively simple means of setting, retrieving and deleting cookies, Scorched aggregates those three actions into a single method, ``cookie``, for the sake of brevity and simplicity.
21
+
22
+ # ruby
23
+ def '/' do
24
+ cookie :previous_page # Retrieves the cookie.
25
+ cookie :previous_page, '/search' # Sets the cookie
26
+ cookie :previous_page, nil # Deletes the cookies
27
+ end
28
+
29
+ For each of the above lines, the corresponding Rack methods are called, e.g. ``Rack::Requeste#cookies``, ``Rack::Response#set_cookie`` and ``Rack::Response#delete_cookie``. The values for setting and deleting a cookie can also be a hash, like ``set_cookie`` and ``delete_cookie`` can take directly. Deleting still works when a Hash is provided, as long as the ``value`` property is nil.
30
+
31
+ # ruby
32
+ def '/' do
33
+ cookie :view, path: '/account', value: 'datasheet' # Sets the cookie
34
+ cookie :view, path: '/account', value: nil # Deletes the cookie
35
+ end
36
+
37
+
38
+ Sessions
39
+ --------
40
+ Sessions are completely handled by Rack. For conveniance, a ``session`` helper is provided. This merely acts as an alias to ``request['rack.session']`` however. It was raise an exception if called without any Rack session middleware loaded, such as ``Rack::Session::Cookie``.
41
+
42
+ # ruby
43
+ class App < Scorched::Controller
44
+ middleware << proc {
45
+ use Rack::Session::Cookie, secret: 'blah'
46
+ }
47
+
48
+ get '/' do
49
+ session['logged_in'] ? 'You're currently logged in.' : 'Please login.'
50
+ end
51
+ end
52
+
53
+ ###Flash
54
+ A common requirement for websites, and especially web applications, is to provide a message on the next page load corresponding to an action that a user just performed. A common framework idiom that Scorched happily implements are flash session variables - special session data that lives for only a single page load.
55
+
56
+ This isn't as trivial to implement as it may sound at a glance, which is why Scorched provides this helper.
57
+
58
+ # ruby
59
+ get '/' do
60
+ "<span class="success">#{flash[:success]}</span>" if flash[:success]
61
+ end
62
+
63
+ post '/login' do
64
+ flash[:success] = 'Logged in successfully.'
65
+ end
66
+
67
+ The flash helper allows multiple sets of flash session data to be stored under different names. Because of how flash sessions are implemented, they're only deleted on the next page load if that particular flash data set is accessed. These properties of flash sessions can satisfy some interesting use cases. Here's a very uninteresting example:
68
+
69
+ # ruby
70
+ class App < Scorched::Controller
71
+ get '/' do
72
+ "<span class="success">#{flash[:success]}</span>" if flash[:success]
73
+ end
74
+
75
+ post '/login' do
76
+ flash[:success] = 'Logged in successfully.'
77
+ if user.membership_type == 'vip' && user.membership_expiry < 5
78
+ flash(:vip)[:warning] = 'Your VIP membership is about to expire, please renew it.'
79
+ end
80
+ end
81
+
82
+ controller pattern: '/vip' do
83
+ get '/' do
84
+ "<span class="warning">#{flash(:vip)[:warning]}</span>" if flash(:vip)[:warning]
85
+ end
86
+ end
87
+ end
88
+
89
+ In the rather contrived example above, when a VIP user logs in, a message is generated and stored as a flash session variable in the ``:vip`` flash data set. Because the ``:vip`` flash data set isn't accessed on the main page, it lives on until it's finally re-accessed on the VIP page.
@@ -0,0 +1,4 @@
1
+ Views
2
+ =====
3
+
4
+ _More documentation coming soon. Until then, as a tip, the whole view infrastructure of Scorched is implemented as the ``render`` method._
@@ -0,0 +1,16 @@
1
+ Sharing and Managing Request State
2
+ ==================================
3
+ Because Scorched allows sub-controllers to an arbitrary depth and treats all controllers equal (i.e it has no concept of a _root_ controller), it means instance variables and the likes cannot be used to track, maintain or share request state between controllers. I mention this because instance variables are the most common way to manage the request state in frameworks such as Sinatra.
4
+
5
+ The only data exchanged between controllers is the Rack environment hash. Using the Rack environment hash is the only thread-safe way to manage the request state between controllers. As an example use case, consider the Scorched implementation of flash session data. Flash session data must be shared between controllers throughout the life of the request. The rack environment hash is the only suitable place to store this data.
6
+
7
+ The Rack idiom is to namespace your keys, typically with your project name, using dots as delimiters. You can further group your keys using more dot-delimited namespaces. Perhaps an example is due:
8
+
9
+ # ruby
10
+ before user_agent: /MSIE|Windows/ do
11
+ env['myapp.dangerous_request'] = true
12
+ end
13
+
14
+ get '/' do
15
+ "Welcome to #{env['myapp.settings.site_name']}!"
16
+ end
@@ -0,0 +1,45 @@
1
+ Be Creative
2
+ ===========
3
+ Getting the most out of Scorched requires a bit of creative thinking. A couple of examples are given below.
4
+
5
+ Effortless REST
6
+ ---------------
7
+ An DRY way to serve multiple content-types:
8
+
9
+ # ruby
10
+ class App < Scorched::Controller
11
+ def view(view = nil)
12
+ view ? env['app.view'] = view : env['app.view']
13
+ end
14
+
15
+ after do
16
+ data = response.body.join('')
17
+ response['Content-type'] = 'text/html'
18
+ if check_condition?(:media_type, 'text/html')
19
+ response.body = render(view, locals: {data: data})
20
+ elsif check_condition?(:media_type, 'application/json')
21
+ response['Content-type'] = 'application/json'
22
+ response.body = data.to_json
23
+ elsif check_condition?(:media_type, 'application/pdf')
24
+ response['Content-type'] = 'text/plain'
25
+ response.status = 406
26
+ response.body = 'PDF rendering service currently unavailable.'
27
+ else
28
+ response.body = render(view, locals: {data: data})
29
+ end
30
+ end
31
+
32
+ get '/' do
33
+ view :index
34
+ [
35
+ {title: 'Sweet Purple Unicorns', date: '08/03/2013'},
36
+ {title: 'Mellow Grass Men', date: '21/03/2013'}
37
+ ]
38
+ end
39
+ end
40
+
41
+
42
+ Authentication and Permissions
43
+ ------------------------------
44
+
45
+ _Example coming soon._
@@ -0,0 +1,44 @@
1
+ require File.expand_path('../../lib/scorched.rb', __FILE__)
2
+
3
+ class MediaTypesExample < Scorched::Controller
4
+
5
+ get '/' do
6
+ response['Content-Type'] = 'text/html'
7
+ <<-HTML
8
+ <form method="POST" action="#{absolute(request.matched_path)}" enctype="multipart/form-data">
9
+ <input type="file" name="example_file" />
10
+ <input type="submit" value="Submit" />
11
+ </form>
12
+ HTML
13
+ end
14
+
15
+ post '/' do
16
+ example_file = request[:example_file]
17
+ <<-HTML
18
+ We know the following about the received file.
19
+ <ul>
20
+ <li><strong>Name:</strong> #{example_file[:filename]}</li>
21
+ <li><strong>Supposed Type:</strong> #{example_file[:type]}</li>
22
+ <li><strong>Actual Type:</strong> <em>Just pretend we are using MimeMagic</em></li>
23
+ <li><strong>Size:</strong> #{format_byte_size example_file[:tempfile].size}</li>
24
+ </ul>
25
+ HTML
26
+ end
27
+
28
+ after do
29
+ response['Content-Type'] = 'text/html; charset=utf-8' unless response['Content-Type']
30
+ end
31
+
32
+ # My self-proclaimed awesome byte size formatter: https://gist.github.com/Wardrop/4952405
33
+ def format_byte_size(bytes, opts = {})
34
+ opts = {binary: true, precision: 2, as_bits: false}.merge(opts)
35
+ suffixes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB','EB', 'ZB', 'YB']
36
+ opts[:as_bits] && suffixes[0] = 'bits' && suffixes[1..-1].each { |v| v.downcase! } && bytes *= 8
37
+ opts[:binary] ? base = 1024 : (base = 1000) && suffixes[1..-1].each { |v| v.insert(1,'i') }
38
+ exp = Math.log(bytes, base).floor
39
+ "#{(bytes.to_f / (base ** exp)).round(opts[:precision])} #{suffixes[exp]}"
40
+ end
41
+
42
+ end
43
+
44
+ run MediaTypesExample
@@ -1,2 +1,26 @@
1
- require './media_types.rb'
1
+ require 'json'
2
+ require File.expand_path('../../lib/scorched.rb', __FILE__)
3
+
4
+ class MediaTypesExample < Scorched::Controller
5
+
6
+ get '/', media_type: 'text/html' do
7
+ <<-HTML
8
+ <div><strong>Name: </strong> John Falkon</div>
9
+ <div><strong>Age: </strong> 39</div>
10
+ <div><strong>Occupation: </strong> Carpet Cleaner</div>
11
+ HTML
12
+ end
13
+
14
+ controller pattern: '/*' do
15
+ get '/*' do |*captures|
16
+ request.breadcrumb.inspect
17
+ end
18
+ end
19
+
20
+ get '/,', media_type: 'application/json' do
21
+ {name: 'John Falkon', age: 39, occupation: 'Carpet Cleaner'}.to_json
22
+ end
23
+
24
+ end
25
+
2
26
  run MediaTypesExample
@@ -15,7 +15,7 @@ module Scorched
15
15
  }
16
16
 
17
17
  view_config << {
18
- :dir => 'views', # The directory containing all the view templates, relative to the application root.
18
+ :dir => 'views', # The directory containing all the view templates, relative to the current working directory.
19
19
  :layout => false, # The default layout template to use, relative to the view directory. Set to false for no default layout.
20
20
  :engine => :erb
21
21
  }
@@ -84,17 +84,17 @@ module Scorched
84
84
  # Generates and assigns mapping hash from the given arguments.
85
85
  #
86
86
  # Accepts the following keyword arguments:
87
- # :url - The url pattern to match on. Required.
87
+ # :pattern - The url pattern to match on. Required.
88
88
  # :target - A proc to execute, or some other object that responds to #call. Required.
89
89
  # :priority - Negative or positive integer for giving a priority to the mapped item.
90
90
  # :conditions - A hash of condition:value pairs
91
91
  # Raises ArgumentError if required key values are not provided.
92
- def map(url: nil, priority: nil, conditions: {}, target: nil)
93
- raise ArgumentError, "Mapping must specify url pattern and target" unless url && target
92
+ def map(pattern: nil, priority: nil, conditions: {}, target: nil)
93
+ raise ArgumentError, "Mapping must specify url pattern and target" unless pattern && target
94
94
  priority = priority.to_i
95
95
  insert_pos = mappings.take_while { |v| priority <= v[:priority] }.length
96
96
  mappings.insert(insert_pos, {
97
- url: compile(url),
97
+ pattern: compile(pattern),
98
98
  priority: priority,
99
99
  conditions: conditions,
100
100
  target: target
@@ -113,17 +113,17 @@ module Scorched
113
113
  # (or inherits from) a Scorched::Controller.
114
114
  def controller(parent_class = self, **mapping, &block)
115
115
  c = Class.new(parent_class, &block)
116
- self << {url: '/', target: c}.merge(mapping)
116
+ self << {pattern: '/', target: c}.merge(mapping)
117
117
  c
118
118
  end
119
119
 
120
120
  # Generates and returns a new route proc from the given block, and optionally maps said proc using the given args.
121
- def route(url = nil, priority = nil, **conditions, &block)
121
+ def route(pattern = nil, priority = nil, **conditions, &block)
122
122
  target = lambda do |env|
123
- env['rack.response'].body << instance_exec(*env['rack.request'].captures, &block)
124
- env['rack.response']
123
+ env['scorched.response'].body = instance_exec(*env['scorched.request'].captures, &block)
124
+ env['scorched.response']
125
125
  end
126
- self << {url: compile(url, true), priority: priority, conditions: conditions, target: target} if url
126
+ self << {pattern: compile(pattern, true), priority: priority, conditions: conditions, target: target} if pattern
127
127
  target
128
128
  end
129
129
 
@@ -151,11 +151,11 @@ module Scorched
151
151
  # Parses and compiles the given URL string pattern into a regex if not already, returning the resulting regexp
152
152
  # object. Accepts an optional _match_to_end_ argument which will ensure the generated pattern matches to the end
153
153
  # of the string.
154
- def compile(url, match_to_end = false)
155
- return url if Regexp === url
156
- raise Error, "Can't compile URL of type #{url.class}. Must be String or Regexp." unless String === url
157
- match_to_end = !!url.sub!(/\$$/, '') || match_to_end
158
- pattern = url.split(%r{(\*{1,2}|(?<!\\):{1,2}[^/*$]+)}).each_slice(2).map { |unmatched, match|
154
+ def compile(pattern, match_to_end = false)
155
+ return pattern if Regexp === pattern
156
+ raise Error, "Can't compile URL of type #{pattern.class}. Must be String or Regexp." unless String === pattern
157
+ match_to_end = !!pattern.sub!(/\$$/, '') || match_to_end
158
+ regex_pattern = pattern.split(%r{(\*{1,2}|(?<!\\):{1,2}[^/*$]+)}).each_slice(2).map { |unmatched, match|
159
159
  Regexp.escape(unmatched) << begin
160
160
  if %w{* **}.include? match
161
161
  match == '*' ? "([^/]+)" : "(.+)"
@@ -166,8 +166,8 @@ module Scorched
166
166
  end
167
167
  end
168
168
  }.join
169
- pattern << '$' if match_to_end
170
- Regexp.new(pattern)
169
+ regex_pattern << '$' if match_to_end
170
+ Regexp.new(regex_pattern)
171
171
  end
172
172
  end
173
173
 
@@ -179,8 +179,8 @@ module Scorched
179
179
  define_singleton_method :env do
180
180
  env
181
181
  end
182
- env['rack.request'] ||= Request.new(env)
183
- env['rack.response'] ||= Response.new
182
+ env['scorched.request'] ||= Request.new(env)
183
+ env['scorched.response'] ||= Response.new
184
184
  end
185
185
 
186
186
  def action
@@ -232,7 +232,7 @@ module Scorched
232
232
  to_match = to_match.chomp('/') if config[:strip_trailing_slash] == :ignore && to_match =~ %r{./$}
233
233
  matches = []
234
234
  mappings.each do |m|
235
- m[:url].match(to_match) do |match_data|
235
+ m[:pattern].match(to_match) do |match_data|
236
236
  if match_data.pre_match == ''
237
237
  if check_conditions?(m[:conditions])
238
238
  if match_data.names.empty?
@@ -240,7 +240,7 @@ module Scorched
240
240
  else
241
241
  captures = Hash[match_data.names.map{|v| v.to_sym}.zip match_data.captures]
242
242
  end
243
- matches << {mapping: m, captures: captures, url: match_data.to_s}
243
+ matches << {mapping: m, captures: captures, path: match_data.to_s}
244
244
  break if short_circuit
245
245
  end
246
246
  end
@@ -274,12 +274,12 @@ module Scorched
274
274
 
275
275
  # Convenience method for accessing Rack request.
276
276
  def request
277
- env['rack.request']
277
+ env['scorched.request']
278
278
  end
279
279
 
280
280
  # Convenience method for accessing Rack response.
281
281
  def response
282
- env['rack.response']
282
+ env['scorched.response']
283
283
  end
284
284
 
285
285
  # Convenience method for accessing Rack session.
@@ -375,7 +375,6 @@ module Scorched
375
375
  else
376
376
  uri.to_s
377
377
  end
378
-
379
378
  end
380
379
 
381
380
  # Takes an optional path, relative to the applications root URL, and returns an absolute path.
@@ -1,6 +1,7 @@
1
1
  module Scorched
2
2
  class Request < Rack::Request
3
- # Keeps track of the matched URL portions and what object handled them.
3
+ # Keeps track of the matched URL portions and what object handled them. Useful for debugging and building
4
+ # breadcrumb navigation.
4
5
  def breadcrumb
5
6
  env['breadcrumb'] ||= []
6
7
  end
@@ -10,17 +11,20 @@ module Scorched
10
11
  breadcrumb.last ? breadcrumb.last[:captures] : []
11
12
  end
12
13
 
14
+ # Returns an array of capture arrays; one for each mapping that's been hit during the request processing so far.
13
15
  def all_captures
14
16
  breadcrumb.map { |v| v[:captures] }
15
17
  end
16
18
 
19
+ # The portion of the path that's currently been matched by one or more mappings.
17
20
  def matched_path
18
- join_paths(breadcrumb.map{|v| v[:url]})
21
+ join_paths(breadcrumb.map{|v| v[:path]})
19
22
  end
20
23
 
24
+ # The remaining portion of the path that has yet to be matched by any mappings.
21
25
  def unmatched_path
22
26
  path = path_info.partition(matched_path).last
23
- path[0,0] = '/' unless path[0] == '/'
27
+ path[0,0] = '/' if path.empty? || matched_path[-1] == '/'
24
28
  path
25
29
  end
26
30
 
@@ -14,5 +14,18 @@ module Scorched
14
14
  self.status, @header, self.body = response
15
15
  end
16
16
  end
17
+
18
+ # Automatically wraps the assigned value in an array if it doesn't respond to ``each``.
19
+ def body=(value)
20
+ super(value.respond_to?(:each) ? value : [value])
21
+ end
22
+
23
+ def finish(*args, &block)
24
+ self['Content-Type'] ||= 'text/html;charset=utf-8'
25
+ super
26
+ end
27
+
28
+ alias :to_a :finish
29
+ alias :to_ary :finish
17
30
  end
18
31
  end
@@ -1,3 +1,3 @@
1
1
  module Scorched
2
- VERSION = '0.5.1'
2
+ VERSION = '0.5.2'
3
3
  end
@@ -24,13 +24,13 @@ module Scorched
24
24
  end
25
25
 
26
26
  it "handles a root rack call correctly" do
27
- app << {url: '/$', target: generic_handler}
27
+ app << {pattern: '/$', target: generic_handler}
28
28
  response = rt.get '/'
29
29
  response.status.should == 200
30
30
  end
31
31
 
32
32
  it "does not maintain state between requests" do
33
- app << {url: '/state', target: proc { |env| [200, {}, [@state = 1 + @state.to_i]] }}
33
+ app << {pattern: '/state', target: proc { |env| [200, {}, [@state = 1 + @state.to_i]] }}
34
34
  response = rt.get '/state'
35
35
  response.body.should == '1'
36
36
  response = rt.get '/state'
@@ -39,7 +39,7 @@ module Scorched
39
39
 
40
40
  it "raises exception when invalid mapping hash given" do
41
41
  expect {
42
- app << {url: '/'}
42
+ app << {pattern: '/'}
43
43
  }.to raise_error(ArgumentError)
44
44
  expect {
45
45
  app << {target: generic_handler}
@@ -49,70 +49,90 @@ module Scorched
49
49
 
50
50
  describe "URL matching" do
51
51
  it 'always matches from the beginning of the URL' do
52
- app << {url: 'about', target: generic_handler}
52
+ app << {pattern: 'about', target: generic_handler}
53
53
  response = rt.get '/about'
54
54
  response.status.should == 404
55
55
  end
56
56
 
57
57
  it "matches eagerly by default" do
58
- request = nil
59
- app << {url: '/*', target: proc do |env|
60
- request = env['rack.request']; [200, {}, ['ok']]
58
+ req = nil
59
+ app << {pattern: '/*', target: proc do |env|
60
+ req = request; [200, {}, ['ok']]
61
61
  end}
62
62
  response = rt.get '/about'
63
- request.captures.should == ['about']
63
+ req.captures.should == ['about']
64
64
  end
65
65
 
66
66
  it "can be forced to match end of URL" do
67
- app << {url: '/about$', target: generic_handler}
67
+ app << {pattern: '/about$', target: generic_handler}
68
68
  response = rt.get '/about/us'
69
69
  response.status.should == 404
70
- app << {url: '/about', target: generic_handler}
70
+ app << {pattern: '/about', target: generic_handler}
71
71
  response = rt.get '/about/us'
72
72
  response.status.should == 200
73
73
  end
74
74
 
75
+ it "unmatched path doesn't always begin with a forward slash" do
76
+ gh = generic_handler
77
+ app << {pattern: '/ab', target: Class.new(Scorched::Controller) do
78
+ map(pattern: 'out', target: gh)
79
+ end}
80
+ rt.get('/about').body.should == "ok"
81
+ end
82
+
83
+ it "unmatched path begins with forward slash if last match was up to or included a forward slash" do
84
+ gh = generic_handler
85
+ app << {pattern: '/about/', target: Class.new(Scorched::Controller) do
86
+ map(pattern: '/us', target: gh)
87
+ end}
88
+ app << {pattern: '/contact', target: Class.new(Scorched::Controller) do
89
+ map(pattern: '/us', target: gh)
90
+ end}
91
+ rt.get('/about/us').body.should == "ok"
92
+ rt.get('/contact/us').body.should == "ok"
93
+ end
94
+
75
95
  it "can match anonymous wildcards" do
76
- request = nil
77
- app << {url: '/anon/*/**', target: proc do |env|
78
- request = env['rack.request']; [200, {}, ['ok']]
96
+ req = nil
97
+ app << {pattern: '/anon/*/**', target: proc do |env|
98
+ req = request; [200, {}, ['ok']]
79
99
  end}
80
100
  response = rt.get '/anon/jeff/has/crabs'
81
- request.captures.should == ['jeff', 'has/crabs']
101
+ req.captures.should == ['jeff', 'has/crabs']
82
102
  end
83
103
 
84
104
  it "can match named wildcards (ignoring anonymous captures)" do
85
- request = nil
86
- app << {url: '/anon/:name/*/::infliction', target: proc do |env|
87
- request = env['rack.request']; [200, {}, ['ok']]
105
+ req = nil
106
+ app << {pattern: '/anon/:name/*/::infliction', target: proc do |env|
107
+ req = request; [200, {}, ['ok']]
88
108
  end}
89
109
  response = rt.get '/anon/jeff/smith/has/crabs'
90
- request.captures.should == {name: 'jeff', infliction: 'has/crabs'}
110
+ req.captures.should == {name: 'jeff', infliction: 'has/crabs'}
91
111
  end
92
112
 
93
113
  it "can match regex and preserve anonymous captures" do
94
- request = nil
95
- app << {url: %r{/anon/([^/]+)/(.+)}, target: proc do |env|
96
- request = env['rack.request']; [200, {}, ['ok']]
114
+ req = nil
115
+ app << {pattern: %r{/anon/([^/]+)/(.+)}, target: proc do |env|
116
+ req = request; [200, {}, ['ok']]
97
117
  end}
98
118
  response = rt.get '/anon/jeff/has/crabs'
99
- request.captures.should == ['jeff', 'has/crabs']
119
+ req.captures.should == ['jeff', 'has/crabs']
100
120
  end
101
121
 
102
122
  it "can match regex and preserve named captures (ignoring anonymous captures)" do
103
- request = nil
104
- app << {url: %r{/anon/(?<name>[^/]+)/([^/]+)/(?<infliction>.+)}, target: proc do |env|
105
- request = env['rack.request']; [200, {}, ['ok']]
123
+ req = nil
124
+ app << {pattern: %r{/anon/(?<name>[^/]+)/([^/]+)/(?<infliction>.+)}, target: proc do |env|
125
+ req = request; [200, {}, ['ok']]
106
126
  end}
107
127
  response = rt.get '/anon/jeff/smith/has/crabs'
108
- request.captures.should == {name: 'jeff', infliction: 'has/crabs'}
128
+ req.captures.should == {name: 'jeff', infliction: 'has/crabs'}
109
129
  end
110
130
 
111
131
  it "matches routes based on priority, otherwise giving precedence to those defined first" do
112
- app << {url: '/', priority: -1, target: proc { |env| self.class.mappings.shift; [200, {}, ['four']] }}
113
- app << {url: '/', target: proc { |env| self.class.mappings.shift; [200, {}, ['two']] }}
114
- app << {url: '/', target: proc { |env| self.class.mappings.shift; [200, {}, ['three']] }}
115
- app << {url: '/', priority: 2, target: proc { |env| self.class.mappings.shift; [200, {}, ['one']] }}
132
+ app << {pattern: '/', priority: -1, target: proc { |env| self.class.mappings.shift; [200, {}, ['four']] }}
133
+ app << {pattern: '/', target: proc { |env| self.class.mappings.shift; [200, {}, ['two']] }}
134
+ app << {pattern: '/', target: proc { |env| self.class.mappings.shift; [200, {}, ['three']] }}
135
+ app << {pattern: '/', priority: 2, target: proc { |env| self.class.mappings.shift; [200, {}, ['one']] }}
116
136
  rt.get('/').body.should == 'one'
117
137
  rt.get('/').body.should == 'two'
118
138
  rt.get('/').body.should == 'three'
@@ -128,14 +148,14 @@ module Scorched
128
148
  end
129
149
 
130
150
  it "executes route only if all conditions return true" do
131
- app << {url: '/', conditions: {methods: 'POST'}, target: generic_handler}
151
+ app << {pattern: '/', conditions: {methods: 'POST'}, target: generic_handler}
132
152
  response = rt.get "/"
133
153
  response.status.should == 404
134
154
  response = rt.post "/"
135
155
  response.status.should == 200
136
156
 
137
157
  app.conditions[:has_name] = proc { |name| request.GET['name'] }
138
- app << {url: '/about', conditions: {methods: ['GET', 'POST'], has_name: 'Ronald'}, target: generic_handler}
158
+ app << {pattern: '/about', conditions: {methods: ['GET', 'POST'], has_name: 'Ronald'}, target: generic_handler}
139
159
  response = rt.get "/about"
140
160
  response.status.should == 404
141
161
  response = rt.get "/about", name: 'Ronald'
@@ -143,15 +163,15 @@ module Scorched
143
163
  end
144
164
 
145
165
  it "raises exception when condition doesn't exist or is invalid" do
146
- app << {url: '/', conditions: {surprise_christmas_turkey: true}, target: generic_handler}
166
+ app << {pattern: '/', conditions: {surprise_christmas_turkey: true}, target: generic_handler}
147
167
  expect {
148
168
  rt.get "/"
149
169
  }.to raise_error(Scorched::Error)
150
170
  end
151
171
 
152
172
  it "falls through to next route when conditions are not met" do
153
- app << {url: '/', conditions: {methods: 'POST'}, target: proc { |env| [200, {}, ['post']] }}
154
- app << {url: '/', conditions: {methods: 'GET'}, target: proc { |env| [200, {}, ['get']] }}
173
+ app << {pattern: '/', conditions: {methods: 'POST'}, target: proc { |env| [200, {}, ['post']] }}
174
+ app << {pattern: '/', conditions: {methods: 'GET'}, target: proc { |env| [200, {}, ['get']] }}
155
175
  rt.get("/").body.should == 'get'
156
176
  rt.post("/").body.should == 'post'
157
177
  end
@@ -161,7 +181,7 @@ module Scorched
161
181
  it "allows end points to be defined more succinctly" do
162
182
  route_proc = app.route('/*', 2, methods: 'GET') { |capture| capture }
163
183
  mapping = app.mappings.first
164
- mapping.should == {url: mapping[:url], priority: 2, conditions: {methods: 'GET'}, target: route_proc}
184
+ mapping.should == {pattern: mapping[:pattern], priority: 2, conditions: {methods: 'GET'}, target: route_proc}
165
185
  rt.get('/about').body.should == 'about'
166
186
  end
167
187
 
@@ -170,7 +190,7 @@ module Scorched
170
190
  wrapped_block = app.route(&block)
171
191
  app.mappings.length.should == 0
172
192
  block.should_not == wrapped_block
173
- app << {url: '/*', target: wrapped_block}
193
+ app << {pattern: '/*', target: wrapped_block}
174
194
  rt.get('/turkey').body.should == 'turkey'
175
195
  end
176
196
 
@@ -208,7 +228,7 @@ module Scorched
208
228
  end
209
229
 
210
230
  it "should ignore the already matched portions of the path" do
211
- app.controller url: '/article' do
231
+ app.controller pattern: '/article' do
212
232
  get('/*') { |title| title }
213
233
  end
214
234
  rt.get('/article/hello-world').body.should == 'hello-world'
@@ -391,7 +411,7 @@ module Scorched
391
411
  get '/'do
392
412
  request.env['scorched.simple_counter']
393
413
  end
394
- controller url: '/sub_controller' do
414
+ controller pattern: '/sub_controller' do
395
415
  get '/' do
396
416
  request.env['scorched.simple_counter']
397
417
  end
@@ -549,6 +569,7 @@ module Scorched
549
569
  app.post('/') { cookie :test, 'hello' }
550
570
  app.post('/goodbye') { cookie :test, {value: 'goodbye', expires: Time.now() + 999999 } }
551
571
  app.delete('/') { cookie :test, nil }
572
+ app.delete('/alt') { cookie :test, {value: nil} }
552
573
 
553
574
  rt.get('/').body.should == ''
554
575
  rt.post('/')
@@ -557,6 +578,8 @@ module Scorched
557
578
  rt.get('/').body.should == 'goodbye'
558
579
  rt.delete('/')
559
580
  rt.get('/').body.should == ''
581
+ rt.delete('/alt')
582
+ rt.get('/').body.should == ''
560
583
  end
561
584
  end
562
585
 
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.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Wardrop
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-03-06 00:00:00.000000000 Z
11
+ date: 2013-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -91,11 +91,18 @@ files:
91
91
  - LICENSE
92
92
  - Milestones.md
93
93
  - README.md
94
- - docs/be_creative.md
95
- - docs/filters.md
96
- - docs/routing.md
97
- - docs/sharing_request_state.md
98
- - examples/media_types.rb
94
+ - docs/01_preface.md
95
+ - docs/02_fundamentals/01_the_controller.md
96
+ - docs/02_fundamentals/02_configuration.md
97
+ - docs/02_fundamentals/03_routing.md
98
+ - docs/02_fundamentals/04_requests_and_responses.md
99
+ - docs/02_fundamentals/05_filters.md
100
+ - docs/02_fundamentals/06_middleware.md
101
+ - docs/02_fundamentals/07_request_and_session_data.md
102
+ - docs/02_fundamentals/08_views.md
103
+ - docs/02_fundamentals/09_sharing_request_state.md
104
+ - docs/03_further_reading/be_creative.md
105
+ - examples/file_upload.ru
99
106
  - examples/media_types.ru
100
107
  - lib/scorched.rb
101
108
  - lib/scorched/collection.rb
@@ -1,32 +0,0 @@
1
- Effortless REST
2
- ---------------
3
- An easy way to serve multiple content-types:
4
-
5
- class App < Scorched::Controller
6
- def view(view = nil)
7
- view ? env['app.view'] = view : env['app.view']
8
- end
9
-
10
- after do
11
- if check_condition?(:media_type, 'text/html')
12
- response.body = [render(view)]
13
- if check_condition?(:media_type, 'application/json')
14
- response['Content-type'] = 'application/json'
15
- response.body = [response.body.to_json]
16
- elsif check_condition?(:media_type, 'application/pdf')
17
- response['Content-type'] = 'application/pdf'
18
- # response.body = [render_pdf(view)]
19
- else
20
- response.body = [render(view)]
21
- end
22
- end
23
-
24
- get '/' do
25
- view :index
26
- [
27
- {title: 'Sweet Purple Unicorns', date: '08/03/2013'},
28
- {title: 'Mellow Grass Men', date: '21/03/2013'}
29
- ]
30
- end
31
- end
32
-
@@ -1,8 +0,0 @@
1
-
2
- After Filters
3
- -------------
4
-
5
-
6
- Error Filters
7
- -------------
8
- Error filters are processed like regular filters, except they're called whenever an exception occurs. If an error filter returns false, it's assumed to be unhandled, and the next error filter is called. This continues until one of the following is true, 1) an error filter returns true, in which case the exception is assumed to be handled, 2) an error filter raises an exception itself, or 3) there are no more error handlers defined on the current controller, in which case the exception is re-raised.
@@ -1,29 +0,0 @@
1
- Patterns
2
- ========
3
- All patterns attempt to match the remaining unmatched portion of the request path (the request path being rack's
4
- `path_info` variable). The unmatched path will always begin with a forward slash if the previously matched portion of the
5
- path ended at a forward slash, regardless of whether it actually included the forward slash in the match, or if the
6
- forward slash was the next character. As an example, if the request was to "/article/21", then both "/article/" => "/21"
7
- and "/article" => "/21" would match.
8
-
9
- All patterns match from the beginning of the path. Matches that occur beyond the beginning of the path won't count as a
10
- match. So even though the pattern "article" would match "/article/21", it wouldn't count as a match because the match
11
- didn't start at a non-zero offset.
12
-
13
- If a pattern contains named captures, unnamed captures will be lost (this is how named regex captures work in Ruby). So
14
- if you name one capture, make sure you name any other captures you may want to access.
15
-
16
- String Patterns
17
- ---------------
18
- * `*` - Matches all characters excluding the forward slash.
19
- * `**` - Matches all characters including the forward slash.
20
- * `:param` - Same as `*` except the capture is named to whatever the string following the single-colon is.
21
- * `::param` - Same as `**` except the capture is named to whatever the string following the double-colon is.
22
- * `$` - If placed at the end of a pattern, the pattern only matches if it matches the entire path. For routes, this is
23
- implied, so it should only be explicitly added if trying to match a dollar sign character. If added anywhere other
24
- than the end of the pattern, it will match the dollar sign character.
25
-
26
-
27
- Regex Patterns
28
- --------------
29
- Regex patterns offer more power and flexibility than string patterns (naturally). To be continued... (captures, named captures, etc).
@@ -1,5 +0,0 @@
1
- Managing Request State
2
- ----------------------
3
- Because Scorched allows sub-controllers to an arbitrary depth and treats all controllers equal (i.e has no concept of a _root_ controller), it means instance variables cannot be used to track, maintain or share request state between controllers. I mention this because instance variables are the most common way to manage the request state in the likes of Sinatra.
4
-
5
- As an example, consider the Scorched implementation of flash session data. This data must be shared between controllers throughout the life of the request. The solution here, which you can find by looking at the Scorched source code, is to use the Rack environment hash. It's the one object that's accessible throughout the entire life-cycle of a request, and it's what you should use to maintain request state between controllers and even other embedded Rack applications.
@@ -1,18 +0,0 @@
1
- require 'json'
2
- require_relative '../lib/scorched.rb'
3
-
4
- class MediaTypesExample < Scorched::Controller
5
-
6
- get '/', media_type: 'text/html' do
7
- <<-HTML
8
- <div><strong>Name: </strong> John Falkon</div>
9
- <div><strong>Age: </strong> 39</div>
10
- <div><strong>Occupation: </strong> Carpet Cleaner</div>
11
- HTML
12
- end
13
-
14
- get '/,', media_type: 'application/json' do
15
- {name: 'John Falkon', age: 39, occupation: 'Carpet Cleaner'}.to_json
16
- end
17
-
18
- end