waves 0.7.3 → 0.7.5

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.
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