mdub@DogBiscuit.org
... mmm, crunchy!
about - weblog - software - resume - email - pgp

ReadOnlyFormBuilder

For RubyOnRails developers, form_for and fields_for are the accepted way of DRYing up form templates. You know the deal; you code

<% form_for :customer, :url => customers_path() do |customer_form| %>
  <p>
    <label>Name:</label> 
    <%= customer_form.text_field :first_name, :size => 15 %>
    <%= customer_form.text_field :last_name, :size => 20 %>
  </p>
  ... etc ...
<% end %>

and you get

<form action="/customers" method="post">
  <p>
    <label>Name:</label> 
    <input id="customer_first_name" name="customer[first_name]" size="15" type="text" />
    <input id="customer_last_name" name="customer[last_name]" size="20" type="text" value="" />
  </p>
  ... etc ...
</form>

Rails generates sensible field names and ids for you, and slurps existing values out of the model object. So far, so good.

Lately, I've taken to using the same trick when presenting data, not just when editing it. So, whereas before I might have written:

  <p>
    <label>Name:</label> 
    <span id="customer_first_name"><%= h @customer.first_name %></span>
    <span id="customer_last_name"><%= h @customer.last_name %></span>
  </p>
  ... etc ...

I'll now code it up as:

<% fields_for :customer, :builder => ReadOnlyFormBuilder do |customer_form| %>
  <p>
    <label>Name:</label> 
    <%= customer_form.text_field :first_name, :size => 15 %>
    <%= customer_form.text_field :last_name, :size => 20 %>
  </p>
  ... etc ...
<% end %>

and get the same output. (In case you're wondering, the ids are there to help with automated testing).

Note the similarity between the last code snippet and the first one on this page; apart from the first line they're indentical. Usually, I'll put the field-declarations themselves in a partial that's shared between "new", "edit" and "show" actions. That way, your "show" page automatically gets identical layout to the others, just with raw values in place of editable fields.

The ReadOnlyFormBuilder class itself it fairly straightforward - I'm planning to wrap it up into a plugin sometime soon. In the meantime, the implementation of text_field looks something like this:

def text_field(attribute, options={})
  content_tag("span", html_escape(value_of(attribute)), :id => "#{@object_name}_#{attribute}")
end

def value_of(attribute)
  value = model.send(attribute)
end

def model
  @object || @template.instance_variable_get("@#{@object_name}")
end

Rake profiling

Where's the bottleneck in your Rake build? Let's find out. Drop (or include) this in your Rakefile:

module Rake
  class Task
    def execute_with_timestamps(*args)
      start = Time.now
      execute_without_timestamps(*args)
      execution_time_in_seconds = Time.now - start
      printf("** %s took %.1f seconds\n", name, execution_time_in_seconds)
    end
    
    alias :execute_without_timestamps :execute
    alias :execute :execute_with_timestamps 
  end
end

method_missing magic - emulating Groovy's "it" in Ruby

Inspired variously by:

I've cooked up a shortcut for generating simple blocks, meaning that rather than

people.select { |x| x.name.length > 10 }

I can write such things as:

people.select(&its.name.length > 10)

Disclaimer: I think this is more "cool hack" than useful tool; it's probably too much of an alien artifact to be useful in real life. And it's not generally applicable, like "it" in Groovy. And really, it's not that much more verbose to use a block. Aaaaaanyway ...

The trick is that the above is parsed as

people.select(&(its.name.length.>(10)))

The "its" method creates a MessageBuffer object, which records the messages (method invocations) sent it's way:

irb(main):001:0> require 'message_buffer'
=> true
irb(main):002:0> its
=> #<MessageBuffer:0x6b40b44 @messages=[]>
irb(main):003:0> its.name.length < 10
=> #<MessageBuffer:0x6b3e678 @messages=[[:name], [:length], [:<, 10]]>

Now, the "&" operator coerces it's argument to a Proc, and MessageBuffer#to_proc generates a Proc that replays all the recorded messages. Q.E.D.

The full source-code is fairly short, so I'll include it inline:

class MessageBuffer 

  instance_methods.each do |m|
    undef_method m unless m =~ /^(__|respond_to|inspect)/ 
  end
  
  def initialize
    @messages = []
  end

  def method_missing(*message)
    @messages << message        # record the message
    self                        # return self so we can keep recording
  end
  
  def __replay_all_messages__(obj)
    @messages.inject(obj) do |obj, message|
      obj.__send__(*message)
    end 
  end
  
  def to_proc
    proc { |x| __replay_all_messages__(x) }
  end

end

def its
  MessageBuffer.new
end


Update: Florian Gross suggested a better way to replay recorded messages, using inject, and I've updated the code accordingly.

Presentation on Ruby/Rails at EJA

A couple of months ago I gave a presentation on Ruby and Rails to a local Java user-group. My slides are now online:

It contains a few examples showing how expressive Ruby can be, when compared to Java.

Tracing with a dynamic Proxy, in Ruby

Recently, I was writing a (Ruby) script to sync email between two IMAP servers. My unit-tests were all working, but something was going screwy when I plugged in a real server.

I wanted to be able to trace the conversation with the IMAP server (or at least, Ruby's IMAP API), to see what was going on. Initially, I started sprinkling tracing statements throughout my code, until I realised that it was going to be easier to define a simple "tracing proxy", and wrap it around the object I wanted to trace:

imap_handle = TracingProxy.new(imap_handle, $stderr)

# ... do stuff with imap_handle ...

It turned out to be straightforward to implement:

class TracingProxy

  def initialize(obj, dest) 
    @obj = obj
    @dest = dest
  end

  def method_missing(symbol, *args)
    arglist = args.map { |a| a.inspect }.join(', ')
    @dest.puts "#{symbol}(#{arglist})"
    rval = @obj.send(symbol, *args)
    @dest.puts ">> #{rval.inspect}"
    rval
  end
  
end

method_missing is a fallback method invoked when the called method isn't found - it's great for implementing dynamic proxies. There's nothing particularly ground-breaking going on here - this kind of trick is fairly common in Ruby-land.

My point is: implementing a dynamic-proxy for tracing was so easy in Ruby that I actually did it. I could have done something similar in Java, using java.lang.reflect.Proxy, or cglib - but I most likely wouldn't have bothered.

In Ruby, implementing the proxy made my life easier, not harder. Ruby encourages me to produce better designs.

Refactoring "support" for Ruby?

These days, there a number of pretty damn good IDEs for Java, with features like intelligent code-completion (aka "intellisense") and automated refactorings. I was a late-starter with IDEs, myself, but even just over the past year I've become annoyingly dependent on some of those IDE features.

Such features depend quite heavily on gleaning data-type information from the code, which is fine for languages like Java and C#. But in dynamically-typed languages like Ruby, we don't have that type info, so things like method-name completion and automated renaming become impossible. (Or so I thought).

Stealing a trick from SmallTalk

It's been puzzling me that there isn't better refactoring support for Ruby, given that the whole concept of refactoring grew out of the SmallTalk community, in the first place. Or more accurately, I've been confused about how automated refactoring could be possible in a dynamic language like SmallTalk.

Then, recently, I stumbled across a paper describing "A Refactoring Tool for Smalltalk", which contains the following explanation:

The Refactoring Browser uses method wrappers to collect runtime information. These wrappers are activated when the wrapped method is called and when it returns. The wrapper can execute an arbitrary block of Smalltalk code. To perform the rename method refactoring dynamically, the Refactoring Browser renames the initial method and then puts a method wrapper on the original method. As the program runs, the wrapper detects sites that call the original method. Whenever a call to the old method is detected, the method wrapper suspends execution of the program, goes up the call stack to the sender and changes the source code to refer to the new, renamed method. Therefore, as the program is exercised, it converges towards a correctly refactored program.

Ah-ha! Cunning.

The Ruby version

As it turns out, we can do much the same thing in Ruby ... leaving aside the "go up the call stack and change the source code" part.

Here's the supporting code:

def method_renamed(h)
  old_name = h.keys[0].to_sym
  new_name = h.values[0].to_sym
  define_method(old_name) { |*args|
    file, line = caller[1].split(':')
    warning = "##{old_name} renamed to ##{new_name}"
    $stderr.puts "#{file}:#{line}: #{warning}"
    send(new_name, *args)
  }
end

Okay, here's a method I want to rename:

class LinkPanel

  def render
    # ... 
  end

end

When I rename it, I also record the change using method_renamed:

class LinkPanel

  method_renamed :render => :to_html

  def to_html
    # ... 
  end

end

Now, I run my tests, and calls to the renamed method result in warnings:

/home/mikew/eyaw/sidebar.rb:229: #render renamed to #to_html

With a single key-chord in my Ruby IDE, I can jump directly to the source-code in question, and fix up the call. I imagine that an ever-so-slightly-more intelligent IDE could complete the refactoring, applying the rename to the call-site automatically! Later on, when I'm confident that everything has been cleaned up, I'll go back and remove that method_renamed alias.

There's more to refactoring than just renaming stuff, of course. I think the "dynamic analysis" trick would be useful to support other refactorings, too ... though I haven't tried it yet.

Proviso: this approach relies on actually running the code, preferably from tests. As the original paper says:

.. the refactoring is only as good as your test suite. If there are pieces of code that are not executed, they will never be analyzed, and the refactoring will not be completed ...