waves 0.6.7 → 0.6.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/app/Rakefile +1 -1
  2. data/app/configurations/default.rb.erb +1 -1
  3. data/app/configurations/mapping.rb.erb +6 -37
  4. data/app/controllers/default.rb.erb +10 -20
  5. data/app/lib/tasks/cluster.rb +8 -10
  6. data/app/lib/tasks/generate.rb +15 -0
  7. data/app/lib/tasks/schema.rb +6 -5
  8. data/app/schema/{migration → migrations}/templates/empty.rb.erb +0 -0
  9. data/app/templates/errors/{not_found.mab → not_found_404.mab} +0 -0
  10. data/app/templates/errors/{server_error.mab → server_error_500.mab} +0 -0
  11. data/app/templates/layouts/default.mab +1 -1
  12. data/bin/waves +1 -1
  13. data/bin/waves-console +21 -4
  14. data/lib/controllers/mixin.rb +85 -16
  15. data/lib/dispatchers/base.rb +26 -15
  16. data/lib/dispatchers/default.rb +25 -4
  17. data/lib/helpers/common.rb +50 -10
  18. data/lib/helpers/form.rb +18 -1
  19. data/lib/helpers/formatting.rb +17 -9
  20. data/lib/helpers/model.rb +20 -2
  21. data/lib/helpers/view.rb +12 -2
  22. data/lib/mapping/mapping.rb +187 -0
  23. data/lib/mapping/pretty_urls.rb +95 -0
  24. data/lib/renderers/erubis.rb +3 -1
  25. data/lib/renderers/markaby.rb +6 -4
  26. data/lib/renderers/mixin.rb +12 -0
  27. data/lib/runtime/application.rb +33 -23
  28. data/lib/runtime/configuration.rb +131 -9
  29. data/lib/runtime/console.rb +2 -2
  30. data/lib/runtime/logger.rb +42 -18
  31. data/lib/runtime/mime_types.rb +8 -4
  32. data/lib/runtime/request.rb +14 -5
  33. data/lib/runtime/response.rb +15 -1
  34. data/lib/runtime/response_mixin.rb +29 -47
  35. data/lib/runtime/server.rb +60 -35
  36. data/lib/runtime/session.rb +14 -1
  37. data/lib/utilities/integer.rb +7 -1
  38. data/lib/utilities/kernel.rb +28 -2
  39. data/lib/utilities/module.rb +3 -0
  40. data/lib/utilities/object.rb +5 -1
  41. data/lib/utilities/string.rb +7 -2
  42. data/lib/utilities/symbol.rb +2 -0
  43. data/lib/views/mixin.rb +55 -1
  44. data/lib/waves.rb +9 -2
  45. metadata +10 -8
  46. data/lib/runtime/mapping.rb +0 -82
@@ -1,8 +1,14 @@
1
1
  module Waves
2
+ # Waves::Response represents an HTTP response and has methods for constructing a response.
3
+ # These include setters for +content_type+, +content_length+, +location+, and +expires+
4
+ # headers. You may also set the headers directly using the [] operator. If you don't find
5
+ # what you are looking for here, check the documentation for Rack::Response since many
6
+ # methods for this class are delegated to Rack::Response.
2
7
  class Response
3
8
 
4
9
  attr_reader :request
5
10
 
11
+ # Create a new response. Takes the request object. You shouldn't need to call this directly.
6
12
  def initialize( request )
7
13
  @request = request
8
14
  @response = Rack::Response.new
@@ -10,14 +16,22 @@ module Waves
10
16
 
11
17
  %w( Content-Type Content-Length Location Expires ).each do |header|
12
18
  define_method( header.downcase.gsub('-','_')+ '=' ) do | val |
13
- @response.headers[header] = val
19
+ @response[header] = val
14
20
  end
15
21
  end
16
22
 
23
+ # Returns the sessions associated with the request, allowing you to set values within it.
24
+ # The session will be created if necessary and saved when the response is complete.
17
25
  def session ; request.session ; end
18
26
 
27
+ # Finish the response. This will send the response back to the client, so you shouldn't
28
+ # attempt to further modify the response once this method is called. You don't usually
29
+ # need to call it yourself, since it is called by the dispatcher once request processing
30
+ # is finished.
19
31
  def finish ; request.session.save ; @response.finish ; end
20
32
 
33
+ # Methods not explicitly defined by Waves::Response are delegated to Rack::Response.
34
+ # Check the Rack documentation for more informations
21
35
  def method_missing(name,*args)
22
36
  @response.send(name,*args)
23
37
  end
@@ -1,53 +1,35 @@
1
1
  module Waves
2
2
 
3
+ # Defines a set of methods that simplify accessing common request and response methods.
4
+ # These include methods not necessarily associated with the Waves::Request and Waves::Response
5
+ # objects, but which may still be useful for constructing a response.
6
+ #
7
+ # This mixin assumes that a @request@ accessor already exists.
3
8
  module ResponseMixin
4
-
5
- # assumes request method
6
-
7
- def response
8
- request.response
9
- end
10
-
11
- def params
12
- request.params
13
- end
14
-
15
- def session
16
- request.session
17
- end
18
-
19
- def path
20
- request.path
21
- end
22
-
23
- def url
24
- request.url
25
- end
26
-
27
- def domain
28
- request.domain
29
- end
30
-
31
- def redirect(location)
32
- request.redirect(location)
33
- end
34
-
35
- def models
36
- Waves.application.models
37
- end
38
-
39
- def views
40
- Waves.application.views
41
- end
42
-
43
- def controllers
44
- Waves.application.controllers
45
- end
46
-
47
- def not_found
48
- request.not_found
49
- end
50
-
9
+ # Access the response.
10
+ def response; request.response; end
11
+ # Access the request parameters.
12
+ def params; request.params; end
13
+ # Access the request session.
14
+ def session; request.session; end
15
+ # Access the request path.
16
+ def path; request.path; end
17
+ # Access the request url.
18
+ def url; request.url; end
19
+ # Access the request domain.
20
+ def domain; request.domain; end
21
+ # Issue a redirect for the given location.
22
+ def redirect(location); request.redirect(location); end
23
+ # Access the primary application's models
24
+ def models; Waves.application.models; end
25
+ # Access the primary application's views
26
+ def views; Waves.application.views; end
27
+ # Access the primary application's controllers
28
+ def controllers; Waves.application.controllers; end
29
+ # Raise a "not found" exception.
30
+ def not_found; request.not_found; end
31
+ # Access the Waves::Logger.
32
+ def log; Waves::Logger; end
51
33
  end
52
34
 
53
35
  end
@@ -1,58 +1,83 @@
1
1
  module Waves
2
-
2
+ # You can run the Waves::Server via the +waves-server+ command or via <tt>rake cluster:start</tt>. Run <tt>waves-server --help</tt> for options on the <tt>waves-server</tt> command. The <tt>cluster.start</tt> task use the +mode+ environment parameter to determine which configuration to use. You can define +port+ to run on a single port, or +ports+ (taking an array) to run on multiple ports.
3
+ #
4
+ # *Example*
5
+ #
6
+ # Assume that +ports+ is set in the development configuration like this:
7
+ #
8
+ # ports [ 2020, 2021, 2022 ]
9
+ #
10
+ # Then you could start up instances on all three ports using:
11
+ #
12
+ # rake cluster:start mode=development
13
+ #
14
+ # This is the equivalent of running:
15
+ #
16
+ # waves-server -c development -p 2020 -d
17
+ # waves-server -c development -p 2021 -d
18
+ # waves-server -c development -p 2022 -d
19
+ #
3
20
  class Server < Application
4
-
21
+
22
+ # Access the server thread.
5
23
  attr_reader :thread
6
24
 
25
+ # Access the host we're binding to (set via the configuration).
26
+ def host ; options[:host] || config.host ; end
27
+ # Access the port we're listening on (set via the configuration).
28
+ def port ; options[:port] || config.port ; end
29
+ # Run the server as a daemon. Corresponds to the -d switch on +waves-server+.
30
+ def daemonize
31
+ pwd = Dir.pwd
32
+ Daemonize.daemonize( Waves::Logger.output )
33
+ Dir.chdir(pwd)
34
+ File.write( :log / $$+'.pid', '' )
35
+ end
36
+ # Start and / or access the Waves::Logger instance.
37
+ def log ; @log ||= Waves::Logger.start ; end
38
+ # Start the server.
7
39
  def start
8
40
  load( :lib / 'startup.rb' )
9
- Waves::Logger.start
41
+ daemonize if options[:daemon]
10
42
  log.info "** Waves Server Starting ..."
11
-
12
- t = Benchmark.realtime do
13
- app = config.application.to_app
14
- @server = ::Mongrel::HttpServer.new( config.host, config.port )
15
- @server.register('/', Rack::Handler::Mongrel.new( app ) )
16
- trap('INT') { puts; stop }
17
- @thread = @server.run
18
- end
19
- log.info "** Waves Server Running on #{config.host}:#{config.port}"
20
- log.info "Server started in #{(t*1000).round} milliseconds."
43
+ t = real_start
44
+ log.info "** Waves Server Running on #{host}:#{port}"
45
+ log.info "Server started in #{(t*1000).round} ms."
21
46
  @thread.join
22
47
  end
23
-
48
+ # Stop the server.
24
49
  def stop
25
50
  log.info "** Waves Server Stopping ..."
51
+ pid_file = :log / $$ + '.pid'
52
+ FileUtils.rm( pid_file ) if options[:daemon] and File.exist?( pid_file )
26
53
  @server.stop
27
54
  log.info "** Waves Server Stopped"
28
55
  end
29
-
30
- def synchronize( &block )
31
- ( @mutex ||= Mutex.new ).synchronize( &block )
32
- end
33
-
34
- def cache
35
- #@cache ||= MemCache::new '127.0.0.1'
36
- end
37
-
38
- # make this a singleton ... we don't use Ruby's std lib
39
- # singleton module because it doesn't do quite what we
40
- # want - need a run method and implicit instance method
56
+ # Provides access to the server mutex for thread-safe operation.
57
+ def synchronize( &block ) ; ( @mutex ||= Mutex.new ).synchronize( &block ) ; end
58
+
41
59
  class << self
42
-
43
60
  private :new, :dup, :clone
44
-
45
- def run( mode = :development )
46
- @server.stop if @server; @server = new( mode ); @server.start
61
+ # Start or restart the server.
62
+ def run( options={} )
63
+ @server.stop if @server; @server = new( options ); @server.start
47
64
  end
48
-
49
- # allow Waves::Server to act as The Server Instance
65
+ # Allows us to access the Waves::Server instance.
50
66
  def method_missing(*args); @server.send(*args); end
51
-
67
+ # Probably wouldn't need this if I added a block parameter to method_missing.
52
68
  def synchronize(&block) ; @server.synchronize(&block) ; end
53
-
69
+ end
70
+
71
+ private
72
+
73
+ def real_start
74
+ Benchmark.realtime do
75
+ @server = ::Mongrel::HttpServer.new( host, port )
76
+ @server.register('/', Rack::Handler::Mongrel.new( config.application.to_app ) )
77
+ trap('INT') { puts; stop }; @thread = @server.run
78
+ end
54
79
  end
55
80
 
56
81
  end
57
82
 
58
- end
83
+ end
@@ -1,11 +1,22 @@
1
1
  module Waves
2
+
3
+ # Encapsulates the session associated with a given request. A session has an expiration
4
+ # and path. You must set these in your configuration file. See Waves::Configuration for
5
+ # more information.
6
+ #
2
7
  class Session
3
8
 
9
+ # Create a new session object using the given request. This is not necessarily the
10
+ # same as constructing a new session. The session_id cookie for the request domain
11
+ # is used to store a session id. The actual session data will be stored in a directory
12
+ # specified by the application's configuration file.
4
13
  def initialize( request )
5
14
  @request = request
6
15
  @data ||= ( File.exist?( session_path ) ? load_session : {} )
7
16
  end
8
17
 
18
+ # Save the session data. You shouldn't typically have to call this directly, since it
19
+ # is called by Waves::Response#finish.
9
20
  def save
10
21
  if @data && @data.length > 0
11
22
  File.write( session_path, @data.to_yaml )
@@ -15,13 +26,15 @@ module Waves
15
26
  end
16
27
  end
17
28
 
29
+ # Access a given data element of the session using the given key.
18
30
  def [](key) ; @data[key] ; end
31
+ # Set the given data element of the session using the given key and value.
19
32
  def []=(key,val) ; @data[key] = val ; end
20
33
 
21
34
  private
22
35
 
23
36
  def session_id
24
- @request.cookies['session_id'] || generate_session_id
37
+ @session_id ||= ( @request.cookies['session_id'] || generate_session_id )
25
38
  end
26
39
 
27
40
  def generate_session_id # from Camping ...
@@ -1,8 +1,14 @@
1
+ # Added methods to Integer for commonly used "numeric phrases" like 6.days or 30.megabytes.
1
2
  class Integer
2
3
  def seconds ; self ; end
3
4
  def minutes ; self * 60 ; end
4
5
  def hours ; self * 60.minutes ; end
5
6
  def days ; self * 24.hours ; end
6
7
  def weeks ; self * 7.days ; end
7
- # months and years are not precise
8
+ def bytes ; self ; end
9
+ def kilobytes ; self * 1024 ; end
10
+ def megabytes ; self * 1024.kilobytes ; end
11
+ def gigabytes ; self * 1024.megabytes ; end
12
+ def terabytes ; self * 1024.gigabytes ; end
13
+ def petabytes ; self * 1024.gigabytes ; end
8
14
  end
@@ -1,8 +1,34 @@
1
1
  module Kernel
2
-
3
- # inspired by similar function in rails ...
2
+ #
3
+ # From Rails. This comes in handy when you want to return a value that you need to modify.
4
+ # So instead of the awkward:
5
+ #
6
+ # foo = Foo.new
7
+ # foo.bar = 'bar'
8
+ # foo
9
+ #
10
+ # You can just say
11
+ #
12
+ # returning Foo.new { |foo| foo.bar = 'bar' }
13
+ #
4
14
  def returning( object, &block )
5
15
  yield object; object
6
16
  end
17
+
18
+ #
19
+ # Inspired by a question on comp.lang.ruby. Sort of like returning, except
20
+ # you don't need to pass in the returnee as an argument. The drawback is that,
21
+ # since this relies on instance_eval, you can't access in methods in the local
22
+ # scope. You can work around this by assigning values to variables, but in that
23
+ # case, you might be better off using returning.
24
+ #
25
+ # Our above example would look like this:
26
+ #
27
+ # with( Foo.new ) { bar = 'bar' }
28
+ #
29
+ def with( object, &block )
30
+ object.instance_eval(&block); object
31
+ end
32
+
7
33
 
8
34
  end
@@ -1,5 +1,8 @@
1
1
  class Module
2
2
 
3
+ # This comes in handy when you are trying to do meta-programming with modules / classes
4
+ # that may be nested within other modules / classes. I think I've seen it defined in
5
+ # facets, but I'm not relying on facets just for this one method.
3
6
  def basename
4
7
  self.name.split('::').last || ''
5
8
  end
@@ -1,5 +1,9 @@
1
1
  class Object
2
- # this code from mauricio fernandez via ruby-talk
2
+ # This is an extremely powerful little function that will be built-in to Ruby 1.9.
3
+ # This version is from Mauricio Fernandez via ruby-talk. Works like instance_eval
4
+ # except that you can pass parameters to the block. This means you can define a block
5
+ # intended for use with instance_eval, pass it to another method, which can then
6
+ # invoke with parameters. This is used quite a bit by the Waves::Mapping code.
3
7
  def instance_exec(*args, &block)
4
8
  mname = "__instance_exec_#{Thread.current.object_id.abs}"
5
9
  class << self; self end.class_eval{ define_method(mname, &block) }
@@ -1,13 +1,18 @@
1
+ # Waves extends String with a variety of methods for changing from singular to plural and back, and switching to different types of case and word separators. These methods are similar to those found in Rails and other frameworks, but some (all?) of the names are different. The names here were chosen for increased clarity and are hopefully easy to adjust to ...
2
+ #
3
+ # Notably, the inflector code here is not as comprehensive as the Rails code. This will be fixed in a future version of Waves.
4
+
1
5
  class String
2
6
 
7
+ # Does a File.join on the two arguments joined by the /. Very handy
8
+ # for doing platform-safe paths without having to use File.join.
9
+ #
3
10
  # I unfortunately don't recall where i first saw this ... see
4
11
  # Symbol extension as well, allowing for :files / 'afilename.txt'
5
12
 
6
13
  def / ( string )
7
14
  File.join(self,string.to_s)
8
15
  end
9
-
10
- # inspired by the inflector code in rails ...
11
16
 
12
17
  def singular
13
18
  gsub(/ies$/,'y').gsub(/es$/,'').gsub(/s$/,'')
@@ -1,4 +1,6 @@
1
1
  class Symbol
2
+ # Does File.join on self and string, converting self to string before invocation.
3
+ # See String#/ for more.
2
4
  def / ( string )
3
5
  self.to_s / string
4
6
  end
data/lib/views/mixin.rb CHANGED
@@ -1,9 +1,63 @@
1
1
  module Waves
2
2
 
3
+ # Views in Waves are ultimately responsible for generating the response body. Views mirror controllers - both have full access to the request and response, and both may modify the response and even short-circuit the request processing and return a result by calling redirect or calling Response#finish.
4
+ #
5
+ # Views, like controllers, are classes with methods that are invoked by a mapping block (see Waves::Mapping). View instance methods take an assigns hash that are typically converted into instance variables accesible from within a template.
6
+ #
7
+ # Like controllers, a default implementation is provided by the +waves+ command when you first create your application. This default can be overridden to change the behaviors for all views, or you can explicitly define a View class to provide specific behavior for one view.
8
+ #
9
+ # The default implementation simply determines which template to render and renders it, returning the result as a string. This machinery is provided by the View mixin, so it is easy to create your own View implementations.
10
+ #
11
+ # = Templates
12
+ #
13
+ # Although you won't typically need to modify or define View classes, you will often create and modify templates. Templates can be evaluated using any registered Renderer. Two are presently packaged with Waves: Markaby and Erubis. Templates have access to the assigns hash passed to the view method as instance variables. These can be explicitly defined by the mapping file or whomever is invoking the view.
14
+ #
15
+ # *Example*
16
+ #
17
+ # # Find the story named 'home' and pass it into the view template as @story
18
+ # use( :story ) | controller { find( 'home' ) } | view { |x| show( :story => x ) }
19
+ #
20
+ # = Helpers
21
+ #
22
+ # Helper methods can be defined for any view template by simply defining them within the default Helper module in <tt>helpers/default.rb</tt> of the generated application. Helpers specific to a particular View class can be explicitly defined by creating a helper module that corresponds to the View class. For examples, for the +User+ View class, you would define a helper module in <tt>user.rb</tt> named +User+.
23
+ #
24
+ # The default helper class initially includes a wide-variety of helpers, including helpers for layouts, Textile formatting, rendering forms, and nested views, as well as helpers for accessing the request and response objects. More helpers will be added in future releases, but in many cases, there is no need to include all of them in your application.
25
+ #
26
+ # = Layouts
27
+ #
28
+ # Layouts are defined using the +Layout+ view class, and layout templates are placed in the +layout+ directory of the +templates+ directory. Layouts are explicitly set within a template using the +layout+ method.
29
+ #
30
+ # *Example*
31
+ #
32
+ # layout :default, :title => @story.title do
33
+ # h1 @story.title
34
+ # textile @story.content
35
+ # end
36
+ #
37
+ #
38
+ # The layout method takes a name and an assigns hash that will be available within the layout template as instance variables. In this example, <tt>@title</tt> will be defined as <tt>@story.title</tt> within the layout template named 'default.'
39
+ #
40
+ # Any number of layouts may be included within a single view, and layouts may even be nested within layouts. This makes it possible to create large numbers of highly structured views that can be easily changed with minimal effort. For example, you might a specific layout associated with form elements. By incorporating this into your views as a +layout+ template, you can make changes across all your forms by changing this single template.
41
+ #
42
+ # = Nested Views
43
+ #
44
+ # It is easy to include one view inside another. A common use for this is to define one set of templates for reusable content 'fragments' (somewhat akin to partials in Rails) and another set that incorporate these fragments into specific layouts. Including a view from within another view is done, logically enough, using the +view+ method.
45
+ #
46
+ # *Example*
47
+ #
48
+ # # include the summary view for Story
49
+ # view :story, :summary, :story => @story
50
+ #
51
+ # = Request And Response Objects
52
+ #
53
+ # As always, the request and response objects, and a wide-variety of short-cut methods, are available within view templates via the Waves::ResponseMixin.
54
+ #
55
+
3
56
  module Views
4
57
 
5
58
  class NoTemplateError < Exception ; end
6
59
 
60
+ # A class method that returns the known Renderers, which is any module that is defined within Waves::Renderers and includes the Renderers::Mixin. You can define new Renderers simply be reopening Waves::Renderers and defining a module that mixes in Renderers::Mixin.
7
61
  def Views.renderers
8
62
  return [] if Renderers.constants.nil?
9
63
  Renderers.constants.inject([]) do |rx,cname|
@@ -12,7 +66,7 @@ module Waves
12
66
  end
13
67
  end
14
68
 
15
-
69
+ # The View mixin simply sets up the machinery for invoking a template, along with methods for accessing the request context and the standard interface for invoking a view method.
16
70
  module Mixin
17
71
 
18
72
  attr_reader :request