Hobo is an addition to Ruby on Rails that removes some of the manual labor.

WARNING: Hobo is very unstable at the moment! Only use it if you're ready to roll up your sleeves and dig deep into the code.

Introduction

Quick tutorial to get up and running: HoboIntro

Models

Rich Types

You can define your own types:

   class Isbn < String; COLUMN_TYPE = :string; end
   Hobo.field_types[:isbn] = Isbn

You can then use :isbn as a field type in 'fields do' and register a view with <def tag="view" for="isbn">

Look at hobo/email_address.rb as a template (it doesn't do anything now but hopefully it will in the future).

You can declare what rich type any function returns using the return_type function. This allows DRYML to render the correct widget even when the method is returns nil

   return_type :html
   def my_method
     ...
   end

Included Rich Types

  • :email_address
  • :html
  • :markdown_string
  • :password_string
  • :textile_string
  • :text (just a string, but indicates a multi-line text-area is preferred for editing)

Dryml

Instead of embedding Ruby code into your views like ERb, you just declare dryml tags. It's very flexible. Somehow, it feels a lot like HTML::Mason.

Tag Concepts

Polymorphic Tags

A tag can have many definitions for different types of object.

   <def tag="card" for="User">...</def>
   <def tag="card" for="BlogPost">...</def>
   <def tag="card"> ... </def>

call with <card for_type/> TODO: what does for_type mean? Isn't polymorphism enabled by default? [Right now polymorphic calls are too slow to be used in every tag call - we hope to address this in the future -Tom]

If a tag can be matched, it will be used. Otherwise, the default tag will be used. (TODO: what happens if no tag matches and I don't have a default? Will the raw HTML be sent to the user's browser? [No - that's an error -Tom]) If multiple tags match, it's undefined behavior. Make sure that your selections are mutually exclusive. [I think that last part is left over from predicate tags, which we've got rid of. It doesn't really apply to polymorphic tags -Tom]

Default Tagbodies

Tags can supply default content using tagbody:

 <def tag="foo"> ... <tagbody>this is the default</tagbody> ... </def>
  • Callers can keep the default: <foo/>
  • replace it <foo>my tagbody</foo>
  • or wrap it in new content: <foo>“<default_tagbody/>”</foo>

You can use the default tagbody of an element further up the hierarchy using the 'for' attribute. e.g:

 <foo><baa>“<default_tagbody for="foo"/>”</baa></foo>

Restoring Template Parameters

TODO: huh?

restoring replaced template parameters. For example, wrapping a template param in an extra div:

   <MyTemplate>
<my_param replace>
<my_param restore/>
</my_param>
   </MyTemplate>

Tags

For safety, Hobo will throw an error if it doesn't recognize a tag (a NoMethodError in 0.6). If you really DO want to send a tag to the browser, add it to lib/hobo/static_tags. Actually, better to copy lib/hobo/static_tags to "#{RAILS_ROOT}/config/dryml_static_tags.txt" and then add your tags there. TODO: there should be a way for the user's app to *add* to the static tags? TODO: my experiment to add to the static tags failed. No change!

Attribute Interpolation

You will definitely want to interpolate values into attributes. One way is typical Ruby string interpolation:

   <view with="#{3+4}"/>

This will replace the tag with "7". The problem is, this sort of interpolation can only produce a string. There are many places in Hobo where you will need to pass a full-blown object as an attribute (for instance, passing a Ruby array to the repeat tag). If the tag begins with '&', its value will be interpreted.

   <view with="&3+4"/>   # same result as above
   TODO: add a more meaningful example here.

[Actually it's not the same - the above passes the String "7", while this passes the Fixnum 7 -Tom]

Pre-0.6, # was used instead of & (<view with="#3+4"/>) but it was changed because there were too many ambiguities with Ruby's string interpolation.

(In Tom's documentation, he uses the @ sign to indicate an attribute. @if, @with, etc. This is pretty obscure and overloads an already busy character so you hopefully won't see that usage here. But, if you see it in other documentation, that's why).

Implicit Context

When processing tags, a single object is used as the default. TODO...

Defining Tags

   <def tag="a">
     <def tag="panel">...</def>
     
     ... Any call to panel in here, or in the tagbody of the call to
     <a> will get the local definition. Note that local tags are full
     closures and have access to state from the containing tag...
   </def>

[That's a local-tag definition - we axed those :-) -Tom]

if the <def> tag has an attribute debug_source, the generated ERB source will be output to logger.debug


<def> supports the alias_current_as attribute. For instance, this overrides the current <page> definition and makes the old one available as <theme_page>

   <def tag="page" alias_current_as="theme_page">

Shorthand Syntax

<view:author.name/> is equivalent to <view field="author.name"/>. The colon and everything after is optional in the close tag. <repeat:comments> is just <repeat field="comments">. Hobo just takes the stuff after the colon and inserts it verbatim as a field attribute (right?).

<logo small/> is equivalent to <logo small="&true"/>

Not recommended because your dryml will no longer be valid xml.

Control Attributes

Instead of surrounding tags in control structures, you can just give them an attribute:

  • <p if="..."/> as a shorthand for <if test="..."><p/></if>
  • <p unless="..."/> as a shorthand for <unless test="..."><p/></unless>
  • <p repeat="..."/> as a shorthand for <repeat with="..."><p/></repeat>

Think of these as the dryml equivalent of the suffix 'if' and 'unless' in Ruby.

If you give the attributes a string, it is treated test for a non-blank field, e.g. if="address" is like <if test="&!this.address.blank?">

You can use <else> after tags with these attributes on them.

Core Tags

  • call_tag -- call another tag. <call_tag tag="show" ... > Used to be named dynamic_tag. TODO: what's the difference between this and naming the tag directly? <show ...>
  • CallTemplate -- call a template I suppose. TODO: what does the CamelCase mean? What's a template?
  • def -- create a new tag. See Defining Tags.
  • include -- includes a tag library. src givesd the filename to include. You can specify the namespace with as. <include src="my_taglib" as="foo"/>, then... <foo.my_tag/>.
  • join --
  • partial -- render a Rails partial. <partial as="account">
  • repeat -- You can add an else tag after the repeat that gets executed if the collection was empty.
  • set -- <set a="&1" b="&2"/> is just equivalent to <% a = 1; b = 2 %>

Are these still a part of core: <unless_blank>, <unless_empty>, <if_can_edit>

Rapid Tags

  • editor -- displays an editable version of the object. Degenerates to view if the user doesn't have edit permissions. <editor obj="#@this" attr="name"/> Used to be called both edit and form_field (I think)
  • object_table -- Additional rows can be added by giving a tag-body. skip_fields attribute removes fields from the table.
  • name -- displays the name of an object (just calls obj.to_s to find the name).
  • view -- displays an object. for=object to view. <view for="#Employee.find(:first)">
  • count -- Outputs the number of items in the collection. unless_none=1 means don't output anything if the count is 0. May be followed by <else>.

Language Tricks

Hobo includes some very handy language extensions.

Hash Extensions

Enumerable#build_hash

   (1..3).build_hash {|i| ["#{i} Xs", "X" * i]}
   => {"1 Xs"=>"X", "3 Xs"=>"XXX", "2 Xs"=>"XX"}

Enumerable#map_hash

   (1..5).map_hash { |x| x**2 }
   => {5=>25, 1=>1, 2=>4, 3=>9, 4=>16}

Hash#-

   {:a => 1, :b => 2} - [:a] 
   => {:b = 2}

Hash#&

   {:a => 1, :b => 2} & [:a]
   => {:a = 2}

Hash#map_hash

   {:a => 1, :b => 2}.map_hash {|k, v| v+1}
   => {:a => 2, :b => 3}

Hash#partition_hash -- Returns two hashes. If you pass a block, block is called for each element. Returns [all elements for which the block returned true, all false]. If you pass a hash, returns [all elements whose keys matched a key in the passed hash, all other elements]. TODO: example.

Hobo::LazyHash -- TODO look up and document.
HashWithIndifferentAccess -- also TODO

Other Extensions

Include method #extract_options_from_args! -- um... isn't this deprecated? Should probably eventually remove this from Hobo, no? [Ah! They've changed to Array#extract_options!. Much better - I'll change this in Hobo -Tom]

Object#is_a? extended to support multiple args: if foo.is_a?(Symbol, String)

_why's metaid added to Object.

Enumerable#map_with_index

Rake

Environments.rake defines tasks like dev, prod, testing so you can, i.e. (thanks to err.the_blog IIRC)

   $ rake prod db:migrate

TODO

TODO: nillable_field (shouldn't that be nullable_field?) is mentioned in CHANGES.txt but I can't find it in the source.

TODO: hobo will automatically maintain a login_count on the user model. Wonder where I should document that? And what about also maintaining a last_logged_in field?

Wouldn't Ambition be awesome with Hobo? http://errtheblog.com/post/10722