waves 0.6.7 → 0.6.9
Sign up to get free protection for your applications and to get access to all the features.
- data/app/Rakefile +1 -1
- data/app/configurations/default.rb.erb +1 -1
- data/app/configurations/mapping.rb.erb +6 -37
- data/app/controllers/default.rb.erb +10 -20
- data/app/lib/tasks/cluster.rb +8 -10
- data/app/lib/tasks/generate.rb +15 -0
- data/app/lib/tasks/schema.rb +6 -5
- data/app/schema/{migration → migrations}/templates/empty.rb.erb +0 -0
- data/app/templates/errors/{not_found.mab → not_found_404.mab} +0 -0
- data/app/templates/errors/{server_error.mab → server_error_500.mab} +0 -0
- data/app/templates/layouts/default.mab +1 -1
- data/bin/waves +1 -1
- data/bin/waves-console +21 -4
- data/lib/controllers/mixin.rb +85 -16
- data/lib/dispatchers/base.rb +26 -15
- data/lib/dispatchers/default.rb +25 -4
- data/lib/helpers/common.rb +50 -10
- data/lib/helpers/form.rb +18 -1
- data/lib/helpers/formatting.rb +17 -9
- data/lib/helpers/model.rb +20 -2
- data/lib/helpers/view.rb +12 -2
- data/lib/mapping/mapping.rb +187 -0
- data/lib/mapping/pretty_urls.rb +95 -0
- data/lib/renderers/erubis.rb +3 -1
- data/lib/renderers/markaby.rb +6 -4
- data/lib/renderers/mixin.rb +12 -0
- data/lib/runtime/application.rb +33 -23
- data/lib/runtime/configuration.rb +131 -9
- data/lib/runtime/console.rb +2 -2
- data/lib/runtime/logger.rb +42 -18
- data/lib/runtime/mime_types.rb +8 -4
- data/lib/runtime/request.rb +14 -5
- data/lib/runtime/response.rb +15 -1
- data/lib/runtime/response_mixin.rb +29 -47
- data/lib/runtime/server.rb +60 -35
- data/lib/runtime/session.rb +14 -1
- data/lib/utilities/integer.rb +7 -1
- data/lib/utilities/kernel.rb +28 -2
- data/lib/utilities/module.rb +3 -0
- data/lib/utilities/object.rb +5 -1
- data/lib/utilities/string.rb +7 -2
- data/lib/utilities/symbol.rb +2 -0
- data/lib/views/mixin.rb +55 -1
- data/lib/waves.rb +9 -2
- metadata +10 -8
- data/lib/runtime/mapping.rb +0 -82
data/app/Rakefile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module <%= name %>
|
2
2
|
module Configurations
|
3
3
|
class Default < Waves::Configurations::Default
|
4
|
-
database :host => host, :adapter => 'mysql', :database => '
|
4
|
+
database :host => host, :adapter => 'mysql', :database => '<%= name.downcase %>',
|
5
5
|
:user => 'root', :password => ''
|
6
6
|
end
|
7
7
|
end
|
@@ -4,43 +4,12 @@ module <%= name %>
|
|
4
4
|
|
5
5
|
module Mapping
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# get all resources for the given model
|
14
|
-
path %r{^/#{model}/?$} do | model |
|
15
|
-
use( model ) | controller { all } | view { |data| list( model.plural => data ) }
|
16
|
-
end
|
17
|
-
|
18
|
-
# get all resources identifiers for the given model in json format
|
19
|
-
path %r{^/#{model}/?$}, :accept => /json/ do | model |
|
20
|
-
use( model ) | controller { all } | view { |data| list( model.plural => data ) }
|
21
|
-
end
|
22
|
-
|
23
|
-
# get the given resource for the given model
|
24
|
-
path %r{^/#{model}/#{name}/?$} do | model, name |
|
25
|
-
use(model) | controller { find( name ) } |
|
26
|
-
view { |data| show( model => data ) }
|
27
|
-
end
|
28
|
-
|
29
|
-
# display an editor for the given resource / model
|
30
|
-
path %r{^/#{model}/#{name}/editor/?$} do | model, name |
|
31
|
-
use(model) | controller { find( name ) } |
|
32
|
-
view { |data| editor( model => data ) }
|
33
|
-
end
|
34
|
-
|
35
|
-
# add / update the given resource for the given model
|
36
|
-
path %r{^/#{model}/#{name}/?$}, :method => :post do | model, name |
|
37
|
-
use(model) | controller { update( name ) }; redirect( url )
|
38
|
-
end
|
39
|
-
|
40
|
-
# delete the given resource for the given model
|
41
|
-
path %r{^/#{model}/#{name}/?$}, :method => :delete do | model, name |
|
42
|
-
use(model) | controller { delete( name ) }; redirect( "/#{model}/" )
|
43
|
-
end
|
7
|
+
module Mapping
|
8
|
+
extend Waves::Mapping
|
9
|
+
# your custom rules go here
|
10
|
+
include Waves::Mapping::PrettyUrls::RestRules
|
11
|
+
include Waves::Mapping::PrettyUrls::GetRules
|
12
|
+
end
|
44
13
|
|
45
14
|
end
|
46
15
|
|
@@ -6,31 +6,21 @@ module <%= name %>
|
|
6
6
|
|
7
7
|
include Waves::Controllers::Mixin
|
8
8
|
|
9
|
-
def
|
10
|
-
|
11
|
-
end
|
9
|
+
def attributes; params[model_name.singular.intern]; end
|
10
|
+
|
11
|
+
def all; model.all; end
|
12
12
|
|
13
|
-
def find( name )
|
14
|
-
model[ name ]
|
15
|
-
end
|
13
|
+
def find( name ); model[ :name => name ] or not_found; end
|
16
14
|
|
17
|
-
def create(
|
18
|
-
model.new( attrs )
|
19
|
-
end
|
15
|
+
def create; model.create( attributes ); end
|
20
16
|
|
21
|
-
def update
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
instance.save
|
26
|
-
else
|
27
|
-
model.create( attrs )
|
28
|
-
end
|
17
|
+
def update( name )
|
18
|
+
instance = find( name )
|
19
|
+
instance.set( attributes )
|
20
|
+
instance.save_changes
|
29
21
|
end
|
30
22
|
|
31
|
-
def delete
|
32
|
-
model[ name ].destroy
|
33
|
-
end
|
23
|
+
def delete( name ); find( name ).destroy; end
|
34
24
|
|
35
25
|
end
|
36
26
|
|
data/app/lib/tasks/cluster.rb
CHANGED
@@ -1,21 +1,19 @@
|
|
1
1
|
namespace :cluster do
|
2
2
|
|
3
3
|
desc 'Start a cluster of waves applications.'
|
4
|
-
task :start
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
Process.detach( pid ); pids << pid
|
4
|
+
task :start do |task|
|
5
|
+
( Waves::Console.config.ports || [ Waves::Console.config.port ] ).each do |port|
|
6
|
+
cmd = "waves-server -p #{port} -c #{ENV['mode']||'development'} -d"
|
7
|
+
puts cmd ; `#{cmd}`
|
9
8
|
end
|
10
|
-
File.write( path, pids.join(',') )
|
11
9
|
end
|
12
10
|
|
13
11
|
desc 'Stop a cluster of waves applications.'
|
14
12
|
task :stop do |task|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
Dir[ :log / '*.pid' ].each do |path|
|
14
|
+
pid = File.basename(path,'.pid').to_i
|
15
|
+
puts "Stopping process #{pid} ..."
|
16
|
+
Process.kill( 'INT', pid ) rescue nil
|
19
17
|
end
|
20
18
|
end
|
21
19
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
namespace :generate do
|
2
|
+
desc 'Generate a new model'
|
3
|
+
task :model do |task|
|
4
|
+
template = File.read :models / 'default.rb'
|
5
|
+
File.write( :models / ENV['name'] + '.rb',
|
6
|
+
template.gsub('class Default < Sequel::Model',
|
7
|
+
"class #{ENV['name'].camel_case} < Sequel::Model(:#{ENV['name'].plural})") )
|
8
|
+
end
|
9
|
+
desc 'Generate a new controller'
|
10
|
+
task :controller do |task|
|
11
|
+
template = File.read :controllers / 'default.rb'
|
12
|
+
File.write( :controllers / ENV['name'] + '.rb',
|
13
|
+
template.gsub('class Default',"class #{ENV['name'].camel_case}") )
|
14
|
+
end
|
15
|
+
end
|
data/app/lib/tasks/schema.rb
CHANGED
@@ -4,15 +4,16 @@ namespace :schema do
|
|
4
4
|
desc 'Create a new Sequel Migration using name.'
|
5
5
|
task :migration do |task|
|
6
6
|
|
7
|
-
version = ( ENV['version'].
|
8
|
-
Sequel::Migrator.get_current_migration_version( Blog.database )
|
7
|
+
version = ( ENV['version'].nil? ?
|
8
|
+
Sequel::Migrator.get_current_migration_version( Blog.database ) :
|
9
|
+
ENV['version'].to_i ) + 1
|
9
10
|
|
10
11
|
name = ENV['name'] || 'migration'
|
11
12
|
class_name = name.camel_case
|
12
13
|
|
13
14
|
template = ( ENV['template'] || 'empty' ) + '.rb.erb'
|
14
|
-
source = :schema / :
|
15
|
-
destination = :schema / :
|
15
|
+
source = :schema / :migrations / :templates / template
|
16
|
+
destination = :schema / :migrations / "#{'%03d' % version}_#{name}.rb"
|
16
17
|
code = Erubis::Eruby.new( File.read( source ) ).result( binding )
|
17
18
|
File.write( destination, code )
|
18
19
|
|
@@ -21,7 +22,7 @@ namespace :schema do
|
|
21
22
|
desc 'Performs migration from version, to version.'
|
22
23
|
task :migrate do |task|
|
23
24
|
version = ENV['version']; version = version.to_i unless version.nil?
|
24
|
-
Sequel::Migrator.apply( Waves.application.database, :schema / :
|
25
|
+
Sequel::Migrator.apply( Waves.application.database, :schema / :migrations , version )
|
25
26
|
end
|
26
27
|
|
27
28
|
end
|
File without changes
|
File without changes
|
File without changes
|
data/bin/waves
CHANGED
@@ -18,7 +18,7 @@ Dir["#{app_name}/**/EMPTY"].each { |path| system "rm #{path}" }
|
|
18
18
|
|
19
19
|
# next, process all template files ...
|
20
20
|
Dir["#{app_name}/**/*.erb"].each do |path|
|
21
|
-
unless path =~ %r{^#{app_name}/(schema/
|
21
|
+
unless path =~ %r{^#{app_name}/(schema/migrations/templates|templates)}
|
22
22
|
name = app_name.camel_case
|
23
23
|
File.write( path.gsub(/\.erb$/,''),
|
24
24
|
Erubis::Eruby.new( File.read( path ) ).result( binding ) )
|
data/bin/waves-console
CHANGED
@@ -1,8 +1,25 @@
|
|
1
1
|
require 'waves'
|
2
|
+
require 'choice'
|
2
3
|
|
3
|
-
|
4
|
-
|
4
|
+
Choice.options do
|
5
|
+
header 'Run a waves application server.'
|
6
|
+
header ''
|
7
|
+
option :mode do
|
8
|
+
short '-c'
|
9
|
+
long '--config=CONFIG'
|
10
|
+
desc 'Configuration to use.'
|
11
|
+
desc 'Defaults to development.'
|
12
|
+
cast Symbol
|
13
|
+
end
|
14
|
+
separator ''
|
15
|
+
option :directory do
|
16
|
+
short '-D'
|
17
|
+
long '--dir=DIR'
|
18
|
+
desc 'Directory containing the application.'
|
19
|
+
desc 'Defaults to the current directory.'
|
20
|
+
end
|
21
|
+
separator ''
|
5
22
|
end
|
6
|
-
|
7
|
-
Waves::Console.load(
|
23
|
+
|
24
|
+
Waves::Console.load( Choice.choices )
|
8
25
|
require 'irb'; IRB.start
|
data/lib/controllers/mixin.rb
CHANGED
@@ -1,36 +1,105 @@
|
|
1
1
|
module Waves
|
2
|
+
|
3
|
+
#
|
4
|
+
# Controllers in Waves are simply classes that provide a request / response context for operating on a model. While models are essentially just a way to manage data in an application, controllers manage data in response to a request. For example, a controller updates a model instance using parameters from the request.
|
5
|
+
#
|
6
|
+
# Controller methods simply return data (a resource), if necessary, that can be then used by views to determine how to render that data. Controllers do not determine which view will be invoked. They are independent of the view; one controller method might be suitable for several different views. In some cases, controllers can choose to directly modify the response and possibly even short-circuit the view entirely. A good example of this is a redirect.
|
7
|
+
#
|
8
|
+
# Controllers, like Views and Mappings, use the Waves::ResponseMixin to provide a rich context for working with the request and response objects. They can even call other controllers or views using the controllers method. In addition, they provide some basic reflection (access to the model and model_name that corresponds to the class name for the given model) and automatic parameter destructuring. This allows controller methods to access the request parameters as a hash, so that a POST variable named <tt>entry.title</tt> is accessed as <tt>params[:entry][:title]</tt>.
|
9
|
+
#
|
10
|
+
# Controllers often do not have to be explicitly defined. Instead, one or more default controllers can be defined that are used as exemplars for a given model. By default, the +waves+ command generates a single default, placed in the application's <tt>controllers/default.rb</tt> file. This can be modified to change the default behavior for all controllers. Alternatively, the <tt>rake generate:controller</tt> command can be used to explicitly define a controller.
|
11
|
+
#
|
12
|
+
# As an example, the code for the default controller is below for the Blog application.
|
13
|
+
#
|
14
|
+
# module Blog
|
15
|
+
# module Controllers
|
16
|
+
# class Default
|
17
|
+
#
|
18
|
+
# # Pick up the default controller methods, like param, url, etc.
|
19
|
+
# include Waves::Controllers::Mixin
|
20
|
+
#
|
21
|
+
# # This gets me the parameters associated with this model
|
22
|
+
# def attributes; params[model_name.singular.intern]; end
|
23
|
+
#
|
24
|
+
# # A simple generic delegator to the model
|
25
|
+
# def all; model.all; end
|
26
|
+
#
|
27
|
+
# # Find a specific instance based on a name or raise a 404
|
28
|
+
# def find( name ); model[ :name => name ] or not_found; end
|
29
|
+
#
|
30
|
+
# # Create a new instance based on the request params
|
31
|
+
# def create; model.create( attributes ); end
|
32
|
+
#
|
33
|
+
# # Update an existing record. find will raise a 404 if not found.
|
34
|
+
# def update( name )
|
35
|
+
# instance = find( name )
|
36
|
+
# instance.set( attributes )
|
37
|
+
# instance.save_changes
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# # Find and delete - or will raise a 404 if it doesn't exist
|
41
|
+
# def delete( name ); find( name ).destroy; end
|
42
|
+
#
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# Since the mapping file handles "glueing" controllers to views, controllers don't have to be at all concerned with views. They don't have to set instance variables, layouts, or contain logic to select the appropriate view based on the request. All they do is worry about updating the model when necessary based on the request.
|
2
48
|
|
3
49
|
module Controllers
|
4
50
|
|
51
|
+
#
|
52
|
+
# This mixin provides some handy methods for Waves controllers. You will probably
|
53
|
+
# want to include it in any controllers you define for your application. The default
|
54
|
+
# controllers generated using the +wave+ command already do this.
|
55
|
+
#
|
56
|
+
# Basically, what the mixin does is adapt the class so that it can be used within
|
57
|
+
# mappings (see Waves::Mapping); add some simple reflection to allow controller methods
|
58
|
+
# to be written generically (i.e., without reference to a specific model); and provide
|
59
|
+
# parameter destructuring for the request parameters.
|
60
|
+
#
|
61
|
+
|
5
62
|
module Mixin
|
6
|
-
|
63
|
+
|
7
64
|
attr_reader :request
|
8
65
|
|
9
66
|
include Waves::ResponseMixin
|
10
67
|
|
68
|
+
# When you include this Mixin, a +process+ class method is added to your class,
|
69
|
+
# which accepts a request object and a block. The request object is used to initialize
|
70
|
+
# the controller and the block is evaluated using +instance_eval+. This allows the
|
71
|
+
# controller to be used within a mapping file.
|
72
|
+
|
11
73
|
def self.included( c )
|
12
74
|
def c.process( request, &block )
|
13
75
|
self.new( request ).instance_eval( &block )
|
14
76
|
end
|
15
77
|
end
|
16
78
|
|
17
|
-
def initialize( request )
|
18
|
-
@request = request
|
19
|
-
end
|
79
|
+
def initialize( request ); @request = request; end
|
20
80
|
|
81
|
+
# The params variable is taken from the request object and "destructured", so that
|
82
|
+
# a parameter named 'blog.title' becomes:
|
83
|
+
#
|
84
|
+
# params['blog']['title']
|
85
|
+
#
|
86
|
+
# If you want to access the original parameters object, you can still do so using
|
87
|
+
# +request.parameters+ instead of simply +params+.
|
88
|
+
def params; @params ||= destructure(request.params); end
|
21
89
|
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
90
|
+
# You can access the name of the model related to this controller using this method.
|
91
|
+
# It simply takes the basename of the module and converts it to snake case, so if the
|
92
|
+
# model uses a different plurality, this won't, in fact, be the model name.
|
93
|
+
def model_name; self.class.basename.snake_case; end
|
94
|
+
|
95
|
+
# This uses the model_name method to attempt to identify the model corresponding to this
|
96
|
+
# controller. This allows you to write generic controller methods such as:
|
97
|
+
#
|
98
|
+
# model.find( name )
|
99
|
+
#
|
100
|
+
# to find an instance of a given model. Again, the plurality of the controller and
|
101
|
+
# model must be the same for this to work.
|
102
|
+
def model; Waves.application.models[ model_name.intern ]; end
|
34
103
|
|
35
104
|
private
|
36
105
|
|
data/lib/dispatchers/base.rb
CHANGED
@@ -11,29 +11,40 @@ module Waves
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
# The Base dispatcher simply makes it easier to write dispatchers by inheriting
|
15
|
+
# from it. It creates a Waves request, ensures the request processing is done
|
16
|
+
# within a mutex, benchmarks the request processing, logs it, and handles common
|
17
|
+
# exceptions and redirects. Derived classes need only process the request within
|
18
|
+
# their +safe+ method, which takes a Waves::Request and returns a Waves::Response.
|
19
|
+
|
14
20
|
class Base
|
15
21
|
|
22
|
+
# Like any Rack application, Waves' dispatchers must provide a call method
|
23
|
+
# taking an +env+ parameter.
|
16
24
|
def call( env )
|
17
25
|
Waves::Server.synchronize do
|
18
26
|
request = Waves::Request.new( env )
|
19
27
|
response = request.response
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
t = Benchmark.realtime do
|
29
|
+
begin
|
30
|
+
safe( request )
|
31
|
+
rescue Dispatchers::Redirect => redirect
|
32
|
+
response.status = '302'
|
33
|
+
response.location = redirect.path
|
34
|
+
rescue Dispatchers::NotFoundError => e
|
35
|
+
html = Waves.application.views[:errors].process( request ) do
|
36
|
+
not_found_404( :error => e )
|
37
|
+
end
|
38
|
+
response.status = '404'
|
39
|
+
response.content_type = 'text/html'
|
40
|
+
response.write( html )
|
41
|
+
end
|
42
|
+
end
|
43
|
+
Waves::Logger.info "#{request.method}: #{request.url} handled in #{(t*1000).round} ms."
|
33
44
|
response.finish
|
34
45
|
end
|
35
|
-
|
36
|
-
|
46
|
+
end
|
47
|
+
|
37
48
|
end
|
38
49
|
|
39
50
|
end
|
data/lib/dispatchers/default.rb
CHANGED
@@ -2,13 +2,34 @@ module Waves
|
|
2
2
|
|
3
3
|
module Dispatchers
|
4
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
|
+
|
5
23
|
class Default < Base
|
6
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
|
7
28
|
def safe( request )
|
8
29
|
|
9
30
|
response = request.response
|
10
|
-
|
11
|
-
Waves::Server.
|
31
|
+
|
32
|
+
Waves::Server.reload if Waves::Server.debug?
|
12
33
|
response.content_type = Waves::Server.config.mime_types[ request.path ] || 'text/html'
|
13
34
|
|
14
35
|
mapping = Waves::Server.mapping[ request ]
|
@@ -16,14 +37,14 @@ module Waves
|
|
16
37
|
mapping[:before].each do | block, args |
|
17
38
|
ResponseProxy.new(request).instance_exec(*args,&block)
|
18
39
|
end
|
19
|
-
|
40
|
+
|
20
41
|
block, args = mapping[:action]
|
21
42
|
response.write( ResponseProxy.new(request).instance_exec(*args, &block) )
|
22
43
|
|
23
44
|
mapping[:after].each do | block, args |
|
24
45
|
ResponseProxy.new(request).instance_exec(*args,&block)
|
25
46
|
end
|
26
|
-
|
47
|
+
|
27
48
|
end
|
28
49
|
|
29
50
|
end
|