David, Marcel, and I had an interesting design-related discussion yesterday. But it wasn’t related to designing graphical UI’s — it was abut designing developer UI’s in code.

I was sharing some code I’d written related to the new data export feature we’re adding to Basecamp. The Export model supports a set of distinct states, “pending”, “processing”, and “completed”. I found myself iterating over those states in a few different places so I added a custom iterator to the model. This allowed me to centralize the work needed to do that loop:

  class Export < ActiveRecord::Base
    PENDING    = "pending" 
    PROCESSING = "processing" 
    COMPLETED  = "completed" 

    STATES     = [PENDING, PROCESSING, COMPLETED]

    def self.each_state
      STATES.each { |state| yield state }
    end

    # ...
  end

The custom iterator is then used something like this:

  class ExportPresenter
    # ...

    Export.each_state do |state|
      class_eval "def #{state}?; @export && @export.#{state}?; end" 
    end

    # ...
  end

Some discussion ensued:

David H.
You find that more readable than just Export::STATES.each ?
Jamis B.
yah
Jamis B.
I really find constants, in general, rather ugly
David H.
Hmm.. I actually regret doing my own iterator for errors in AR
David H.
From the smalltalk book, Kent advices that you turn constants into methods
David H.
so you’d have
David H.
Export.states
David H.
the problem I have with custom iterators is that there are too many
David H.
and often times you don’t want each, but you want collect
David H.
or select
David H.
or each_with_index
Jamis B.
actually, I like that. Export.states.each

The result was much cleaner, and allowed for the full gamut of Array operations to be performed on Export.states.

  class Export < ActiveRecord::Base
    PENDING    = "pending" 
    PROCESSING = "processing" 
    COMPLETED  = "completed" 

    def self.states
      @states ||= [PENDING, PROCESSING, COMPLETED]
    end

    # ...
  end

  class ExportPresenter
    # ...

    Export.states.each do |state|
      class_eval "def #{state}?; @export && @export.#{state}?; end" 
    end

    # ...
  end

Jamis B.
I like that better
David H.
exactly
Marcel M.
that’s a cool little pattern
Mark I.
That reads much better than Export::STATES does too.
Jamis B.
yah
Marcel M.
WHY DO YOU ALL HATE EIFFEL SO MUCH