What is Hobo?

Posted by Tom on 2007-01-25

There’s a fair few reactions to Hobo out there on the big wide Internets by now, and it’s interesting to see people’s interpretations. I suppose it’s pretty obvious that people are going to jump to conclusions when I’ve only given a brief glimpse of what Hobo is about. I just wanted to take a quick moment to clear a few things up. [Update: this turned from a “quick moment” into a fairly comprehensive overview of what Hobo can do :-)]

The main point here is this - Hobo is nothing but extensions to Rails! There’s nothing that Rails can do that Hobo can’t. I’m not saying that Hobo will be to everyone’s taste - of course you may prefer Rails as it is. I’m just making the point that Hobo only adds to Rails, it doesn’t take anything away. Perhaps it’s the slightly “magic” feel to the POD screencast – I can see that the screencast might leave some people thinking they’d rather build the app themselves and have control over everything. So I guess I’m really just making the point that you do have control over everything, in just as direct a manner as you do in Rails without Hobo.

Hobo does do more than your average plugin though – it’s kind of like several plugins rolled into one. Inevitably people are going to be interested in having those features as separate plugins – a few people have asked for DRYML by itself already. But there are a bunch of dependencies between the different features, and to be honest I won’t know exactly where things can be separated until Hobo matures a bit.

So, for now at least, I’ve taken a different approach - you install all these features at once by installing Hobo, but you use only those features you want to. If you want the whole “web app builder” thing, then you’ll end up using most or all of them, but if you just want DRYML? Not a problem, the rest of Hobo won’t get in your way. If you later see that you could also benefit from, say, the ajax-mechanism, fine – just start using it.

So perhaps a quick tour of what these features are would be in order :-)

DRYML

This is really the heart of Hobo – it all started with DRYML. There’s a few posts on this already of course - have a look in the documentation category. DRYML consists of the templating mechanism itself, as well as a “core” tag library, with the basic things like loops and conditionals.

Ajax Rendering

Building on DRYML, this is the ability to mark a section of your page as a “part”. Having done this, Hobo will track the object that was in context when the part was rendered, and can re-render the part using the new state of that object when requested. This mechanism can be used by itself, but works best in combination with the Hobo Rapid tag library, which lets you write very simple stuff like

<delete_button update="number_of_users"/>

and the number of users will automatically change to reflect the deletion.

Right now using the Ajax mechanism without also buying into Hobo Rapid is kind of a pain. Some extra work to smooth that out is in order. Perhaps some regular Rails helpers.

Hobo Rapid Tag Library

This is where things get groovy. A tag library is basically just like a load of Rails helpers, but as DRYML tags instead of methods. Hobo Rapid contains the Hobo equivalent of link helpers, form helpers, ajax helpers, and pagination helpers. There’s also some “scaffold-like” tags for default pages, and support for navigation bars.

This library is expected to grow!

ActiveRecord Permission System

This appears prominently in the POD screencast. At it’s heart it’s just a simple convention for declaring who’s allowed to do what to your models (create, update, delete and view). The permissions come into effect in two places: in customising the views, and in validating http requests.

View customisation is provided in the tag library. The output of various tags is dependent on permissions. For example the <edit> tag will automatically become a read-only view if the current user does not have edit permission.

Validation of web requests is done in Hobo’s generic model controller (see below). If the request, be it a view, a create, an update or a delete (http get/post/put/delete) is not permitted for the current user, it will be rejected.

Fairly simple but useful. Models can declare which columns are searchable (there are sensible defaults of course!) and Hobo provides both server and client-side support for a nice ajaxified search that finds many types of model from a single search page.

Switchable Themes

A Hobo theme consists of public assets such as a stylesheet and images, and some tag definitions. The tags define the basic HTML structure of the various theme elements - the overall page, header and footer, navigation bar, sidebars, panels (boxed sections within the page) etc. Themes are switchable by changing a configuration variable. All the variable does is determine the paths used to find the tag definitions (app/views/hobolib/themes/theme-name) and the public assets (public/hobothemes/theme-name).

User Management (Acts As Authenticated)

In order for the permission system to work, a user model must be present. For that reason we decided to go ahead and include AAA in Hobo. It’s been tweaked a bit – the controller has been merged with Hobo’s “front” controller (so called because it gives you your front page and related pages like search, login and signup). The user model methods that are not commonly changed have been moved out into a module to keep your user model clean.

Hobo also adds the concept of a guest user, this is a pseudo model that is not stored in the database. It’s just a place to define the permissions for someone who has not logged in. One implication of this change this is that instead of testing if current_user is nil, you test for current_user.guest?.

ActiveRecord Composable Query Mechanism

It’s common practice with ActiveRecord to write query methods to capture standard queries your application needs beyond the magic find_by_thingumybob methods. A problem I ran into was that I wanted to be able to compose queries: find everything that matches this query or that one; find the first record that matches some query and some other one.

I found I needed such a feature in Hobo so I implemented the following:

Post.find(:all) { (title_contains("Hobo") | content_contains("Hobo")) &
                   is_in(news_category.posts) }
User.find(:first) { name_is("Tom") | email_is("foo@bar.com") }

Note that in the first example, the collection my_category.posts doesn’t need to be loaded - the query generates SQL and runs entirely on the database.

The query operations in those examples – *_contains, *_is and in_collection – are all built in. If you write class methods on your model that return SQL expressions you can use those in the query too.

Generic Model Controller (CRUD Support)

Hmmm - this does a fair few things. Maybe I need another cup of tea… Slurp, ahhh. Sorry where was I? Oh yeah - the model controller. To get this functionality, add this to your controller

hobo_model_controller

It’s just a short-hand for including Hobo::ModelController. You can also generate a controller which already contains this declaration (hey - every little helps!) with:

$ ./script/generate hobo_model_controller <model-name>

CRUD support

Hobo derives the model class from the controller class name, so you should follow the Rails convention of PostsController for model Post (the generator will do this for you of course). You then get the basic CRUD methods familiar from scaffolding: index, show, new, create, edit and update.

The controller also has support for your has_many and belongs_to associations. Firstly, your has_many associations get published automatically, so for example the URL

/posts/12/comments

Would map to the action show_comments, which is implemented for you. Also

/posts/12/comments/new

Will invoke the new_comment action - again it’s implemented for you. Note that the form on that page doesn’t post to /posts/12/comments but to /comments as usual. I decided it was best to have a single URL for creating a given model.

There’s also support for your associations when creating / updating models. Without going into too much detail in this overview, when you post (create) or put (update), you can set both has_many associations and belongs_to associations. You can either pass the id of an existing record, or pass entirely new records in nested parameter hashes. There’s support for all this in DRYML and Hobo Rapid, so for example if a person has an address as a separate, associated model, you can easily build a single form with fields for both the person and the associated address.

Data filters

The composable query mechanism for ActiveRecord has been described above. Data filters allow you to expose these queries to the web. This is easiest to explain with an example. Say we have people that are members of groups, and we want a page with all the people not in a particular group. Inside the people controller we define a data filter:

def_data_filter :not_in do |collection|
  not_in(collection)
end

A page with all the people not in the group with id 7 would then be available at

/people?where_not_in=group_17

Enhanced autocomplete

Rails already has support for working with the Scriptaculous auto-completer through the helper auto_complete_for (although that’s on its way out in 2.0). Hobo’s version is enhanced to work with the data filter mechanism. Say for example you wanted an auto-completer to allow you to add people to a list - you don’t want the people already in the list to be included in the completions. You would add the following to your controller:

def_data_filter :not_in do |collection|
  not_in(collection)
end

autocomplete_for :name

You would then point your auto-completer at (e.g.) /people/completions?for=name&where_not_in=list_12

Hobo Rapid has an auto-completer tag so the client side is nice and easy too.

Remote procedure calls

When DHH introduced the Simply Restful stuff, he described CRUD as an aspiration rather than a hard rule (IIRC). In other words, you shouldn’t go crazy trying to model absolutely everything as resources – there will still be “remote procedure calls” (RPC) that we post to. I hit this recently with a need for a “reset password” service on an app I’m building. (BTW - this is basically how Hobo moves forward - if I hit a need for a feature that I feel will crop up again and again, I build it into Hobo, not the app. In this case I didn’t want to build password reset into Hobo, but I did build the RPC mechanism).

The basic procedure is: write an action in your controller as normal. Add web_method <method-name>. Hobo will add a route for your method, and apply a before filter so the object in question is already available in @this. Hobo’s ajax mechanism supports updating the page with results from your method, and there’s integration into the permission system too.

Let’s see an example - the reset password method. In the controller we add:

web_method :reset_password
def reset_password
  new_password = @this.reset_password
  hobo_ajax_response(@this, :password => new_password)
end

On the model we can add a method can_call_reset_password?(user). In my case I only wanted the super-user to have access to this method so I didn’t need can_call_reset_password?.

The method will be available to HTTP posts at, e.g.

/users/21/reset_password

For the client side there’s a <remote_method_button> tag (note to self: should be called <web_method_button>?)

Alternative show methods

Simply Restful introduced the semicolon in the URL to access alternate views of the same object. So

/posts/12

Would be a ‘normal’ view of the post, while

/post/12;edit

Would give you a form to edit the post – still a view of the post, just a different one. You’ll probably find the need for additional “alternative views”, e.g. you might have a main view of a user, plus a separate “My Profile” page where users can edit details that don’t change so often, like their email address. Hobo allows you to simply say

show_method :profile

In your controller. You then create app/views/users/profile.dryml. You also get a named route person_profile so you can call person_profile_url(fred) and get the URL back.

Migration Enhancements

This is a little one, but very useful. In create_table calls, you can add columns like this

t.string :name, :subject, :limit => 50

instead of

t.column :name, :string, :limit => 50
t.column :subject, :string, :limit => 50

There’s also t.auto_dates that gives you updated_at and created_at, and

t.fkey :zip, :zap

That will give you integer columns zip_id and zap_id.

ID Names

In ActiveRecord everything has a numeric ID. It’s common however, that models also have an identifying name (e.g. a name column where every row is unique). If a model has an ID name like this, it’s nice to be able to sometimes use it instead of the numeric ID, for example in readable URLs. If your model declares hobo_model, you can also declare

id_name <column-name>

If you don’t specify a column it defaults to name. This gives you a read/write attribute id_name which is just an alias for the column in question. You also get find_by_id_name on the class.

If you are using Hobo’s model_controller you can then use names instead of IDs in your URLs, e.g. (using ID names in combination with the associations support):

/categories/News/posts

The helper object_url will generate this kind of URL too - if the object you pass supports ID names.

That’s All Folks!

That’s pretty much everything, although of course lots of detail is lacking. Oh and there’s a ton of great new stuff just around the corner of course!



(edit)