public
Rubygem
Description: Resource-oriented open source Ruby framework for Web apps.
Homepage: http://rubywaves.com/
Clone URL: git://github.com/dyoder/waves.git
waves /
name age message
file .gitignore Thu Sep 18 11:55:21 -0700 2008 Layer-level dependencies just don't work for fr... [ab5tract]
file README.rdoc Thu Jun 19 09:59:34 -0700 2008 RDoc changes made for 0.7.5 [automatthew]
file Rakefile Mon Sep 22 13:57:00 -0700 2008 Rakefile: updated version numbers dyoder-fileba... [ab5tract]
directory app/ Thu Sep 18 11:55:21 -0700 2008 The beginning of Cache [ab5tract]
directory bin/ Fri Oct 03 20:38:03 -0700 2008 waves-console requires needed help [automatthew]
directory doc/ Thu Sep 04 10:11:36 -0700 2008 tutorial textile [automatthew]
directory lib/ Sat Oct 04 01:25:27 -0700 2008 added exceptions and rescues for 401 and 400 codes [automatthew]
directory samples/ Sat Oct 04 01:25:27 -0700 2008 HTTP Basic authentication example in blog [automatthew]
directory verify/ Wed Sep 24 15:54:49 -0700 2008 thread test redux, plus cleanup tweaks [automatthew]
file waves.gemspec Fri Oct 03 20:42:10 -0700 2008 String Interpolation Nazi strikes again [automatthew]
README.rdoc

Waves

An open source framework for building web-applications with Ruby.

Waves is … Full-featured and thread-safe. Compact and extensible. Configuration and convention. RESTful but also Magical (very important).

Waves is powered by … Rack and Mongrel (HTTP server), Sequel or Filebase (storage), AutoCode (code reloading), LiveConsole (hot patching).

Links

  • {Web Site}[http://rubywaves.com/]
    • {Tutorial}[http://rubywaves.com/tutorial]
    • {Contributors}[http://rubywaves.com/credits]
  • {Source}[http://github.com/dyoder/waves]
  • {Issue tracker}[http://waves.lighthouseapp.com]
  • {Mailing List}[http://groups.google.com/group/rubywaves/]
  • {RubyForge}[http://rubyforge.org/projects/waves/]
  • {RDoc}[http://waves.rubyforge.org/]

Bootstrap!

Get Waves

  # latest release from Rubyforge
  gem install waves

  # relatively recent gem build from master on GitHub
  gem install dyoder-waves --source=http://gems.github.com

  # get the framework source
  git clone git://github.com/dyoder/waves.git
  cd waves
  rake setup # install gem dependencies needed to work from source

Generate an application

  # working from gems
  waves ~/dev/web/killer_app

  # working from source
  ./bin/waves ~/dev/web/killer_app

This generates a default application in the target directory. The application module’s name is the constant-cased version of the target directory basename, in this case KillerApp.

Configure basic settings

Configure your database connection using the database attribute in configurations/development.rb. The default ORM is currently Sequel, but there are other ORM layers in the works. The value of database is used with {Sequel.connect}[http://sequel.rubyforge.org/classes/Sequel.html#M000069]. Sequel’s current documentation seems to favor a URL-style argument, but we have been getting along fine with a hash.

  # With the Sequel sqlite adapter, the :database parameter is read as a path
  # relative to the application root.
  database :adapter => 'sqlite', :database => 'killer_app.db'

Create the initial db schema. You can create a {Sequel migration}[http://sequel.rubyforge.org/classes/Sequel/Migration.html] with:

  rake schema:migration name=users
  vi schema/migrations/001_users.rb

And you can run migrations with:

  rake schema:migrate

  # or with a version number
  rake schema:migrate version=1

Mappings

Mappings are the Waves equivalent to routes. An individual mapping consists of a request-matching construct and an arbitrary block. When a request matches a mapping, Waves runs that block. In the simplest case, you do all the response work in the block, a la {Sinatra}[http://sinatrarb.com/Home]. The standard Waves application also offers an MVC infrastructure, with a silent R for Resource.

An application’s mappings live in configurations/mapping.rb. The generated default mixes in some RESTy mappings as a helpful starter:

  • Waves::Mapping::PrettyUrls::RestRules
  • Waves::Mapping::PrettyUrls::GetRules

Here’s an example of a mapping adapted from GetRules:

  # define some regexes
  model_regex = '([\w\-]+)'
  name_regex = '([\w\-\_\.\+\@]+)';

  # display the given resource for the given model
  path %r{^/#{model_regex}/#{name_regex}/?$}, :method => :get do | model, name |
    resource( model ) do
      controller { find( name ) }  |  view { |data| show( model => data ) }
    end
  end

For convenience, we defined regexes to match the model and name components of a path. The path method registers a block for use with requests where the path matches the supplied regex and where the HTTP method is GET. The parameters passed to the block are the MatchData captures, i.e. the strings caught by model_regex and name_regex. Note that you can supply strings instead of regexes to match exact text. There is also a url method for matching against the entire URL.

So a GET to "/user/vyvyan", for example, will pass "user" and "vyvyan" to the block as the model and name parameters, respectively.

The resource method uses its argument to determine which controller and view will be instantiated for work done in its block. Thus using the example above, we instantiate KillerApp::Controllers::User when we call controller and KillerApp::Views::User when we call view.

The "|" character, as seen between the controller and view invocations, is a method that causes the result of the controller block to be passed into the view block. Thus, in the controller block, an instance of Controllers::User calls its find method with "vyvyan" as the argument. The resulting object is passed to an instance of Views::User, which calls show( "user" => <some user object>).

When undefined methods are called on a View, it attempts to render a template named after the method, with the method argument passed into the renderer as instance variables. In the present example, the view will try to render templates/user/show.mab (or show.erb), providing it with @user = <the user object>. You can, of course, define methods in the View to override this.

You can also register before, after, wrap, and always mappings, with the same flexibility in matching requests. The handle method registers exception handling blocks, matching the exception class as well as the usual request attributes.

For more complete documentation, see Waves::Mapping.

Running an application

  cd killer_app

  # defaults to running on 127.0.0.1:3000, using the development config, not daemonized.
  waves-server -h 0.0.0.0 -p 3001 -c production --daemon

There are also rake tasks for running a cluster:

  rake cluster:start mode=production # will read the Production config to determine which ports.

You can work in an irb-based console:

  waves-console # or bin/waves-console

Where the Wavy Things Are

A Waves application consists of a Ruby module structured with internal namespaces. Thus:

   KillerApp::Configurations
   KillerApp::Controllers
   KillerApp::Helpers
   KillerApp::Models
   KillerApp::Views

As you might have guessed, the working classes live in these namespaces. E.g. KillerApp::Models::User.

The otherwise harsh birth and life of these working classes is softened by {AutoCode}[http://autocode.rubyforge.org/], a sort of mini-Industrial Revolution for Ruby constants. The Waves framework uses AutoCode to automatically generate controllers, models, views, etc. the first time each constant is needed by the application. Waves looks for a file in a path that corresponds to the namespaced constant name. If such exists, it gets loaded. If not, Waves creates the class or module from sensible defaults.

For example, when a Waves application encounters KillerApp::Views::MonkeyShines for the first time, it tries to load it from ./views/monkey_shines.rb. If that file does not exist, the app creates KillerApp::Views::MonkeyShines as a dupe of Waves::Views::Base.

The result is that you only need to define models, views, etc. when the default behavior stops meeting your needs.

Directory structure

  ./
    bin/
      waves-console
      waves-server
    configurations/    # auto_load defined for Mapping; (auto_load || auto_create) anything else
    controllers/       # (auto_load || auto_create)
    helpers/           # (auto_load || auto_create)
    lib/
      application.rb   # Application requires and includes, plus your configuration needs.
      tasks/           # .rb and .rake files in here are automatically required by the main Rakefile
    models/            # (auto_load || auto_create)
    schema/
      migrations/
    startup.rb         # Framework setup.  Don't touch.
    templates/         # Views expect templates to live here.
    views/             # (auto_load || auto_create)

Web Server

You can run on any {Rack-supported Web server}[], including Thin and Mongrel. Just edit your configuration file, like you did for your database settings(configurations/development.rb) and change the <tt>handler parameter. The default is Mongrel. For example, to switch to Thin:

  handler ::Rack::Handler::Thin, :Host => host, :Port => port

In addition, you can also configure your Rack application the same way. Just edit the application parameter:

  application do
    use ::Rack::ShowExceptions
    use Rack::CommonLogger
    run ::Waves::Dispatchers::Default.new
  end