Posts Tagged ‘markaby’

Using Markaby w/ Rails 2.0.1

Thursday, December 13th, 2007

Rails 2.0.1 is out, in general, it’s pretty nice. Markaby doesn’t play too nice w/ it. Markaby requires every named route to have a .to_s on the end of it. Yuck.

After migrating my 3rd project to rails 2.0.1, I got sick of adding .to_s to each named routes, and did some digging.

Apparently, somehow a string coming from a named route is not exactly a string. Sure

  apples_path().class == String

but

  String === apples_path()

is NOT true. I couldn’t quite track down why this was but it eventually leads to a

ActionView::TemplateError (undefined method `string_path' for #) on line #10 of messages/index.mab:

actionpack-2.0.1/lib/action_controller/polymorphic_routes.rb:27:in `send!'
actionpack-2.0.1/lib/action_controller/polymorphic_routes.rb:27:in `polymorphic_url'
actionpack-2.0.1/lib/action_controller/polymorphic_routes.rb:31:in `polymorphic_path'
actionpack-2.0.1/lib/action_view/helpers/url_helper.rb:79:in `url_for'
actionpack-2.0.1/lib/action_view/helpers/url_helper.rb:144:in `link_to'
...

After an hour of trying in vain to find the source of the problem, I found a simple hack that fixes this. Should have thought of this before, but add this code to your application_helper.rb

  def string_path(string)
    string
  end

If anyone else has a better solution, please pass it over!

Customizing Markaby – Language Level Refactorings

Wednesday, September 26th, 2007

It’s very easy to call out to methods in markaby, but it’d be nice if you could actually customize the dsl as well.

For example, on many of our pages we have a bottom row that has buttons that look a certain way. So on every page, we have :

  table(:width => "100%") do
    tr do
      td.left do
        previous_button
        first_button
      end
      td.center do
        print
      end
      td.right do
        next_button
        last_button
      end
    end
  end

The code for the actual buttons changes, and after a few tries to extract the whole thing into a single method, we gave up. Our efforts had made it harder to read, not easier. There was always just a little too much variance, and it didn’t feel right.

What we really wanted to write was :

  last_row do
    column do
      previous_button
      first_button
    end
    column do
      print
    end
    column do
      next_button
      last_button
    end
  end

This lets the buttons that change all stay in the view, and gets rid of the skeleton and positional stuff that doesn’t change. Furthermore, it’s DRY and puts all that positional logic in one place instead of scattered across 20 views.

How to do this?

I wrote a test (in RSpec) that looks something like :

describe ApplicationHelper do
  it "should generate a table from a buttons method" do
    last_row(:columns => 2) do
      column do
        "foo"
      end
      column do
        "bar"
      end
    end.should == '<table width="100%"><tr>' +
                    '<td class="left">foo</td>' + 
                    '<td class="right">bar</td>' +
                  '</tr></table>'
  end
end

After a bunch of fiddling and poking around, I finally made the test (and a couple others) pass with this code in my ApplicationHelper :

def last_row(options, &block)
  markaby do
    table(:width => "100%") do
      tr do
        LastRowContext.new(self, options[:columns]).
                                  instance_eval(&block)
      end
    end
  end
end
 
class LastRowContext
  def initialize(markaby, columns)
    @markaby, @column_count, @column_index = 
                            markaby, columns, 0
  end
 
  def column(&block)
    alignment = case @column_index += 1
    when 1 : :left
    when @column_count : :right
    else :center
    end
 
    @markaby.instance_eval do
      td(:class => alignment, &block)
    end
  end
end

I’m sure this could get cleaned up more; this was the work of less than an hour. In particular, if you did this often, you could extract a common MarkabyContext superclass that had some convenience methods. The point is, this is really easy to do, and we shouldn’t be scared to try “Language level refactorings” like this.