Contents

4. Hobo Model Controller

To enable

class MyController < ApplicationController
  hobo_model_controller
end

(or use the hobo_model_controller generator)

CRUD Actions

The controller automatically provides:

* index * show * new * create * edit * update * destroy

index will automatically paginate and orders the records according to their default_order (see Model Extensions).

Default page tags

The viewable actions - index, show, new, and edit are rendered using corresponding view templates as with regular Rails. If the template is missing, Hobo will render a tag in order to create a default page.

e.g. if app/views/blog_posts/show.dryml is missing, Hobo will render <show_page/> instead.

The full set of default page tags are:

If the parameters to create or update contains values for belongs_to or has_many associations, the controller will create / update the related objects, within some limits.

(To be continued)

POSTING and PUTing dates

Dates can be set either with year, month, day (etc.) fields or with strings that can be parsed as dates.

HTTP Parameters

event[date][year]=2007&event[date][month]=March&event[date][day]=14

Or as a string

HTTP Parameters

event[date]=March+14th+2007

If Chronic is installed it will be used

HTTP Parameters

event[date]=next+wednesday

Permission System

Before creating, updating or destroying any record, the permissions are checked for the current user. Note there is always a current user in a Hobo app. If no one is logged in, the current user is an instance of the Guest class which typically defines nothing but the permission methods.

The controller will also refuse to render show or edit for a record the user is not permitted to view, and will refuse to render new for a record the user is not allowed to create. Note the creator_attr (see Model Extensions) will be set to the current user before this check is performed.

permission_denied method

If any permission check fails, the permission_denied method is called, and the request goes no further. This method is intended to be overridden. The default renders a curt message and sets a 403 header.

Collection Actions

For every has_many collection in your model, the controller will add two actions. For example, a BlogPost which has_many comments. The actions created will be

* show_comments * new_in_comments

For a BlogPost The routes look like, respectively,

* /blog_posts/12/comments * /blog_posts/12/comments/new

Which are available as the named routes:

* blog_post_comments_url(id) * new_blog_post_comment_url(id)

Both actions will result in a permission_denied call if the current user does not have view permission for the collection (can_view?(post, :comments)).

show_comments

The template for this action is either views/blog_posts/show_comments.dryml or, if that is not found, the more general views/comments/show_in_collection.dryml. (In fact they don’t have to be DRYML templates. A .rhtml suffix would also work).

If neither template is available, Hobo will render <show_collection_page/>

In the view, the owner of the collection is available as both @owner and @blog_post (for this example), and the collection is @this (i.e. the DRYML context).

new_in_comments

This is intended for a form to create a new comment for that particular post.

The template is either views/blog_posts/new_comment.dryml or failing that views/comments/new_in_collection.

If neither template is available, Hobo will render <new_in_collection_page/>

In the view, the owner of the collection is available as both @owner and @blog_post (for this example), and the new object (e.g. the Comment) is @this (i.e. the DRYML context).

If you use Hobo Rapid to render the form, a hidden field will be included to make sure the created object (Comment) belongs to the correct owner (the BlogPost).

Customising the controller

Overview

If you need custom behaviour, the automatic actions provided by Hobo can simply be overridden

Controller class

def show
  ... custom show action ...
end

Often there is a need to partially customise the action, but keep the rest of Hobo’s behaviour. For every action, there is a corresponding Hobo action that provides the default behaviour.

By just calling hobo_show, this overridden show behaves the same as Hobo’s provided show action.

Controller class

def show
  hobo_show
end

To customise the behaviour, hobo_show can be passed various parameters, e.g. to optimise database performance with eager loading:

Controller class

def show
  hobo_show :this => Post.find(params[:id], :include => [ :comments ])
end

You can also override the response by providing a block

def show
  hobo_show do
	render :text => "show what?"
  end
end

You can also customise the response to “object not found”, and “permission denied”:

def show
  hobo_show :permission_denied_response => proc { 
	render :text => "On yer bike sunshine!"
  }, :not_found_response => proc {
	redirect_to homepage_url
  } 
end

General customisation parameters

ParameterApplies toUsage
:thisNon-collection actionsSets the object to show/create/edit/destroy
Provide a blockAll actionsImplement the render or redirect response
:html_responseCRUD actionsA proc that implements the response if the client wants HTML
:js_responseCRUD actionsA proc that implements the response if the client wants JavaScript (i.e. an Ajax call)
:invalid_html_responsecreate and updateA proc that implements the HTML response if the create/update had validation errors
:invalid_js_responsecreate and updateA proc that implements the JavaScript (ajax) response if the create/update had validation errors
:permission_denied_responseAll actionsA proc that implements the response
:not_found_responseAll except new and new_in_collectionA proc that implements the response
:set_creatornew and new_in_collectionPass false to prevent Hobo from calling the set_creator method with the current user.

Customising collection actions

For every collection that is published, Hobo provides, e.g. for a comments collection, show_comments and new_comment actions. In order to customise these, override the default actions like this:

Controller class

def show_comments
  hobo_show_collection :comments
end

def new_comment
  hobo_new_in_collection :comments
end

Parameters can be passed to those methods in order to customise the behaviour (see next section).

Customisation parameters for collection actions

ParameterApplies toUsage
:total_numberindex, show_*Set’s the total number of records for pagination
:page_sizeindex, show_*Number of records per page
:pageindex, show_*Current page to show
:ownershow_*, new_*Set the owner of the collection
:collectionshow_*, new_*Set the collection

Extra Show Actions

Sometimes one show method is not enough. E.g. A User might have a main page plus an “options” page, where that user’s settings are edited.

class UsersController < ApplicationController
  show_method :options
end

This adds an alias of show, with the route

/users/3;options

Available as a named route:

user_options_url(id)

Extra Collections

Models often define methods that return arrays of other models. That is, custom versions of the associations created by has_many. To publish these the same way Hobo publishes has_many collections:

class UsersController < ApplicationController
  publish_collection :comments_for_moderation
end

The collection is then available just as your has_many collections are available

Web Methods

Sometimes, a purely RESTful controller is not possible. Sometimes you need a “remote procedure call”, for example, to request a password reset.

class UsersController < ApplicationController
  web_method :password_reset
  def password_reset
    ...
  end
end

The model in question must then define password_reset_callable_by?(caller) to determine if the current user is allowed to call the method. If the method is not defined, only the super-user can call the method.

Data Filters

The records included on the index page can be filtered according to defined queries. These queries can be customised via HTTP parameters.

Controller class

class UsersController < ApplicationController
  def_data_filter :name_starts do |s|
    name_starts s
  end
end

The block is a Hobo block-query which is passed to the controller’s model. Request parameters are passed to the block

URL with query

/users?where_name_starts=a

Another example

Controller class

class UsersController < ApplicationController
  def_data_filter :not_in_group do |group|
    not_in(Group.find(group).members)
  end
end	

URL with query

/users?where_not_in_group=45

Auto-completion

Controller class

class UsersController < ApplicationController
  autocomplete_for :name
end	

This creates an action completions that expects a for parameter

Auto-complete URL

/users/completions?for=name

The result is a Scriptaculous friendly <ul>.

Completions and data-filters can be combined. A good example is a using auto-complete when adding users to a group. People already in the group should not be included in the completions.

Add to group example – Controller code

class UsersController < ApplicationController
  def_data_filter :not_in_group do |group|
    not_in(Group.find(group).members)
  end
  autocomplete_for :name
end	

Add to group example – URL

/users/completions?for=name&not_in_group=45

Back-button caching

It is very common for dynamic Rails sites to suffer from problems with the back-button. The browser displays the previous page from its cache, and so the user sees stale information. By default Hobo sets headers to tell the browser not to cache these pages, so the back button results in a fresh GET request.

No-Cache Headers

headers["Pragma"] = "no-cache"
headers["Cache-Control"] = "no-store"
headers["Expires"] ='0'

Scriptaculous InPlaceEditor support

If a hobo_model_controller gets an Ajax call to update and only a single attribute is updated, the new value of that attribute will be rendered using DRYML’s core <show> tag, and the result set as a result update (see Hobo Ajax) with the result name new_field_value. Hobo rapid uses this feature to support in-place-editing functionality.

Debug support

(The following are also available as view helpers)

In your actions:

debug a, b

Will pass a pretty-print of a and b to logger.debug. debug also returns its first argument so you can do things like changing

x = some_nasty_expression

To

x = debug some_nasty_expression

You get the debug info, and x is still assigned

There’s also a shorthand for the common raise x.inspect idiom:

abort_with a, b

This raises a pretty-printed string of both a and b