hoodwink.d enhanced
RSS
2.0
XHTML
1.0

RedHanded

Markaby for Rails #

by why in inspect

You know that WebPage code I’ve been playing with lately? Tim Fletcher e-mailed me a revamped version, a plugin for Rails. We’ve gone back and forth on this and found a very satisfying resting point, which I’m calling Markaby. Markup as Ruby.

To install in your Rails app:

 script/plugin install http://code.whytheluckystiff.net/svn/markaby/trunk

To use it, add templates with a .mab extension.

Examples

For example, an app/views/layout/application.mab could look like:

 html do
   head do
     title action_name
     stylesheet_link_tag 'scaffold'
   end

   body do
     p flash[:notice], :style => "color: green" 
     self << @content_for_layout
   end
 end

As you can see all the normal helpers and variables are present. There is one caveat: in default Markaby, helper methods are automatically output when called. So, in the above, the stylesheet_link_tag gets output. (To turn that off, use @output_helpers = false.)

A scaffolding page could look like this (app/views/products/list.mab):

 h1 'Listing products'

 table.editor.classic do 
   tr do
     for column in Product.content_columns
       th column.human_name
     end
   end 

   for product in @products
     tr do
       for column in Product.content_columns
         td product.send(column.name)
       end
       td { link_to 'Show', :action => 'show', :id => product }
       td { link_to 'Edit', :action => 'edit', :id => product }
       td { link_to 'Destroy', { :action => 'destroy', :id => product }, :confirm => 'Are you sure?' }
     end
   end
 end

 link_to 'Previous page', { :page => @product_pages.current.previous } if @product_pages.current.previous
 link_to 'Next page', { :page => @product_pages.current.next } if @product_pages.current.next

 br

 link_to 'New product', :action => 'new'

As you can see classes can be assigned just like in previous experiments. The table.editor.classic gets output as <table class="editor classic">.

One really wonderfully trippy thing about Markaby is that you can use capture to treat elements as strings, if you like. This satisfies one of Dgtized’s feature requests from a few days ago.

 div.menu do
   %w[5.gets bits inspect cult -h].map do |m|
     capture { link_to m, "/#{m}" }
   end.join(" | ")
 end

So, in the above example, the output of link_to is captured for each element of the array and then patched together with pipes between. But how does it get output to div.menu?

The rule is: each element with a block opens a new buffer. You can fill up that buffer by printing elements to it. Or by returning a string from the block.

Markup Shortcuts

Really, there’s not much more to this at present. For example, RedCloth support isn’t built in, nor is the auto-header and auto-body stuff from WebPage.

Just a few shortcuts:

  1. img tags will default to :alt => '', :border => 0.
  2. head automatically includes tag!(:meta, 'http-equiv' => 'Content-Type', 'content' => 'text/html; charset=utf-8').
  3. html defaults to an xml instruction, the XHTML 1.0 Transitional doctype and :xmlns => "http://www.w3.org/1999/xhtml", "xml:lang" => "en", :lang => "en" on the html element.
  4. xhtml_transitional is an alias for html.
  5. xhtml_strict does the same, but with the proper XHTML 1.0 Strict doctype and so on.

If you don’t like any of this, override or undef the methods. So far so good?

Lastly, importantly: Rails 1.0 doesn’t support using Markaby for layouts unless you explicitly state the name of your layout in the controller. But I think Marcel has fixed this in Edge Rails.

said on 12 Jan 2006 at 01:17

It’s nothing a little getting of my ass and testing wouldn’t tell me, but does this work for partials called form inside normal rhtml templates?

said on 12 Jan 2006 at 01:30

Yes, this works for partials.

said on 12 Jan 2006 at 02:52

Mmm I like the capture result.

self << @content_for_layout is definitely cute.

Not certain about table.editor.classic resulting in “editor classic”, something about that smells wrong, but I can’t think of an alternate method of doing this either.

What happens if you def a function anywhere in there? They just get added to that particular instance of a tr or whatever correct?

Which means most helpers should be defined elsewhere as helpers. I assume that:
def helper content
  MarkupAsRuby.new do
    tr.name content
  end
end 

is necessary?

I still think auto-css integration would make this the most awesome of awesome though.

said on 12 Jan 2006 at 09:31

Brilliant!

said on 12 Jan 2006 at 09:55

I agree on the auto-css, Dgtized. I’m just trying to figure out if we have hook into the rcss templates or if we’ve got to do our own thing.

said on 12 Jan 2006 at 10:19

wow, this is soo similar to the html extension in cgi.rb, except this is side effectish.. :)

said on 12 Jan 2006 at 11:09

Right, the main differences between cgi.rb and Markaby are:

  • Markaby uses instance_eval
    ul { li "item" }
    rather than
    cgi.ul { cgi.li "item" }
  • Markaby uses Builder-style IO rather than string concatenation.
    ol { li "one"; li "two" }
    rather than
    cgi.ol { cgi.li("one") + cgi.li("two") }
said on 12 Jan 2006 at 12:19

Very cool stuff there _why & Tim. I just saw the .mab extensions added to edge rails in the svn commit mail list. And can confirm that it does work with edge rails.

I’m a little unclear on what you mean about the stylesheet_link_tag and “in default Markaby, helper methods are automatically output when called” Isn’t that what you want? i mean if you put the stylesheet_link_tag there in the head don’t you want it to output there? I guess i am missing something there.

But this looks very cool. Soon we won’t have to write any html or any sql. Using this and my where plugin it all comes together in pure ruby. Take a peek at this syntax extension I am working on for AR quries:


Model.find_with_conditons( :all, :limit => ..., :order => ... ) do 
  foo == 'bar'       # ["foo = ?", 'bar]
  baz <=> (1..100)   # ["baz BETWEEN ?AND ?", 1,100]
  woo =~ 'substri%'  # ["woo LIKE ?", 'substri%']
  id === (1..4)      # ["id IN (?)", [1,2,3,4]]
  fiz < 10           # lt, gt, leq, geq, etc., should all "just work" 
end
said on 12 Jan 2006 at 12:29

Ezra: Yes, most helper methods are designed for output, so it’s the default.

However, there are still a few problems. Like this:

 div do
   p(human_size(1234567))
 end

...doesn’t do what you’d expect. I don’t think most people want human_size or number_to_percentage to output directly.

said on 12 Jan 2006 at 12:31

One step closer to the unified Rubiverse where everyone speaks Ruby and Unicode is no longer needed. Yay!

said on 14 Jan 2006 at 21:45

Is this completely handled by RAILS ?

Meaning, do I need to define media types for .mab in Apache and Lighttpd?

said on 14 Jan 2006 at 21:52

No, Rails does it. You just run the script/plugin line above. You don’t actually see the .mab extension in the URLs. It’s just the extension used on files in app/views/ directory.

said on 15 Jan 2006 at 06:00

Is there anything special you have to do in order to make it work on Edge? The .mab files aren’t recognized here… (yes, I did svn up)

said on 15 Jan 2006 at 06:48
@Dgtized – I’m still learning Ruby, so it might not work at all, but… something like this maybe?:

html do |:id => 'blah', :class => 'foo bar'|
  ...
end
said on 15 Jan 2006 at 09:58

Rapha: Make sure you don’t have similarly named .rhtml templates. An edit.rhtml will take precedence over edit.mab. I also don’t know if you can mix template types (an rhtml layout with a mab template.)

You can assign IDs and classes with the hash syntax:

 div :id => 'blah', :class => 'foo bar' do
   ...
 end

Identical to:

 div.blah!.foo.bar do
  ...
 end
said on 15 Jan 2006 at 10:35

kxzlfksaldk

said on 15 Jan 2006 at 11:19

Why: Thanks for explaining the syntax for id’s! I tried making a new `Test’ application and removed all RHTML files. Doesn’t work. When the plugin is installed, it works.

Btw., the above command (script/plugin install http://code.whytheluckystiff.net/svn/markaby/trunk) doesn’t appear to work. As a matter of fact, checking out markaby/trunk doesn’t work (please do tell if it’s my fault!). For the time being, I had to


cd vendor/plugins
svn co http://code.whytheluckystiff.net/svn/markaby
mv markaby/trunk/* markaby
rm -rf markaby/{branches,tags}

said on 15 Jan 2006 at 11:24

Trying to use Markaby here in my main application here now and have to say it rocks! The only thing that I see could use improvement is the form tag. Would it be possible to give it the same defaults as Rails’ form_tag(), i.e. action=”/controller/action” and method=”post”? Apart from that, it really does make your templates look much nicer than before. Thanks a lot for it!

said on 15 Jan 2006 at 11:56

_why: This is great! I’ve been looking for a slightly more programmatic template language for a while. I understand that it cuts out some of your average designers, but for me on my own it’s ok for now!

The only issue I’ve found is that the opening ‘head’ tag is missing in output. I have a head block, and the closing head tag is present at the right spot, but the opening tag is missing. I’ll see if I can find the problem here…

Also note that combining template types seems to work, at least for the mab as layout, rhtml as template scenario. The only thing you can’t do is pass the @content_for_layout variable as the parameter for a tag, since that’ll convert all the lt/gt signs in your html to their respective entities (among other things).

said on 15 Jan 2006 at 12:11

Well, the opening head tag is actually there, but hidden after my stylesheet/javascript tags. Since the helpers are outputting right away, they seem to get printed before the opening tag of the enclosing block. Are you able to duplicate this on your side, _why?

said on 15 Jan 2006 at 12:52

Markaby is great, thank you Tim and why!

I would really love to use it everywhere in my views/, but this is difficult. The problem with nested helper methods outputting everything twice is really annoying…especially frequently used idoms like <%= h pluralize( users.size, 'user' ) %> online are hard to translate correctly into Markaby. I wrote an article with a possible solution to the capture problem (excuse the poor layout, this is under construction.)

Basically, I think Markaby needs to be less surprising; I still haven’t figured out why some strange errors occur, so I suggest to minimize the magic behind it. For example, it seems to html_escape arguments when tag methods are called without block…useful, but not always wanted.

Is it possible to report better error messages? Markaby templates with errors are hard still to debug.

said on 15 Jan 2006 at 12:55

About combining Markaby an RHTML : I use a Markaby view with an RHTML partial, and it works perfectly.

said on 16 Jan 2006 at 01:28

how do you output helpers if you turn them off?

said on 16 Jan 2006 at 03:51

How can I install Markaby on my own stand-alone machine without Internet. Thank you very much for your help. ncv

said on 16 Jan 2006 at 05:09

cyx: either return them (a string) from a block like so:

div do
   link_to 'list', :action => :list
 end

or pass them as the first argument to a tag i.e.

p pluralize(things.size, 'thing')
said on 16 Jan 2006 at 05:55

Has anybody experience with Markaby performance compared to the Erb?

said on 16 Jan 2006 at 06:07

This could be a boost to having less html to hand code and more cool ruby code

said on 16 Jan 2006 at 06:32

murphy – just read ure doc and it’s got my vote, – it makes sense.

i find the the capture method is a little unintuitive.

said on 16 Jan 2006 at 11:35

Possibly the first Markaby-powered site in the wild…Ruby on Rails Workshops.

Uses Markaby mostly, but has a few rhtml partials.

I had success with using ’@output_helpers = false’ on the last partial in the tree, and using capture {} otherwise.

said on 16 Jan 2006 at 12:01

Very sweet! I really like this programmatic style of generating HTML .

One suggestion: I find the code to be more readable with {} instead of do/end. The do/end blends in with the HTML tag names, while {} helps the tags stand out more.

For example:

html do
  head do
    title action_name
    stylesheet_link_tag 'scaffold'
  end

  body do
    p flash[:notice], :style => "color: green" 
    self << @content_for_layout
  end
end

vs.

html {
  head {
    title action_name
    stylesheet_link_tag 'scaffold'
  }

  body {
    p flash[:notice], :style => "color: green" 
    self << @content_for_layout
  }
}
said on 16 Jan 2006 at 12:03

Okay, so let’s get this resolved. You guys are using @output_helpers = false only because of stuff that you’re sending to h, right?

murphy, doesn’t the following work?

 h1 'Runes in DB'

 for rune in @runes
   div.rune do
     render :partial => 'rune', :object => rune
     pluralize(rune.elements.size, 'rune')
   end
 end

 p do
   pluralize(@runes.size, 'rune') + ' found.'
 end

The capture method should only be used if you want to do a tricky loop, like the menu generation snippet in the post way up tippy top. It should be rare or never.

said on 16 Jan 2006 at 12:49

The bug with head is fixed.

I’m also trying out scanning for helpers which are used as arguments.

So this works now:

 div.rune do
   render :partial => 'rune', :object => rune
   h pluralize(rune.elements.size, 'rune')
 end

However, this doesn’t:

 h1 'Listing ' + pluralize(rune.elements.size, 'rune')

So I think argument matching will only complicate things.

I wish there were a way to tell between helpers which output strings containing formatted HTML and helpers which output plain strings. The format should be output (link_to, start_form_tag) whereas the latter should be held for questioning.

said on 16 Jan 2006 at 14:52

Okay, so let’s get this resolved.

I hope you don’t see my article as rant or something ;) but Markaby is so cool that it needs critic. I want it to be a success.

murphy, doesn’t the following work? ...

Not perfect. It doesn’t html_escape (not a real problem) but it also swallows the 'found'. It feels a bit like narrowing my possibilities – it’s complicated to use h here, so I omit it and hope XSS doesn’t bite me. ;) We all are lazy, ain’t we?

You guys are using @output_helpers = false only because of stuff that you’re sending to h, right?

It’s not that there are no other solutions, without switching @output_helpers off. But my intent is to make Markaby more simple, and less surprising. It does a lot behind the scenes to be shorter, and I see that my Markaby style can lead to longer templates. But I think it is easier to understand and handle for Nookabys.

I’m always struggling with languages that tend to be too short – YAML , Textile and Markaby all have this fresh new beautiful look of shortness – but then, you find new exceptions on the other side. It seems to be a consequence: Whenever you try to make something shorter, you have exceptions which make it complex. Try Perl. If you are an experienced user, you are productive with it. If you are new, you’re lost.

Ruby is short, but tries hard to avoid these problems. Rails also does, in my opinion. And if we want to target on those users, we should make Markaby extremely simple to learn an maintain, even if we give up some neat shortcuts. Not everything. I love div.class do.

The capture method should only be used if you want to do a tricky loop, like the menu generation snippet in the post way up tippy top. It should be rare or never.

Oh, I got that wrong. Then we all agree it should not be frequently used :)

What about adding out as an alias for self << ?

said on 16 Jan 2006 at 15:17

If it helps… I’m a noob and like “self << ” better then “out”. Actually, it’s quite neat. But then, I’m a noob :-)

said on 16 Jan 2006 at 15:21

Right now text is an alias for <<, but I think out is preferrable. I will change it!

I agree that @output_helpers is easier to understand, but having to do:

 ul do
   li { out(link_to 'RH', 'http://redhanded.hobix.com') }
 end

Seems too verbose.

Another maybe-too-tricky thing… What if we have a version of h which turns off @output_helpers and escapes output?

 div.rune do
   render :partial => 'rune', :object => rune
   h { pluralize(rune.elements.size, 'rune') }
 end
said on 16 Jan 2006 at 16:54

@Rapha: thanks, I shouldn’t think I knew what noobs like. Original noob comments are certanly more helpful than me trying to guess what noobs think or like – in fact, it’s me who likes it better. We should make a noob election for every new feature for more democracy. Sorry.

Right now text is an alias for <<

Oh, I didn’t know text. This is also a good name. Is there a Markaby docu yet? Or some guide with cartoon foxes?

Mmh…what about

ul do
  li(link_to 'RH', 'http://redhanded.hobix.com')
 end

This could be used if you turn automatic html_escaping off. Stack this:

ul li link_to 'RH', 'http://redhanded.hobix.com'
said on 16 Jan 2006 at 17:06

Now we have both … out and self << ... :-) Isn’t that worth a celebration? Let the Champaign flow!

Why, what about my earlier point of having the same defaults as the “old” Rails methods do? Like, form defaulting to &lt;form action="post"&gt; and so on?

Btw, am I the only one who sees the comic foxes analogically to Monthy Python movies? (Read: incomprehensible to a certain breed of people—nothing against Why ;-) )

said on 16 Jan 2006 at 17:08

The preview button shows &lt; and &gt; as < and > when they’re enclosed by code tags, whereas the submitted comments do not. Just something I noticed…

said on 16 Jan 2006 at 17:57

Not sure, have I found a bug here?

list.mab:

odd_or_even = 0
for thing in @things
  odd_or_even = 1 - odd_or_even
  self << thing.inspect
  render :partial => 'item', 
         :object => thing, 
         :locals => { 
           :odd_or_even => odd_or_even 
        }
end

item.mab:

  self << thing.inspect

output of first thing.inspect:

#nil, 
"created_on"=>"2006-01-13 01:34:08", 
"name"=>"Test",
"id"=>"2",}>

output of second thing.inspect:

["", "\n"]

This is just code that I translated from RHTML . In RTHML it works.

said on 16 Jan 2006 at 18:49

Rapha: You were right no local assignments were being merged. Please update your plugins. Thankyou!

said on 17 Jan 2006 at 03:15

”.. there’s not much more to this at present. For example, RedCloth support isn’t built in”

What are your thoughts on putting Redcloth in ? Or would it ruin the rubiness of all ?

said on 17 Jan 2006 at 03:21

Thanks Why! Just tried it out and it’s working perfectly now.

Still no comment on my sensible defaults for attributes-request though? :)

said on 17 Jan 2006 at 12:26

Very nice plugin! It’s possible to include attributes from a helper function?


<td <%= some_function() -%> > 

said on 17 Jan 2006 at 20:45

Just discovered that you don’t have to delete the .rhtml files to get the .mabs running. Rails prefers additional template handlers over the standard ones, see ActionView::Base#pick_template_extension.

said on 18 Jan 2006 at 00:48

Markaby is in Gems for general use now:

 gem install markaby
said on 18 Jan 2006 at 08:58

Hrm, well, for now:

 gem install markaby --source http://code.whytheluckystiff.net/
said on 20 Jan 2006 at 04:19

Alciato: yes you can, e.g.

helper:

def stylesheet_link_attrs
   { :rel => "stylesheet", :type => "text/css" }
 end

markaby:

link stylesheet_link_attrs

result:

&lt;link rel="stylesheet" type="text/css"/&gt;

said on 20 Jan 2006 at 09:57

Thank you, tim. I notice also that the select function conflicts with the Markaby select tag. When i use the select method as usual markaby tries to render as a tag and throws the following error `select’: can’t convert Hash into time interval

This works


module ActionView::Helpers::FormOptionsHelper
alias  select_mab select
end

and use select_mab in the views

...any alternative?

said on 20 Jan 2006 at 14:14

Alternative would be to remove :select from lib/markaby/tags.rb – depends how often you use the two.

said on 22 Jan 2006 at 07:27

The gem works now – thank you.

said on 23 Jan 2006 at 14:30

OK, Wheres the nooby users manual?

This is what Builder for HTML should be!

said on 04 Feb 2006 at 19:49

I’ve finally realized why I find this plugin so downright luscious. It decimates the shift-count of writing markup!

Looking at your first example (and substituting ’ for “) I find 9 shifts necessary, all but 1 of which involve inescapable ’_’ and ’:’ characters. I count 28 in the equivalent ERB markup (this all assumes more efficient use of the shift key than I normally make in my typing).

(The royal) we the anti-shift-while-typing-code community salute you! Were it not for my previous sentence, I would seriously be considering swapping – and _ on my keyboard.

said on 04 Feb 2006 at 22:23

I’m very new to ruby. I like Markaby.

I’m trying to get the select tag to work in a form. Apparently I haven’t a clue (based on all the errors I get back from Markaby). Could someone please give an example of how to use the select tag and options? I can do this in RoR with actionview but I like Markaby better

Markby = less LISP (lots of Insidious Silly Parenthesis (or Braces, anglebrackets) for me to track.

thanks

said on 06 Feb 2006 at 19:40

thabks

said on 08 Feb 2006 at 14:21

The select element gets in a fight with a select function somewhere. Just use tag! ‘select’ instead for now…

said on 22 Mar 2006 at 07:38

Broken for Rails 1.1 RC1 ?

said on 22 Mar 2006 at 13:30

Should work now (revision 33)

Comments are closed for this entry.