dyoder-waves 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/app/Rakefile +14 -0
- data/app/bin/waves-console +3 -0
- data/app/bin/waves-server +3 -0
- data/app/configurations/development.rb.erb +31 -0
- data/app/configurations/mapping.rb.erb +14 -0
- data/app/configurations/production.rb.erb +30 -0
- data/app/lib/application.rb.erb +3 -0
- data/app/startup.rb +5 -0
- data/app/templates/errors/not_found_404.mab +2 -0
- data/app/templates/errors/server_error_500.mab +2 -0
- data/app/templates/layouts/default.mab +14 -0
- data/bin/waves +66 -0
- data/bin/waves-console +4 -0
- data/bin/waves-server +4 -0
- data/lib/commands/waves-console.rb +24 -0
- data/lib/commands/waves-server.rb +55 -0
- data/lib/controllers/mixin.rb +158 -0
- data/lib/dispatchers/base.rb +52 -0
- data/lib/dispatchers/default.rb +67 -0
- data/lib/foundations/default.rb +28 -0
- data/lib/foundations/simple.rb +17 -0
- data/lib/helpers/common.rb +62 -0
- data/lib/helpers/form.rb +39 -0
- data/lib/helpers/formatting.rb +30 -0
- data/lib/helpers/model.rb +33 -0
- data/lib/helpers/view.rb +24 -0
- data/lib/layers/default_errors.rb +24 -0
- data/lib/layers/simple_errors.rb +17 -0
- data/lib/mapping/mapping.rb +252 -0
- data/lib/mapping/pretty_urls.rb +94 -0
- data/lib/renderers/erubis.rb +61 -0
- data/lib/renderers/markaby.rb +33 -0
- data/lib/renderers/mixin.rb +53 -0
- data/lib/runtime/application.rb +65 -0
- data/lib/runtime/configuration.rb +180 -0
- data/lib/runtime/console.rb +20 -0
- data/lib/runtime/debugger.rb +9 -0
- data/lib/runtime/logger.rb +52 -0
- data/lib/runtime/mime_types.rb +22 -0
- data/lib/runtime/request.rb +77 -0
- data/lib/runtime/response.rb +40 -0
- data/lib/runtime/response_mixin.rb +35 -0
- data/lib/runtime/response_proxy.rb +27 -0
- data/lib/runtime/server.rb +94 -0
- data/lib/runtime/session.rb +56 -0
- data/lib/tasks/cluster.rb +25 -0
- data/lib/tasks/gem.rb +31 -0
- data/lib/tasks/generate.rb +15 -0
- data/lib/utilities/inflect.rb +194 -0
- data/lib/utilities/integer.rb +17 -0
- data/lib/utilities/kernel.rb +34 -0
- data/lib/utilities/module.rb +17 -0
- data/lib/utilities/object.rb +17 -0
- data/lib/utilities/proc.rb +9 -0
- data/lib/utilities/string.rb +47 -0
- data/lib/utilities/symbol.rb +7 -0
- data/lib/verify/mapping.rb +29 -0
- data/lib/verify/request.rb +40 -0
- data/lib/views/mixin.rb +108 -0
- data/lib/waves.rb +80 -0
- metadata +260 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
module Waves
|
2
|
+
|
3
|
+
module Dispatchers
|
4
|
+
|
5
|
+
class NotFoundError < Exception ; end
|
6
|
+
|
7
|
+
class Redirect < Exception
|
8
|
+
attr_reader :path, :status
|
9
|
+
def initialize( path, status = '302' )
|
10
|
+
@path = path
|
11
|
+
@status = status
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# The Base dispatcher simply makes it easier to write dispatchers by inheriting
|
16
|
+
# from it. It creates a Waves request, ensures the request processing is done
|
17
|
+
# within a mutex, benchmarks the request processing, logs it, and handles common
|
18
|
+
# exceptions and redirects. Derived classes need only process the request within
|
19
|
+
# their +safe+ method, which takes a Waves::Request and returns a Waves::Response.
|
20
|
+
|
21
|
+
class Base
|
22
|
+
|
23
|
+
# Like any Rack application, Waves' dispatchers must provide a call method
|
24
|
+
# taking an +env+ parameter.
|
25
|
+
def call( env )
|
26
|
+
Waves::Application.instance.synchronize do
|
27
|
+
request = Waves::Request.new( env )
|
28
|
+
response = request.response
|
29
|
+
t = Benchmark.realtime do
|
30
|
+
begin
|
31
|
+
safe( request )
|
32
|
+
rescue Dispatchers::Redirect => redirect
|
33
|
+
response.status = redirect.status
|
34
|
+
response.location = redirect.path
|
35
|
+
end
|
36
|
+
end
|
37
|
+
Waves::Logger.info "#{request.method}: #{request.url} handled in #{(t*1000).round} ms."
|
38
|
+
response.finish
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Called by event driven servers like thin and ebb. Return true if
|
43
|
+
# the server should run the request in a separate thread.
|
44
|
+
def deferred?( env )
|
45
|
+
Waves::Application.instance.mapping.threaded?( env )
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Waves
|
2
|
+
|
3
|
+
module Dispatchers
|
4
|
+
|
5
|
+
#
|
6
|
+
# The default dispatcher essentially checks the application's mapping to see
|
7
|
+
# what to do with the request URL. It checks before and after filters (wrap
|
8
|
+
# filters are just a combination of both) as well as path and url mappings.
|
9
|
+
#
|
10
|
+
# The default dispatcher also attempts to set the content type based on the
|
11
|
+
# MIME type implied by any file extension used in the request URL using Mongrel's
|
12
|
+
# MIME types YAML file.
|
13
|
+
#
|
14
|
+
# You can write your own dispatcher and use it in your application's configuration
|
15
|
+
# file. By default, they use the default dispatcher, like this:
|
16
|
+
#
|
17
|
+
# application do
|
18
|
+
# use Rack::ShowExceptions
|
19
|
+
# run Waves::Dispatchers::Default.new
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
|
23
|
+
class Default < Base
|
24
|
+
|
25
|
+
# All dispatchers using the Dispatchers::Base to provide thread-safety, logging, etc.
|
26
|
+
# must provide a +safe+ method to handle creating a response from a request.
|
27
|
+
# Takes a Waves::Request and returns a Waves::Reponse
|
28
|
+
def safe( request )
|
29
|
+
|
30
|
+
begin
|
31
|
+
response = request.response
|
32
|
+
|
33
|
+
Waves::Application.instance.reload if Waves::Application.instance.debug?
|
34
|
+
response.content_type = Waves::Application.instance.config.mime_types[ request.path ] || 'text/html'
|
35
|
+
|
36
|
+
mapping = Waves::Application.instance.mapping[ request ]
|
37
|
+
|
38
|
+
mapping[:before].each do | block, args |
|
39
|
+
ResponseProxy.new(request).instance_exec(*args,&block)
|
40
|
+
end
|
41
|
+
|
42
|
+
request.not_found unless mapping[:action]
|
43
|
+
|
44
|
+
block, args = mapping[:action]
|
45
|
+
response.write( ResponseProxy.new(request).instance_exec(*args, &block) )
|
46
|
+
|
47
|
+
mapping[:after].each do | block, args |
|
48
|
+
ResponseProxy.new(request).instance_exec(*args,&block)
|
49
|
+
end
|
50
|
+
rescue Exception => e
|
51
|
+
handler = mapping[:handlers].detect do | exception, block, args |
|
52
|
+
e.is_a? exception
|
53
|
+
end
|
54
|
+
if handler
|
55
|
+
ResponseProxy.new(request).instance_exec(*handler[2], &handler[1])
|
56
|
+
else
|
57
|
+
raise e
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'layers/orm/sequel'
|
2
|
+
module Waves
|
3
|
+
module Foundations
|
4
|
+
module Default
|
5
|
+
|
6
|
+
def self.included( app )
|
7
|
+
|
8
|
+
app.instance_eval do
|
9
|
+
|
10
|
+
include Waves::Layers::Simple
|
11
|
+
include Waves::Layers::DefaultErrors
|
12
|
+
include Waves::Layers::MVC
|
13
|
+
include Waves::Layers::ORM::Sequel
|
14
|
+
|
15
|
+
# Set autoloading from default.rb files
|
16
|
+
#autoinit :Configurations do
|
17
|
+
# autoload_class true
|
18
|
+
#end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
Waves << app
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Waves
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
# Common helpers are helpers that are needed for just about any Web page. For example,
|
5
|
+
# each page will likely have a layout and a doctype.
|
6
|
+
|
7
|
+
module Common
|
8
|
+
|
9
|
+
DOCTYPES = {
|
10
|
+
:html3 => "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">\n",
|
11
|
+
:html4_transitional =>
|
12
|
+
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" " <<
|
13
|
+
"\"http://www.w3.org/TR/html4/loose.dtd\">\n",
|
14
|
+
:html4_strict =>
|
15
|
+
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" " <<
|
16
|
+
"\"http://www.w3.org/TR/html4/strict.dtd\">\n",
|
17
|
+
:html4_frameset =>
|
18
|
+
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Frameset//EN\" " <<
|
19
|
+
"\"http://www.w3.org/TR/html4/frameset.dtd\">\n",
|
20
|
+
:xhtml1_transitional =>
|
21
|
+
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" " <<
|
22
|
+
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n",
|
23
|
+
:xhtml1_strict =>
|
24
|
+
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " <<
|
25
|
+
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n",
|
26
|
+
:xhtml1_frameset =>
|
27
|
+
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN\" " <<
|
28
|
+
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">\n",
|
29
|
+
:xhtml2 => "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
|
30
|
+
}
|
31
|
+
|
32
|
+
# Invokes a layout view (i.e., a view from the layouts template directory), using
|
33
|
+
# the assigns parameter to define instance variables for the view. The block is
|
34
|
+
# evaluated and also passed into the view as the +layout_content+ instance variable.
|
35
|
+
#
|
36
|
+
# You can define a layout just by creating a template and then calling the
|
37
|
+
# +layout_content+ accessor when you want to embed the caller's content.
|
38
|
+
#
|
39
|
+
# == Example
|
40
|
+
#
|
41
|
+
# doctype :html4_transitional
|
42
|
+
# html do
|
43
|
+
# title @title # passed as an assigns parameter
|
44
|
+
# end
|
45
|
+
# body do
|
46
|
+
# layout_content
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
def layout( name, assigns = {}, &block )
|
50
|
+
assigns[ :layout_content ] = capture(&block)
|
51
|
+
self << Waves.application.views[:layouts].process( request ) do
|
52
|
+
send( name, assigns )
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# The doctype method simply generates a valid DOCTYPE declaration for your page.
|
57
|
+
# Valid options are defined in the +DOCTYPES+ constant.
|
58
|
+
def doctype(type) ; self << DOCTYPES[type||:html4_strict] ; end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/helpers/form.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Waves
|
2
|
+
|
3
|
+
module Helpers
|
4
|
+
|
5
|
+
# Form helpers are used in generating forms. Since Markaby already provides Ruby
|
6
|
+
# methods for basic form generation, the focus of this helper is on providing templates
|
7
|
+
# to handle things that go beyond the basics. You must define a form template
|
8
|
+
# directory with templates for each type of form element you wish to use. The names
|
9
|
+
# of the template should match the +type+ option provided in the property method.
|
10
|
+
#
|
11
|
+
# For example, this code:
|
12
|
+
#
|
13
|
+
# property :name => 'blog.title', :type => :text, :value => @blog.title
|
14
|
+
#
|
15
|
+
# will invoke the +text+ form view (the template in +templates/form/text.mab+),
|
16
|
+
# passing in the name ('blog.title') and the value (@blog.title) as instance variables.
|
17
|
+
#
|
18
|
+
module Form
|
19
|
+
|
20
|
+
# This method really is a place-holder for common wrappers around groups of
|
21
|
+
# properties. You will usually want to override this. As is, it simply places
|
22
|
+
# a DIV element with class 'properties' around the block.
|
23
|
+
def properties(&block)
|
24
|
+
div.properties do
|
25
|
+
yield
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Invokes the form view for the +type+ given in the option.
|
30
|
+
def property( options )
|
31
|
+
self << view( :form, options[:type], options )
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'redcloth'
|
2
|
+
module Waves
|
3
|
+
module Helpers
|
4
|
+
|
5
|
+
# Formatting helpers are used to convert specialized content, like Markaby or
|
6
|
+
# Textile, into valid HTML. It also provides common escaping functions.
|
7
|
+
module Formatting
|
8
|
+
|
9
|
+
# Escape a string as HTML content.
|
10
|
+
def escape_html(s); Rack::Utils.escape_html(s); end
|
11
|
+
|
12
|
+
# Escape a URI, converting quotes and spaces and so on.
|
13
|
+
def escape_uri(s); Rack::Utils.escape(s); end
|
14
|
+
|
15
|
+
# Treat content as Markaby and evaluate (only works within a Markaby template).
|
16
|
+
# Used to pull Markaby content from a file or database into a Markaby template.
|
17
|
+
def markaby( content ); self << eval( content ); end
|
18
|
+
|
19
|
+
# Treat content as Textile.
|
20
|
+
def textile( content )
|
21
|
+
return if content.nil? or content.empty?
|
22
|
+
( ::RedCloth::TEXTILE_TAGS << [ 96.chr, '&8216;'] ).each do |pat,ent|
|
23
|
+
content.gsub!( pat, ent.gsub('&','&#') )
|
24
|
+
end
|
25
|
+
self << ::RedCloth.new( content ).to_html
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Waves
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
# Model helpers allow you to directly access a model from within a view.
|
5
|
+
# This is useful when creating things like select boxes that need data
|
6
|
+
# from anther model. For example, a Markaby select box for authors might look like:
|
7
|
+
#
|
8
|
+
# select do
|
9
|
+
# all(:user).each do |user|
|
10
|
+
# option user.full_name, :value => user.id
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# You could also use these within a view class to keep model-based logic out
|
15
|
+
# of the templates themselves. For example, in the view class you might define
|
16
|
+
# a method called +authors+ that returns an array of name / id pairs. This could
|
17
|
+
# then be called from the template instead of the model helper.
|
18
|
+
#
|
19
|
+
module Model
|
20
|
+
|
21
|
+
# Just like model.all. Returns all the instances of that model.
|
22
|
+
def all( model )
|
23
|
+
Waves.application.models[ model ].all( domain )
|
24
|
+
end
|
25
|
+
|
26
|
+
# Finds a specific instance using the name field
|
27
|
+
def find( model, name )
|
28
|
+
Waves.application.models[ model ][ :name => name ] rescue nil
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/helpers/view.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Waves
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
# View helpers are intended to help reuse views from within other views.
|
5
|
+
# Both the +layout+ method in the common helpers and the +property+ method
|
6
|
+
# of the form helpers are specialized instance of this.
|
7
|
+
#
|
8
|
+
# The star of our show here is the +view+ method. This takes a model, view,
|
9
|
+
# and assigns hash (which are converted into instance variables in the target
|
10
|
+
# view) and returns the result of evaluating the view as content in the current
|
11
|
+
# template.
|
12
|
+
module View
|
13
|
+
|
14
|
+
# Invokes the view for the given model, passing the assigns as instance variables.
|
15
|
+
def view( model, view, assigns = {} )
|
16
|
+
self << Waves.application.views[ model ].process( request ) do
|
17
|
+
send( view, assigns )
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Waves
|
2
|
+
module Layers
|
3
|
+
module DefaultErrors
|
4
|
+
|
5
|
+
def self.included( app )
|
6
|
+
|
7
|
+
app.instance_eval do
|
8
|
+
|
9
|
+
autoinit 'Configurations::Mapping' do
|
10
|
+
handle(Waves::Dispatchers::NotFoundError) do
|
11
|
+
html = Waves.application.views[:errors].process( request ) do
|
12
|
+
not_found_404( :error => Waves::Dispatchers::NotFoundError )
|
13
|
+
end
|
14
|
+
response.status = '404'
|
15
|
+
response.content_type = 'text/html'
|
16
|
+
response.write( html )
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Waves
|
2
|
+
module Layers
|
3
|
+
module SimpleErrors
|
4
|
+
|
5
|
+
def self.included( app )
|
6
|
+
|
7
|
+
app.instance_eval do
|
8
|
+
|
9
|
+
autoinit 'Configurations::Mapping' do
|
10
|
+
handle(Waves::Dispatchers::NotFoundError) { response.status = 404 }
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
module Waves
|
2
|
+
|
3
|
+
# Waves::Mapping is a mixin for defining Waves URI mappings (mapping a request to Ruby code).
|
4
|
+
# Mappings can work against the request url, path, and elements of the request (such as the
|
5
|
+
# request method or accept header). Mappings may also include before, after, or wrap filters
|
6
|
+
# to be run if they match the request. Mappings are created using an appropriate mapping method
|
7
|
+
# along with a URL pattern (a string or regular expression), a hash of constraint options, and
|
8
|
+
# a block, which is the code to run if the pattern matches.
|
9
|
+
#
|
10
|
+
# == Examples
|
11
|
+
#
|
12
|
+
# resource = '([\w\-]+)'
|
13
|
+
# name = '([\w\-\_\.\+\@]+)'
|
14
|
+
#
|
15
|
+
# path %r{^/#{resource}/#{name}/?$} do |resource, name|
|
16
|
+
# "Hello from a #{resource} named #{name.capitalize}."
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# In this example, we are using binding regular expressions defined by +resource+
|
20
|
+
# and +name+. The matches are passed into the block as parameters. Thus, this
|
21
|
+
# rule, given the URL '/person/john' will return:
|
22
|
+
#
|
23
|
+
# Hello from a person named John.
|
24
|
+
#
|
25
|
+
# The given block may simple return a string. The content type is inferred from the request
|
26
|
+
# if possible, otherwise it defaults to +text+/+html+.
|
27
|
+
#
|
28
|
+
# path '/critters', :method => :post do
|
29
|
+
# request.content_type
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# /critters # => 'text/html'
|
33
|
+
#
|
34
|
+
# In this example, we match against a string and check to make sure that the request is a
|
35
|
+
# POST. If so, we return the request content_type. The request (and response) objects are
|
36
|
+
# available from within the block implicitly.
|
37
|
+
#
|
38
|
+
# = Invoking Controllers and Views
|
39
|
+
#
|
40
|
+
# You may invoke a controller or view method for the primary application by using the
|
41
|
+
# corresponding methods, preceded by the +use+ directive.
|
42
|
+
#
|
43
|
+
# == Examples
|
44
|
+
#
|
45
|
+
# path %r{^/#{resource}/#{name}/?$} do |resource, name|
|
46
|
+
# resource( resource ) do
|
47
|
+
# controller { find( name ) } | view { | instance | show( resource => instance ) }
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# In this example, we take the same rule from above but invoke a controller and view method.
|
52
|
+
# We use the +resource+ directive and the resource parameter to set the MVC instances we're going
|
53
|
+
# to use. This is necessary to use the +controller+ or +view+ methods. Each of these take
|
54
|
+
# a block as arguments which are evaluated in the context of the instance. The +view+ method
|
55
|
+
# can further take an argument which is "piped" from the result of the controller block. This
|
56
|
+
# isn't required, but helps to clarify the request processing. Within a view block, a hash
|
57
|
+
# may also be passed in to the view method, which is converted into instance variables for the
|
58
|
+
# view instance. In this example, the +show+ method is assigned to an instance variable with the
|
59
|
+
# same name as the resource type.
|
60
|
+
#
|
61
|
+
# So given the same URL as above - /person/john - what will happen is the +find+ method for
|
62
|
+
# the +Person+ controller will be invoked and the result passed to the +Person+ view's +show+
|
63
|
+
# method, with +@person+ holding the value returned.
|
64
|
+
#
|
65
|
+
# Crucially, the controller does not need to know what variables the view depends on. This is
|
66
|
+
# the job of the mapping block, to act as the "glue" between the controller and view. The
|
67
|
+
# controller and view can thus be completely decoupled and become easier to reuse separately.
|
68
|
+
#
|
69
|
+
# url 'http://admin.foobar.com:/' do
|
70
|
+
# resource( :admin ) { view { console } }
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# In this example, we are using the +url+ method to map a subdomain of +foobar.com+ to the
|
74
|
+
# console method of the Admin view. In this case, we did not need a controller method, so
|
75
|
+
# we simply didn't call one.
|
76
|
+
#
|
77
|
+
# = Mapping Modules
|
78
|
+
#
|
79
|
+
# You may encapsulate sets of related rules into modules and simply include them into your
|
80
|
+
# mapping module. Some rule sets come packaged with Waves, such as PrettyUrls (rules for
|
81
|
+
# matching resources using names instead of ids). The simplest way to define such modules for
|
82
|
+
# reuse is by defining the +included+ class method for the rules module, and then define
|
83
|
+
# the rules using +module_eval+. See the PrettyUrls module for an example of how to do this.
|
84
|
+
#
|
85
|
+
# *Important:* Using pre-packaged mapping rules does not prevent you from adding to or
|
86
|
+
# overriding these rules. However, order does matter, so you should put your own rules
|
87
|
+
# ahead of those your may be importing. Also, place rules with constraints (for example,
|
88
|
+
# rules that require a POST) ahead of those with no constraints, otherwise the constrainted
|
89
|
+
# rules may never be called.
|
90
|
+
|
91
|
+
module Mapping
|
92
|
+
|
93
|
+
# If the pattern matches and constraints given by the options hash are satisfied, run the
|
94
|
+
# block before running any +path+ or +url+ actions. You can have as many +before+ matches
|
95
|
+
# as you want - they will all run, unless one of them calls redirect, generates an
|
96
|
+
# unhandled exception, etc.
|
97
|
+
def before( path, options = {}, &block )
|
98
|
+
if path.is_a? Hash
|
99
|
+
options = path
|
100
|
+
else
|
101
|
+
options[:path] = path
|
102
|
+
end
|
103
|
+
filters[:before] << [ options, block ]
|
104
|
+
end
|
105
|
+
|
106
|
+
# Similar to before, except it runs its actions after any matching +url+ or +path+ actions.
|
107
|
+
def after( path, options = {}, &block )
|
108
|
+
if path.is_a? Hash
|
109
|
+
options = path
|
110
|
+
else
|
111
|
+
options[:path] = path
|
112
|
+
end
|
113
|
+
filters[:after] << [ options, block ]
|
114
|
+
end
|
115
|
+
|
116
|
+
# Run the action before and after the matching +url+ or +path+ action.
|
117
|
+
def wrap( path, options = {}, &block )
|
118
|
+
if path.is_a? Hash
|
119
|
+
options = path
|
120
|
+
else
|
121
|
+
options[:path] = path
|
122
|
+
end
|
123
|
+
filters[:before] << [ options, block ]
|
124
|
+
filters[:after] << [ options, block ]
|
125
|
+
end
|
126
|
+
|
127
|
+
# Maps a request to a block. Don't use this method directly unless you know what
|
128
|
+
# you're doing. Use +path+ or +url+ instead.
|
129
|
+
def map( path, options = {}, params = {}, &block )
|
130
|
+
if path.is_a? Hash
|
131
|
+
params = options
|
132
|
+
options = path
|
133
|
+
else
|
134
|
+
options[:path] = path
|
135
|
+
end
|
136
|
+
mapping << [ options, params, block ]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Match pattern against the +request.path+, along with satisfying any constraints
|
140
|
+
# specified by the options hash. If the pattern matches and the constraints are satisfied,
|
141
|
+
# run the block. Only one +path+ or +url+ match will be run (the first one).
|
142
|
+
def path( pat, options = {}, params = {}, &block )
|
143
|
+
options[:path] = pat; map( options, params, &block )
|
144
|
+
end
|
145
|
+
|
146
|
+
# Match pattern against the +request.url+, along with satisfying any constraints
|
147
|
+
# specified by the options hash. If the pattern matches and the constraints are satisfied,
|
148
|
+
# run the block. Only one +path+ or +url+ match will be run (the first one).
|
149
|
+
def url( pat, options = {}, params = {}, &block )
|
150
|
+
options[:url] = pat; map( options, params, &block )
|
151
|
+
end
|
152
|
+
|
153
|
+
# Maps the root of the application to a block. If an options hash is specified it must
|
154
|
+
# satisfy those constraints in order to run the block.
|
155
|
+
def root( options = {}, params = {}, &block )
|
156
|
+
path( %r{^/?$}, options, params, &block )
|
157
|
+
end
|
158
|
+
|
159
|
+
# Maps an exception handler to a block.
|
160
|
+
def handle(exception, options = {}, &block )
|
161
|
+
handlers << [exception,options, block]
|
162
|
+
end
|
163
|
+
|
164
|
+
# Maps a request to a block that will be executed within it's
|
165
|
+
# own thread. This is especially useful when you're running
|
166
|
+
# with an event driven server like thin or ebb, and this block
|
167
|
+
# is going to take a relatively long time.
|
168
|
+
def threaded( pat, options = {}, params = {}, &block)
|
169
|
+
params[:threaded] = true
|
170
|
+
map( pat, options, params, &block)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Determines whether the request should be handled in a separate thread. This is used
|
174
|
+
# by event driven servers like thin and ebb, and is most useful for those methods that
|
175
|
+
# take a long time to complete, like for example upload processes. E.g.:
|
176
|
+
#
|
177
|
+
# threaded("/upload", :method => :post) do
|
178
|
+
# handle_upload
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# You typically wouldn't use this method directly.
|
182
|
+
def threaded?( request )
|
183
|
+
mapping.find do | options, params, function |
|
184
|
+
match = match( request, options, function )
|
185
|
+
return params[:threaded] == true if match
|
186
|
+
end
|
187
|
+
return false
|
188
|
+
end
|
189
|
+
|
190
|
+
# Match the given request against the defined rules. This is typically only called
|
191
|
+
# by a dispatcher object, so you shouldn't typically use it directly.
|
192
|
+
def []( request )
|
193
|
+
|
194
|
+
rx = { :before => [], :after => [], :action => nil, :handlers => [] }
|
195
|
+
|
196
|
+
( filters[:before] + filters[:wrap] ).each do | options, function |
|
197
|
+
matches = match( request, options, function )
|
198
|
+
rx[:before] << matches if matches
|
199
|
+
end
|
200
|
+
|
201
|
+
mapping.find do | options, params, function |
|
202
|
+
rx[:action] = match( request, options, function )
|
203
|
+
break if rx[:action]
|
204
|
+
end
|
205
|
+
|
206
|
+
( filters[:after] + filters[:wrap] ).each do | options, function |
|
207
|
+
matches = match( request, options, function )
|
208
|
+
rx[:after] << matches if matches
|
209
|
+
end
|
210
|
+
|
211
|
+
handlers.each do | exception, options, function |
|
212
|
+
matches = match( request, options, function )
|
213
|
+
rx[:handlers] << matches.unshift(exception) if matches
|
214
|
+
end
|
215
|
+
|
216
|
+
return rx
|
217
|
+
end
|
218
|
+
|
219
|
+
# Clear all mapping rules
|
220
|
+
def clear
|
221
|
+
@mapping = @filters = nil;
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
def mapping; @mapping ||= []; end
|
227
|
+
|
228
|
+
def filters; @filters ||= { :before => [], :after => [], :wrap => [] }; end
|
229
|
+
|
230
|
+
def handlers; @handlers ||= []; end
|
231
|
+
|
232
|
+
def match ( request, options, function )
|
233
|
+
return nil unless satisfy( request, options )
|
234
|
+
if options[:path]
|
235
|
+
matches = options[:path].match( request.path )
|
236
|
+
elsif options[:url]
|
237
|
+
matches = options[:url].match( request.url )
|
238
|
+
end
|
239
|
+
return [ function, matches ? matches[1..-1] : nil ]
|
240
|
+
end
|
241
|
+
|
242
|
+
def satisfy( request, options )
|
243
|
+
options.nil? or options.all? do |name,wanted|
|
244
|
+
got = request.send( name ) rescue request.env[ ( name =~ /^rack\./ ) ?
|
245
|
+
name.to_s.downcase : name.to_s.upcase ]
|
246
|
+
( ( wanted.is_a?(Regexp) && wanted.match( got.to_s ) ) or
|
247
|
+
got.to_s == wanted.to_s ) unless ( wanted.nil? or got.nil? )
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|