waves 0.7.3 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. data/app/Rakefile +11 -19
  2. data/app/bin/waves-console +3 -5
  3. data/app/bin/waves-server +3 -5
  4. data/app/configurations/development.rb.erb +19 -11
  5. data/app/configurations/mapping.rb.erb +4 -5
  6. data/app/configurations/production.rb.erb +18 -13
  7. data/app/{doc/EMTPY → controllers/.gitignore} +0 -0
  8. data/app/{public/css/EMPTY → doc/.gitignore} +0 -0
  9. data/app/{public/flash/EMPTY → helpers/.gitignore} +0 -0
  10. data/app/lib/application.rb.erb +4 -51
  11. data/app/{public/images/EMPTY → lib/tasks/.gitignore} +0 -0
  12. data/app/{public/javascript/EMPTY → log/.gitignore} +0 -0
  13. data/app/{tmp/sessions/EMPTY → models/.gitignore} +0 -0
  14. data/app/public/css/.gitignore +0 -0
  15. data/app/public/flash/.gitignore +0 -0
  16. data/app/public/images/.gitignore +0 -0
  17. data/app/public/javascript/.gitignore +0 -0
  18. data/app/schema/migrations/.gitignore +0 -0
  19. data/app/startup.rb +5 -0
  20. data/app/templates/layouts/default.mab +2 -2
  21. data/app/tmp/sessions/.gitignore +0 -0
  22. data/app/views/.gitignore +0 -0
  23. data/bin/waves +38 -27
  24. data/bin/waves-console +3 -25
  25. data/bin/waves-server +4 -45
  26. data/lib/commands/waves-console.rb +21 -0
  27. data/lib/commands/waves-server.rb +55 -0
  28. data/lib/controllers/base.rb +11 -0
  29. data/lib/controllers/mixin.rb +130 -102
  30. data/lib/dispatchers/base.rb +65 -50
  31. data/lib/dispatchers/default.rb +79 -52
  32. data/lib/foundations/default.rb +26 -0
  33. data/lib/foundations/simple.rb +30 -0
  34. data/lib/helpers/common.rb +60 -56
  35. data/lib/helpers/default.rb +13 -0
  36. data/lib/helpers/form.rb +39 -38
  37. data/lib/helpers/formatting.rb +11 -11
  38. data/lib/helpers/model.rb +12 -12
  39. data/lib/helpers/view.rb +13 -13
  40. data/lib/layers/default_errors.rb +29 -0
  41. data/lib/layers/mvc.rb +58 -0
  42. data/lib/layers/orm/active_record.rb +41 -0
  43. data/lib/layers/orm/active_record/migrations/empty.rb.erb +9 -0
  44. data/lib/layers/orm/active_record/tasks/schema.rb +30 -0
  45. data/lib/layers/orm/data_mapper.rb +42 -0
  46. data/lib/layers/orm/filebase.rb +22 -0
  47. data/lib/layers/orm/migration.rb +70 -0
  48. data/lib/layers/orm/sequel.rb +82 -0
  49. data/lib/layers/orm/sequel/migrations/empty.rb.erb +9 -0
  50. data/lib/layers/orm/sequel/tasks/schema.rb +24 -0
  51. data/lib/layers/simple.rb +39 -0
  52. data/lib/layers/simple_errors.rb +26 -0
  53. data/lib/mapping/mapping.rb +222 -120
  54. data/lib/mapping/pretty_urls.rb +42 -41
  55. data/lib/renderers/erubis.rb +54 -31
  56. data/lib/renderers/markaby.rb +28 -28
  57. data/lib/renderers/mixin.rb +49 -52
  58. data/lib/runtime/application.rb +66 -48
  59. data/lib/runtime/blackboard.rb +57 -0
  60. data/lib/runtime/configuration.rb +117 -101
  61. data/lib/runtime/console.rb +19 -20
  62. data/lib/runtime/debugger.rb +9 -0
  63. data/lib/runtime/logger.rb +43 -37
  64. data/lib/runtime/mime_types.rb +19 -19
  65. data/lib/runtime/request.rb +72 -46
  66. data/lib/runtime/response.rb +37 -37
  67. data/lib/runtime/response_mixin.rb +26 -23
  68. data/lib/runtime/response_proxy.rb +25 -24
  69. data/lib/runtime/server.rb +99 -80
  70. data/lib/runtime/session.rb +63 -53
  71. data/lib/tasks/cluster.rb +26 -0
  72. data/lib/tasks/gem.rb +31 -0
  73. data/lib/tasks/generate.rb +80 -0
  74. data/lib/utilities/hash.rb +22 -0
  75. data/lib/utilities/inflect.rb +194 -0
  76. data/lib/utilities/integer.rb +15 -12
  77. data/lib/utilities/kernel.rb +32 -32
  78. data/lib/utilities/module.rb +11 -4
  79. data/lib/utilities/object.rb +5 -5
  80. data/lib/utilities/proc.rb +10 -0
  81. data/lib/utilities/string.rb +44 -38
  82. data/lib/utilities/symbol.rb +4 -4
  83. data/lib/views/base.rb +9 -0
  84. data/lib/views/mixin.rb +91 -89
  85. data/lib/waves.rb +29 -9
  86. metadata +52 -26
  87. data/app/configurations/default.rb.erb +0 -8
  88. data/app/controllers/default.rb.erb +0 -29
  89. data/app/helpers/default.rb.erb +0 -13
  90. data/app/lib/startup.rb.erb +0 -3
  91. data/app/lib/tasks/cluster.rb +0 -24
  92. data/app/lib/tasks/generate.rb +0 -15
  93. data/app/lib/tasks/schema.rb +0 -29
  94. data/app/models/default.rb.erb +0 -13
  95. data/app/schema/migrations/templates/empty.rb.erb +0 -9
  96. data/app/views/default.rb.erb +0 -13
@@ -0,0 +1,41 @@
1
+ # class Symbol
2
+ # # Protect ActiveRecord from itself by undefining the to_proc method.
3
+ # # Don't worry, AR will redefine it.
4
+ # alias :extensions_to_proc :to_proc
5
+ # remove_method :to_proc
6
+ # end
7
+ # require 'active_record'
8
+ #
9
+ # if defined?(Rake)
10
+ # require File.dirname(__FILE__) / :active_record / :tasks / :schema
11
+ # end
12
+ #
13
+ # module Waves
14
+ # module Orm
15
+ #
16
+ # Model = ::ActiveRecord::Base
17
+ #
18
+ # module ActiveRecord
19
+ #
20
+ # def active_record
21
+ # unless @active_record
22
+ # ::ActiveRecord::Base.establish_connection(config.database)
23
+ # @active_record = ::ActiveRecord::Base.connection
24
+ # end
25
+ # @active_record
26
+ # end
27
+ #
28
+ # def database
29
+ # @database ||= active_record
30
+ # end
31
+ #
32
+ # def model_config(context, name)
33
+ # active_record
34
+ # context.set_table_name(name)
35
+ # end
36
+ #
37
+ # end
38
+ # end
39
+ # end
40
+ #
41
+ # ::Application.extend(Waves::Orm::ActiveRecord)
@@ -0,0 +1,9 @@
1
+ class <%= @class_name %> < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ end
5
+
6
+ def self.down
7
+ end
8
+
9
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) / ".." / ".." / :migration
2
+ include Waves::Orm
3
+ namespace :schema do
4
+
5
+ desc "Create a new ActiveRecord Migration using ENV['name']."
6
+ task :migration do |task|
7
+
8
+ source = Migration.template(:active_record, ENV['template'])
9
+ destination = Migration.destination(ENV['name'])
10
+ migration_name = Migration.migration_name(ENV['name'])
11
+
12
+ context = {:class_name => migration_name.camel_case}
13
+
14
+ Migration.write_erb(context, source, destination)
15
+
16
+ end
17
+
18
+ desc 'Performs migration from version, to version.'
19
+ task :migrate => :connect do |task|
20
+ version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
21
+ ActiveRecord::Migrator.migrate(Migration.directory, version)
22
+ end
23
+
24
+ task :connect do
25
+ Application.database
26
+ ActiveRecord::Base.logger = Logger.new($stdout)
27
+ end
28
+
29
+ end
30
+
@@ -0,0 +1,42 @@
1
+ gem 'dm-core', '=0.9.0'
2
+
3
+ require 'data_mapper'
4
+
5
+ module Waves
6
+ module Layers
7
+ module ORM
8
+
9
+ # Work in Progress
10
+ module DataMapper
11
+
12
+ def self.included(app)
13
+
14
+ def app.database
15
+ @adapter ||= ::DataMapper.setup(:main_repository, config.database[:database])
16
+ end
17
+
18
+ app.instance_eval do
19
+
20
+ auto_eval :Models do
21
+ auto_load true, :directories => [:models]
22
+ end
23
+
24
+ auto_eval :Configurations do
25
+ auto_eval :Mapping do
26
+ before true do
27
+ app.database #force adapter init if not already done
28
+ ::DataMapper::Repository.context.push(::DataMapper::Repository.new(:main_repository))
29
+ end
30
+ always true do
31
+ ::DataMapper::Repository.context.pop
32
+ end
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,22 @@
1
+ module Waves
2
+ module Layers
3
+ module ORM
4
+
5
+ # Work in Progress
6
+ module Filebase
7
+
8
+ def self.included(app)
9
+ app.module_eval do
10
+ auto_eval( :Models ) do
11
+ auto_eval( true ) { include Filebase::Model[ :db / self.name.snake_case ] }
12
+ end
13
+ end
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,70 @@
1
+ module Waves
2
+ module Orm # :nodoc:
3
+ # Helper methods to establish an inter-ORM standard for migrations
4
+ module Migration
5
+
6
+ # stuff in this file largely lifted from Sequel. Thanks, bro.
7
+
8
+ # Glob pattern
9
+ MIGRATION_FILE_PATTERN = '[0-9][0-9][0-9]_*.rb'.freeze
10
+
11
+ # Where Waves keeps its migration files
12
+ def self.directory
13
+ :schema / :migrations
14
+ end
15
+
16
+ # Returns any found migration files in the supplied directory.
17
+ def self.migration_files(range = nil)
18
+ pattern = directory / MIGRATION_FILE_PATTERN
19
+ files = Dir[pattern].inject([]) do |m, path|
20
+ m[File.basename(path).to_i] = path
21
+ m
22
+ end
23
+ filtered = range ? files[range] : files
24
+ filtered ? filtered.compact : []
25
+ end
26
+
27
+ # Use the supplied version number or determine the next in sequence
28
+ # based on the migration files in the migration directory
29
+ def self.next_migration_version
30
+ version = ENV['version'] || latest_migration_version
31
+ version.to_i + 1
32
+ end
33
+
34
+ # Uses the migration files in the migration directory to determine
35
+ # the highest numbered existing migration.
36
+ def self.latest_migration_version
37
+ l = migration_files.last
38
+ l ? File.basename(l).to_i : nil
39
+ end
40
+
41
+ # If the user doesn't pass a name, defaults to "migration"
42
+ def self.migration_name(name=nil)
43
+ name || 'migration'
44
+ end
45
+
46
+ # Returns the path to the migration template file for the given ORM.
47
+ # <em>orm</em> can be a symbol or string
48
+ def self.template(orm, name=nil)
49
+ file = ( name || 'empty' ) + '.rb.erb'
50
+ source = File.dirname(__FILE__) / orm / :migrations / file
51
+ end
52
+
53
+ # Given a migration name, returns the path of the file that would be created.
54
+ def self.destination(name)
55
+ version = next_migration_version
56
+ directory / "#{'%03d' % version}_#{migration_name(name)}.rb"
57
+ end
58
+
59
+ # Takes an assigns hash as the Erubis context. Keys in the hash become
60
+ # instance variable names.
61
+ def self.write_erb(context, source, destination)
62
+ code = Erubis::Eruby.new( File.read( source ) ).evaluate( context )
63
+ puts "Creating #{destination}"
64
+ File.write( destination, code )
65
+ end
66
+
67
+ end
68
+ end
69
+
70
+ end
@@ -0,0 +1,82 @@
1
+ gem 'sequel', '>= 2.0.0'
2
+ require 'sequel'
3
+ require File.dirname(__FILE__) / :sequel / :tasks / :schema if defined?(Rake)
4
+
5
+ module Waves
6
+ module Layers
7
+ module ORM # :nodoc:
8
+
9
+ # Sets up the Sequel connection and configures AutoCode on Models, so that constants in that
10
+ # namespace get loaded from file or created as subclasses of Models::Default
11
+ module Sequel
12
+
13
+ # On inclusion, this module:
14
+ # - creates on the application module a database method that establishes the Sequel connection
15
+ # - arranges for autoloading/autocreation of missing constants in the Models namespace
16
+ # - defines Sequel-specific helper methods on Waves::Controllers::Base
17
+ #
18
+ # The controller helper methdods are:
19
+ # - all
20
+ # - find(name)
21
+ # - create
22
+ # - delete(name)
23
+ # - update(name)
24
+ #
25
+ def self.included(app)
26
+
27
+ def app.database ; @sequel ||= ::Sequel.open( config.database ) ; end
28
+
29
+ app.instance_eval do
30
+
31
+ auto_create_module( :Models ) do
32
+ include AutoCode
33
+ auto_create_class :Default, ::Sequel::Model
34
+ auto_load :Default, :directories => [ :models ]
35
+ end
36
+
37
+ auto_eval :Models do
38
+ auto_create_class true, app::Models::Default
39
+ auto_load true, :directories => [ :models ]
40
+ # set the Sequel dataset based on the model class name
41
+ # note that this is not done for app::Models::Default, as it isn't
42
+ # supposed to represent a table
43
+ auto_eval true do
44
+ default = superclass.basename.snake_case.pluralize.intern
45
+ if @dataset && @dataset.opts[:from] != [ default ]
46
+ # don't clobber dataset from autoloaded file
47
+ else
48
+ set_dataset Waves.application.database[ basename.snake_case.pluralize.intern ]
49
+ end
50
+ end
51
+ end
52
+
53
+ Waves::Controllers::Base.module_eval do
54
+ def all #:nodoc:
55
+ model.all
56
+ end
57
+
58
+ def find( name ) #:nodoc:
59
+ model[ :name => name ] or not_found
60
+ end
61
+
62
+ def create #:nodoc:
63
+ model.create( attributes )
64
+ end
65
+
66
+ def delete( name ) #:nodoc:
67
+ find( name ).destroy
68
+ end
69
+
70
+ def update( name ) #:nodoc:
71
+ instance = find( name )
72
+ instance.update_with_params( attributes )
73
+ instance
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,9 @@
1
+ class <%= @class_name %> < Sequel::Migration
2
+
3
+ def up
4
+ end
5
+
6
+ def down
7
+ end
8
+
9
+ end
@@ -0,0 +1,24 @@
1
+ require File.dirname(__FILE__) / ".." / ".." / :migration
2
+ include Waves::Orm
3
+ namespace :schema do
4
+
5
+ desc "Create a new Sequel Migration using ENV['name']."
6
+ task :migration do |task|
7
+
8
+ source = Migration.template(:sequel, ENV['template'])
9
+ destination = Migration.destination(ENV['name'])
10
+ migration_name = Migration.migration_name(ENV['name'])
11
+
12
+ context = {:class_name => migration_name.camel_case}
13
+
14
+ Migration.write_erb(context, source, destination)
15
+
16
+ end
17
+
18
+ desc 'Performs migration from version, to version.'
19
+ task :migrate do |task|
20
+ version = ENV['version']; version = version.to_i unless version.nil?
21
+ Sequel::Migrator.apply( Waves.application.database, Migration.directory , version )
22
+ end
23
+
24
+ end
@@ -0,0 +1,39 @@
1
+ module Waves
2
+
3
+ # Waves uses Layers to provide discrete, stackable, interchangeable bundles of functionality.
4
+ #
5
+ # Developers can make use of Layers by including them directly in a Waves application:
6
+ #
7
+ # module MyApp
8
+ # include SomeLayer
9
+ # end
10
+ module Layers
11
+
12
+ # Creates the Configurations namespace and establishes the standard autoload-or-autocreate
13
+ # rules.
14
+ module Simple
15
+ def self.included( app )
16
+
17
+ def app.config ; Waves.config ; end
18
+ def app.configurations ; self::Configurations ; end
19
+
20
+ app.instance_eval do
21
+
22
+ include AutoCode
23
+
24
+ auto_create_module( :Configurations ) do
25
+ include AutoCode
26
+ auto_create_class true, Waves::Configurations::Default
27
+ auto_load :Mapping, :directories => [:configurations]
28
+ auto_load true, :directories => [:configurations]
29
+ auto_eval :Mapping do
30
+ extend Waves::Mapping
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,26 @@
1
+ module Waves
2
+ module Layers
3
+ # Configures Waves for minimal exception handling.
4
+ #
5
+ # For example,
6
+ # a NotFoundError results in response status of 404, with body text
7
+ # of "404 Not Found".
8
+ module SimpleErrors
9
+
10
+ def self.included( app )
11
+
12
+ app.instance_eval do
13
+
14
+ auto_eval :Configurations do
15
+ auto_eval :Mapping do
16
+ handle(Waves::Dispatchers::NotFoundError) do
17
+ response.status = 404; response.body = "404 Not Found"
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,14 +1,39 @@
1
1
  module Waves
2
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.
3
+ # Mappings in Waves are the interface between the request dispatcher and your
4
+ # application code. The dispatcher matches each request against the mappings
5
+ # to determine a primary action and to collect sets of before, after, wrap,
6
+ # and always actions. The dispatcher also looks for an exception handler
7
+ # registered in the mappings when attempting a rescue.
8
+ #
9
+ # Each mapping associates a block with a set of constraints. Mappings can be
10
+ # one of several types:
11
+ #
12
+ # - action (the actual request processing and response)
13
+ # - handle (exception handling)
14
+ # - before
15
+ # - after
16
+ # - wrap (registers its block as both a before and after action)
17
+ # - always (like an "ensure" clause in a rescue)
18
+ #
19
+ # Actions are registered using path, url, or map. The other types may be
20
+ # registered using methods named after the type.
21
+ #
22
+ #
23
+ # The available constraints are:
24
+ #
25
+ # - a string or regexp that the path or url must match
26
+ # - parameters to match against the HTTP request headers and the Rack-specific variables (e.g. 'rack.url_scheme')
27
+ # - an additional hash reserved for settings not related to the Rack request (e.g. giving Rack handers special instructions for certain requests. See threaded? )
28
+ #
29
+ # The dispatcher evaluates mapping blocks in an instance of ResponseProxy,
30
+ # which provides access to foundational classes of a Waves application (i.e. controllers and views)
9
31
  #
10
32
  # == Examples
11
33
  #
34
+ # resource = '([\w\-]+)'
35
+ # name = '([\w\-\_\.\+\@]+)'
36
+ #
12
37
  # path %r{^/#{resource}/#{name}/?$} do |resource, name|
13
38
  # "Hello from a #{resource} named #{name.capitalize}."
14
39
  # end
@@ -20,7 +45,7 @@ module Waves
20
45
  # Hello from a person named John.
21
46
  #
22
47
  # The given block may simple return a string. The content type is inferred from the request
23
- # if possible, otherwise it defaults to +text+/+html+.
48
+ # if possible, otherwise it defaults to +text+/+html+.
24
49
  #
25
50
  # path '/critters', :method => :post do
26
51
  # request.content_type
@@ -28,31 +53,32 @@ module Waves
28
53
  #
29
54
  # /critters # => 'text/html'
30
55
  #
31
- # In this example, we match against a string and check to make sure that the request is a
56
+ # In this example, we match against a string and check to make sure that the request is a
32
57
  # POST. If so, we return the request content_type. The request (and response) objects are
33
58
  # available from within the block implicitly.
34
59
  #
35
60
  # = Invoking Controllers and Views
36
61
  #
37
- # You may invoking a controller or view method for the primary application by using the
62
+ # You may invoke a controller or view method for the primary application by using the
38
63
  # corresponding methods, preceded by the +use+ directive.
39
64
  #
40
65
  # == Examples
41
66
  #
42
67
  # path %r{^/#{resource}/#{name}/?$} do |resource, name|
43
- # use( resource ) | controller { find( name ) } |
44
- # view { | instance | show( resource => instance ) }
68
+ # resource( resource ) do
69
+ # controller { find( name ) } | view { | instance | show( resource => instance ) }
70
+ # end
45
71
  # end
46
72
  #
47
73
  # In this example, we take the same rule from above but invoke a controller and view method.
48
- # We use the +use+ directive and the resource parameter to set the MVC instances we're going
74
+ # We use the +resource+ directive and the resource parameter to set the MVC instances we're going
49
75
  # to use. This is necessary to use the +controller+ or +view+ methods. Each of these take
50
76
  # a block as arguments which are evaluated in the context of the instance. The +view+ method
51
77
  # can further take an argument which is "piped" from the result of the controller block. This
52
78
  # isn't required, but helps to clarify the request processing. Within a view block, a hash
53
- # may also be passed in, which is converted into instance variables for the view instance. In
54
- # this example, the +show+ method is assigned to an instance variable with the same name as
55
- # the resource type.
79
+ # may also be passed in to the view method, which is converted into instance variables for the
80
+ # view instance. In this example, the +show+ method is assigned to an instance variable with the
81
+ # same name as the resource type.
56
82
  #
57
83
  # So given the same URL as above - /person/john - what will happen is the +find+ method for
58
84
  # the +Person+ controller will be invoked and the result passed to the +Person+ view's +show+
@@ -63,12 +89,12 @@ module Waves
63
89
  # controller and view can thus be completely decoupled and become easier to reuse separately.
64
90
  #
65
91
  # url 'http://admin.foobar.com:/' do
66
- # use( admin ) | view { console }
92
+ # resource( :admin ) { view { console } }
67
93
  # end
68
94
  #
69
- # In this example, we are using the +url+ method to map a subdomain of +foobar.com+ to the
95
+ # In this example, we are using the +url+ method to map a subdomain of +foobar.com+ to the
70
96
  # console method of the Admin view. In this case, we did not need a controller method, so
71
- # we simply didn't call one.
97
+ # we simply didn't call one.
72
98
  #
73
99
  # = Mapping Modules
74
100
  #
@@ -76,112 +102,188 @@ module Waves
76
102
  # mapping module. Some rule sets come packaged with Waves, such as PrettyUrls (rules for
77
103
  # matching resources using names instead of ids). The simplest way to define such modules for
78
104
  # reuse is by defining the +included+ class method for the rules module, and then define
79
- # the rules using +module_eval+. This will likely be made simpler in a future release, but
80
- # see the PrettyUrls module for an example of how to do this.
105
+ # the rules using +module_eval+. See the PrettyUrls module for an example of how to do this.
81
106
  #
82
- # *Important:* Using pre-packaged mapping rules does not prevent you from adding to or
83
- # overriding these rules. However, order does matter, so you should put your own rules
107
+ # *Important:* Using pre-packaged mapping rules does not prevent you from adding to or
108
+ # overriding these rules. However, order does matter, so you should put your own rules
84
109
  # ahead of those your may be importing. Also, place rules with constraints (for example,
85
110
  # rules that require a POST) ahead of those with no constraints, otherwise the constrainted
86
111
  # rules may never be called.
87
-
88
- module Mapping
89
-
90
- # If the pattern matches and constraints given by the options hash are satisfied, run the
91
- # block before running any +path+ or +url+ actions. You can have as many +before+ matches
92
- # as you want - they will all run, unless one of them calls redirect, generates an
93
- # unhandled exception, etc.
94
- def before( pattern, options=nil, &block )
95
- filters[:before] << [ pattern, options, block ]
96
- end
97
-
98
- # Similar to before, except it runs its actions after any matching +url+ or +path+ actions.
99
- def after( pattern, options=nil, &block )
100
- filters[:after] << [ pattern, options, block ]
101
- end
102
-
103
- # Run the action before and after the matching +url+ or +path+ action.
104
- def wrap( pattern, options=nil, &block )
105
- filters[:before] << [ pattern, options, block ]
106
- filters[:after] << [ pattern, options, block ]
107
- end
108
-
109
- # Maps a request to a block. Don't use this method directly unless you know what
112
+
113
+ module Mapping
114
+
115
+ # If the pattern matches and constraints given by the options hash are satisfied, run the
116
+ # block before running any +path+ or +url+ actions. You can have as many +before+ matches
117
+ # as you want - they will all run, unless one of them calls redirect, generates an
118
+ # unhandled exception, etc.
119
+ def before( path, options = {}, &block )
120
+ if path.is_a? Hash
121
+ options = path
122
+ else
123
+ options[:path] = path
124
+ end
125
+ filters[:before] << [ options, block ]
126
+ end
127
+
128
+ # Similar to before, except it runs its actions after any matching +url+ or +path+ actions.
129
+ # Note that after methods will run even if an exception is thrown during processing.
130
+ def after( path, options = {}, &block )
131
+ if path.is_a? Hash
132
+ options = path
133
+ else
134
+ options[:path] = path
135
+ end
136
+ filters[:after] << [ options, block ]
137
+ end
138
+
139
+ # Run the action before and after the matching +url+ or +path+ action.
140
+ def wrap( path, options = {}, &block )
141
+ if path.is_a? Hash
142
+ options = path
143
+ else
144
+ options[:path] = path
145
+ end
146
+ filters[:before] << [ options, block ]
147
+ filters[:after] << [ options, block ]
148
+ end
149
+
150
+ # Like after, but will run even when an exception is thrown. Exceptions in
151
+ # always mappings are simply logged and ignored.
152
+ def always( path, options = {}, &block )
153
+ if path.is_a? Hash
154
+ options = path
155
+ else
156
+ options[:path] = path
157
+ end
158
+ filters[:always] << [ options, block ]
159
+ end
160
+
161
+ # Maps a request to a block. Don't use this method directly unless you know what
110
162
  # you're doing. Use +path+ or +url+ instead.
111
- def map( options, &block )
112
- pattern = options[:path] || options[:url]
113
- mapping << [ pattern, options, block ]
114
- end
115
-
116
- # Match pattern against the +request.path+, along with satisfying any constraints
117
- # specified by the options hash. If the pattern matches and the constraints are satisfied,
118
- # run the block. Only one +path+ or +url+ match will be run (the first one).
119
- def path( pat, options = {}, &block )
120
- options[:path] = pat; map( options, &block )
121
- end
122
-
123
- # Match pattern against the +request.url+, along with satisfying any constraints
124
- # specified by the options hash. If the pattern matches and the constraints are satisfied,
125
- # run the block. Only one +path+ or +url+ match will be run (the first one).
126
- def url( pat, options = {}, &block )
127
- options[:url] = pat; map( options, block )
128
- end
129
-
130
- # Match the given request against the defined rules. This is typically only called
131
- # by a dispatcher object, so you shouldn't typically use it directly.
132
- def []( request )
133
-
134
- rx = { :before => [], :after => [], :action => nil }
135
-
136
- ( filters[:before] + filters[:wrap] ).each do | pattern, options, function |
137
- matches = pattern.match(request.path)
138
- rx[:before] << [ function, matches[1..-1] ] if matches &&
139
- ( ! options || satisfy( request, options ))
140
- end
141
-
142
- mapping.find do |pattern, options, function|
143
- matches = pattern.match(request.path)
144
- rx[:action] = [ function, matches[1..-1] ] if matches &&
145
- ( ! options || satisfy( request, options ) )
146
- end
147
-
148
- ( filters[:after] + filters[:wrap] ).each do | pattern, options, function |
149
- matches = pattern.match(request.path)
150
- rx[:after] << [ function, matches[1..-1] ] if matches &&
151
- ( ! options || satisfy( request, options ))
152
- end
153
-
154
- not_found(request) unless rx[:action]
155
-
156
- return rx
157
-
158
- end
159
-
160
- private
161
-
162
- def mapping; @mapping ||= []; end
163
-
164
- def filters; @filters ||= { :before => [], :after => [], :wrap => [] }; end
165
-
166
- def not_found(request)
167
- raise Waves::Dispatchers::NotFoundError.new( request.url + ' not found.')
168
- end
169
-
170
- def satisfy( request, options )
171
- options.each do |method, param|
172
- return false unless self.send( method, param, request )
173
- end
174
- return true
175
- end
176
-
177
- def method( method, request )
178
- request.method == method
179
- end
180
-
181
- # TODO: Add constraint for request accept header
182
- def accept( format, request )
183
- end
184
-
185
- end
163
+ def map( path, options = {}, params = {}, &block )
164
+ case path
165
+ when Hash
166
+ params = options; options = path
167
+ when String
168
+ options[:path] = path
169
+ end
170
+ mapping << [ options, params, block ]
171
+ end
172
+
173
+ # Match pattern against the +request.path+, along with satisfying any constraints
174
+ # specified by the options hash. If the pattern matches and the constraints are satisfied,
175
+ # run the block. Only one +path+ or +url+ match will be run (the first one).
176
+ def path( pat, options = {}, params = {}, &block )
177
+ options[:path] = pat; map( options, params, &block )
178
+ end
179
+
180
+ # Match pattern against the +request.url+, along with satisfying any constraints
181
+ # specified by the options hash. If the pattern matches and the constraints are satisfied,
182
+ # run the block. Only one +path+ or +url+ match will be run (the first one).
183
+ def url( pat, options = {}, params = {}, &block )
184
+ options[:url] = pat; map( options, params, &block )
185
+ end
186
+
187
+ # Maps the root of the application to a block. If an options hash is specified it must
188
+ # satisfy those constraints in order to run the block.
189
+ def root( options = {}, params = {}, &block )
190
+ path( %r{^/?$}, options, params, &block )
191
+ end
192
+
193
+ # Maps an exception handler to a block.
194
+ def handle(exception, options = {}, &block )
195
+ handlers << [exception,options, block]
196
+ end
197
+
198
+ # Maps a request to a block that will be executed within it's
199
+ # own thread. This is especially useful when you're running
200
+ # with an event driven server like thin or ebb, and this block
201
+ # is going to take a relatively long time.
202
+ def threaded( pat, options = {}, params = {}, &block)
203
+ params[:threaded] = true
204
+ map( pat, options, params, &block)
205
+ end
206
+
207
+ # Determines whether the request should be handled in a separate thread. This is used
208
+ # by event driven servers like thin and ebb, and is most useful for those methods that
209
+ # take a long time to complete, like for example upload processes. E.g.:
210
+ #
211
+ # threaded("/upload", :method => :post) do
212
+ # handle_upload
213
+ # end
214
+ #
215
+ # You typically wouldn't use this method directly.
216
+ def threaded?( request )
217
+ mapping.find do | options, params, function |
218
+ match = match( request, options, function )
219
+ return params[:threaded] == true if match
220
+ end
221
+ return false
222
+ end
223
+
224
+ # Match the given request against the defined rules. This is typically only called
225
+ # by a dispatcher object, so you shouldn't typically use it directly.
226
+ def []( request )
227
+
228
+ rx = { :before => [], :after => [], :always => [], :action => nil, :handlers => [] }
229
+
230
+ ( filters[:before] + filters[:wrap] ).each do | options, function |
231
+ matches = match( request, options, function )
232
+ rx[:before] << matches if matches
233
+ end
234
+
235
+ mapping.find do | options, params, function |
236
+ rx[:action] = match( request, options, function )
237
+ break if rx[:action]
238
+ end
239
+
240
+ ( filters[:after] + filters[:wrap] ).each do | options, function |
241
+ matches = match( request, options, function )
242
+ rx[:after] << matches if matches
243
+ end
244
+
245
+ filters[:always].each do | options, function |
246
+ matches = match( request, options, function )
247
+ rx[:always] << matches if matches
248
+ end
249
+
250
+ handlers.each do | exception, options, function |
251
+ matches = match( request, options, function )
252
+ rx[:handlers] << matches.unshift(exception) if matches
253
+ end
254
+
255
+ return rx
256
+ end
257
+
258
+ # Clear all mapping rules
259
+ def clear
260
+ @mapping = @filters = @handlers = nil;
261
+ end
262
+
263
+ private
264
+
265
+ def mapping; @mapping ||= []; end
266
+
267
+ def filters; @filters ||= { :before => [], :after => [], :wrap => [], :always => [] }; end
268
+
269
+ def handlers; @handlers ||= []; end
270
+
271
+ def match ( request, options, function )
272
+ return nil unless satisfy( request, options )
273
+ return [ function, nil ] if ( options[:path] == true or options[:url] == true )
274
+ matches = options[:path].match( request.path ) if options[:path]
275
+ matches = options[:url].match( request.url ) if options[:url]
276
+ return [ function, matches ? matches[1..-1] : nil ]
277
+ end
278
+
279
+ def satisfy( request, options )
280
+ options.nil? or options.all? do |name,wanted|
281
+ return true if wanted == true
282
+ got = request.send( name ) rescue request.env[ ( name =~ /^rack\./ ) ? name.to_s.downcase : name.to_s.upcase ]
283
+ ( ( wanted.is_a?(Regexp) and wanted.match( got.to_s ) ) or got.to_s == wanted.to_s ) unless ( wanted.nil? or got.nil? )
284
+ end
285
+ end
286
+ end
287
+
186
288
 
187
289
  end