Thursday, November 16, 2006

button_to and images: a lesson in false laziness

Today I discovered another little rails annoyance. The "button_to" helper does not yet allow you to use an image for the button. Actually, somebody has submitted a patch to rails to implement this functionality, but for whatever reason this patch has not yet been applied (as of the date of this post).

What to do then? Well, you can take the lazy way out, and use form_tag together with an image tag, which I admit is what I first did, or you can realize that this is really false laziness, and do the right thing.

First, here are the goods: download "url_helper.rb" and add "require 'url_helper'" to your "environment.rb". But wait, if you just do that, you've just fallen into the trap of false laziness! Eeek! So, if you're busy right now you better bookmark this and come back later. Otherwise, well, I curse you with an eternal misery of arduous, falsely lazy programming. If you want to do the right thing, if you really believe in the One True Laziness, then meditate a little on the following:
[code@penguin scibuzz]$ cd lib
[code@penguin lib]$ wget http://dev.rubyonrails.org/attachment/ticket/5388/caller_wins_html_options_for_button_to.2.diff?format=txt
--04:52:17-- http://dev.rubyonrails.org/attachment/ticket/5388/caller_wins_html_options_for_button_to.2.diff?format=txt
=> `caller_wins_html_options_for_button_to.2.diff?format=txt'
Resolving dev.rubyonrails.org... 207.7.108.248
Connecting to dev.rubyonrails.org[207.7.108.248]:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2,051 [text/plain]
100%[====================================>] 2,051 --.--K/s

04:52:17 (1.07 MB/s) - `caller_wins_html_options_for_button_to.2.diff?format=txt' saved [2,051/2,051]

[code@penguin lib]$ locate actionpack | head -1
/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.1
[code@penguin lib]$ cp /usr/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_view/helpers/url_helper.rb ./
[code@penguin lib]$ patch url_helper.rb caller_wins_html_options_for_button_to.2.diff\?format\=txt
patching file url_helper.rb
Hunk #1 FAILED at 60.
1 out of 1 hunk FAILED -- saving rejects to file url_helper.rb.rej
patching file url_helper.rb
Hunk #1 succeeded at 108 (offset -3 lines).

[code@penguin lib]$ cat url_helper.rb.rej
***************
*** 60,65 ****
)
end

def test_link_tag_with_straight_url
assert_dom_equal "<a href=\"http://www.example.com\">Hello</a>", link_to("Hello", "http://www.example.com")
end
--- 60,72 ----
)
end

+ def test_button_to_with_image_submit
+ assert_dom_equal(
+ "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input src=\"example.gif\" type=\"image\" value=\"Hello\" /></div></form>",
+ button_to("Hello", "http://www.example.com", :type => "image", :src => "example.gif")
+ )
+ end
+
def test_link_tag_with_straight_url
assert_dom_equal "<a href=\"http://www.example.com\">Hello</a>", link_to("Hello", "http://www.example.com")
end

[code@penguin lib]$ vi url_helper.rb

[code@penguin lib]$ cat url_helper.rb
module ActionView
module Helpers
module UrlHelper
def button_to(name, options = {}, html_options = nil)
url = options.is_a?(String) ? options : url_for(options)
name ||= url

html_options = (html_options || {}).stringify_keys
if confirm = html_options.delete("confirm")
html_options["onclick"] = "return #{confirm_javascript_function(confirm)};"
end
html_options = { "type" => "submit", "value" => name }.merge(html_options)
convert_boolean_attributes!(html_options, %w( disabled ))

"<form method=\"post\" action=\"#{h url}\" class=\"button-to\"><div>" +
tag("input", html_options) + "</div></form>"
end
end
end
end
[code@penguin lib]$
[code@penguin lib]$ rm url_helper.rb.rej caller_wins_html_options_for_button_to.2.diff\?format\=txt url_helper.rb.orig

[code@penguin lib]$ cat >> ../config/environment.rb
require 'url_helper'

[code@penguin lib]$

Note that one hunk of the patch fails: we quickly check to make sure this is just the test case. Also, you may need to restart your rails application for this to work. And yikes, be careful if you use 'cat' -- remember to append with ">>", don't clobber your file with ">"!!!

The truth is it actually took me quite a while to come up with the above solution, perhaps more time than would have been lost had I simply used 'form_to' for the rest of my rails programming career. However, aside from the auxiliary benefit to the community that I have provided by doing the right thing and documenting it, there is another reason that the easy solution (using form_tag) is really a false laziness. Why? Because, if I hadn't done that the right way, I would have never learned HowToWritePluginToModifyRailsCore. Nor would I have got some delightful practice with such essential UNIX tools as 'patch', 'grep', and 'head'. The next time something about rails annoys me, I can just override it with my own plug-in.

So I strongly recommend you read and understand the above. It's the lazy thing to do.

No comments: