The Happy Book of Happy

Welcome to the Happy Book of Happy, an introduction on how to develop web applications using Happy, a cute little web application toolkit for Ruby! Let's get straight to it, shall we?


The Basics

"Hello World" with Happy

So, without further ado, here's the Happy version of "Hello World":

# config.ru
require 'happy'

class MyApp < Happy::Controller
  def route
    "Hello World"
  end
end

run MyApp

You can run this example (assuming it's inside a file called config.ru) by simply issuing the rackup command. Try rackup --help for available options.

Happy Controllers

So, the previous chapter's "Hello World" example isn't terribly exciting, but there's a couple of interesting things to note here.

First of all, note how you're creating a subclass of Happy::Controller. That class is central to how Happy works; pretty much everything you do in Happy revolves around controllers.

Unlike your may know it from frameworks like Ruby on Rails, controllers are really self-contained applications that describe behaviour. They can be very simple (simply rendering a string, like the example above), but also very complex (for example, they could provide a complete blog engine, or admin backend.)

So here's some of the things a Happy controller can do:

  • serve a simple string-like response
  • render a template
  • process URL paths
  • pass control over the request to another controller (or Rack app)

Note for friends of Rack: Happy is a very Rack-friendly framework. Happy controllers are also just Rack apps, so you can mount them anywhere that you can mount Rack apps (like inside another Rails application). Happy also happily mounts non-Happy Rack apps, so it works both ways. Life is good.

The route method

Pretty much the most important part of any Happy controller is the route method. In your own controller classes, you're expected to override this method to implement your controller's request handling logic.

And this is where Happy is very different from most other web frameworks: instead of dispatching incoming requests among a set of controller classes or actions, Happy will route each and every request -- no exceptions -- through your application controller's route method. It is then up to this method to decide on how to deal with the request.

Let's take a look at some examples in the following chapters.

Handling paths

In most web applications, you will probably want to serve different responses depending on the URL accessed. In Happy, you use the on method to define the behavior for specific URL paths. Here's a simple example:

class MyApp < Happy::Controller
  def route
    # When /foo is accessed, respond with 'bar!'
    on 'foo' do
      'bar!'
    end

    # Paths can be nested, of course:
    on 'one' do
      on 'two' do
        'one and two!'
      end

      'just one!'
    end

    'Try /foo, /one or /one/two!'
  end
end

In other words, on lets you provide code blocks that are only executed when a certain path has been requested. Note that on calls can be nested; also note that if one of these blocks returns just a string value, it will be rendered as the response.

Reacting on specific HTTP verbs

While the on command doesn't care about the HTTP verb being used to make the request, there's also on_get, on_post, on_put and on_delete. Note that these can also be called without a path argument. Here's an example:

class MyApp < Happy::Controller
  def route
    on 'resource' do
      on_get    { 'You accessed /resource using GET!' }
      on_post   { 'You accessed /resource using POST!' }
      on_put    { 'You accessed /resource using PUT!' }
      on_delete { 'You accessed /resource using DELETE!' }
    end

    'Try /resource with any HTTP verb!'
  end
end

Setting headers

You can use the header method to set any type of HTTP header for your response. Example:

# Set the 'Content-type' header to 'text/css'
header :content_type, 'text/css'

There's a couple of shortcut methods available to set certain headers directly. Examples:

content_type 'text/css'
cache_control 'public, max-age=3600, must-revalidate'

For a complete list, please check the documentation for Happy::Controller::Actions.

Rendering view templates

Obviously, just serving simple strings isn't really what you want in most applications, so Happy also allows you to render view templates through myriad of available template engines. (Happy uses the tilt gem, so any template engine supported by tilt is also supported by Happy.)

All template rendering is done through the render method. Simply pass the name of a template file. For example:

class MyApp < Happy::Controller
  def route
    on('info') { render 'info.erb' }
    on('help') { render 'help.erb' }

    render 'home.erb'
  end
end

The default directory Happy will look for view templates in is the views/ subdirectory of your application. This is configurable, of course; more on configuration alter.

Using layouts

Happy allows you to define a view template as a layout template, applying it to every response generated. Use the layout command to set it. For example:

class MyApp < Happy::Controller
  def route
    layout 'layouts/default.erb'

    # From a previous example...
    on('info') { render 'info.erb' }
    on('help') { render 'help.erb' }

    on('admin') do
      # use a different layout inside the admin section
      layout 'layouts/admin.erb'
      render 'admin.erb'
    end

    render 'home.erb'
  end
end

The layout file itself should contain an invocation of yield where the inner part of the response should appear.

Adding view helpers

In Happy, view templates are rendered within the scope of your controller (as opposed to a specific view context object), so any method from your controller will also be available in your views.

It is advised, however, that you specifically declare helper methods using the helper command so that they are available to all controllers. This is necessary if you modularize your applications into several different controller classes that share view files.

Here's an example:

class MyApp < Happy::Controller
  # You can provide a block that contains method definitions.
  #
  helpers do
    def some_helper
      'something useful'
    end
  end

  # Alternatively, you can provide a module that contains your
  # helper methods
  #
  helpers MyHelpers

  def route
    # home.erb contains calls to helper methods
    render 'home.erb'
  end
end

Serving responses explicitly

You will have noticed by now that if a path block (or the route method itself) returns a simple string, it will be used for the response body. However, you can also serve responses explicitly using the serve! method. Example:

class MyApp < Happy::Controller
  def route
    serve! 'my response', :content_type => 'text/plain'
  end
end

Note that calling serve! will finish processing of the current request.

Passing control over the request to another controller or Rack app

Happy allows you to modularize your application into several separate controller classes. For example, you could put your admin backend's code into a separate controller and then invoke it within the /admin path. For this, you would use the run command. Kinda like this:

class MyAdminController < Happy::Controller
  def route
    on 'users' do
      # ...
      # do some user admin stuff here
    end

    on 'articles' do
      # ...
      # do some articles admin stuff here
    end

    render 'admin/home.erb'
  end
end

class MyApp < Happy::Controller
  def route
    on 'admin' do
      run MyAdminController
    end

    render 'home.erb'
  end
end

Yup, this also means that you can build reusable controllers that you can distribute within gems for other Happy developers to use.


Permission Managament

Setting and querying permissions

Happy contains a light-weight run-time permission management system powered by the Allowance gem. Using the can object, you can declare (and later query) permissions at runtime.

Example:

class MyApp < Happy::Controller
  def setup_permissions
    if current_user
      # Logged in users can submit new stuff
      can.submit!

      # But only admin users can edit and delete it.
      if current_user.is_admin?
        can.edit!
        can.delete!
      end
    end
  end

  def route
    setup_permissions    

    on 'submit' do
      if can.submit?
        render 'submit.erb'
      else
        redirect! '/login'
      end
    end

    # Paths are defined dynamically, so the following is possible, too:
    if can.edit?
      on('edit') { render 'edit.erb' }
    end

    if can.delete?
      on('delete') { render 'delete.erb' }
    end

    render 'home.erb'
  end

  def current_user
    # ...a method returning the current user.
  end
end

Permissions for specific objects

TODO

ActiveModel integration

TODO


Controller configuration

TODO


Happy Recipes

Writing & Reading Session Data

TODO

Setting & Reading Cookies

TODO

Using HAML (and other view engines) in Happy

TODO

Serving assets (stylesheets, JavaScript files etc.)

TODO

Caching

TODO

Dealing with users and authentication

TODO

Using ResourceController

TODO

Using ActiveModelResourceController

TODO

Authenticating against Twitter, Facebook etc. using OmniAuth

TODO

Handling image uploads using DragonFly

TODO