Archive for the 'snippets' Category

link_to as a block helper

Tuesday, June 19th, 2007

When it comes to creating helpers for the view portion of satisfaction, I’ve started taking the approach that once I see something “ugly” twice in the rhtml that it should be extracted out into a helper. In many cases, before that.

Today’s wart? link_to calls that have html for the text content. take for example:

<%= link_to "<strong>#{product.name}</strong><span>#{pluralize(product.topic_count(company), 'topic')}</span>", href, :class => "product_label" %>

ewww… and that doesn’t even get into having href defined above in a <% %> block. So, I extended link_to with the help of alias_method_chain such that it will take a block argument instead of the its normal first parameter.

<% link_to browse_url(product), :class => "product_label" do %>
  <strong><%= product.name %></strong>
  <span>(<%= pluralize(product.topic_count(company), 'topic') %>)</span>
<% end %> 

Much more readable. See the code block_link_to.rb

15 minute hack: Poor man’s function composition

Thursday, June 14th, 2007

I loves me some functional programming and I loves me some Symbol#to_proc (which lets you do .each(&:upcase)).

One of those _fp_ features I wish I had in ruby-land was function composition. Specifically, I find myself writing things like:

initials = ["Foo". "Bar", "Baz"].map{|str| str.first.upcase}

That is fine, but just isn’t as cool as leveraging to_proc in some fashion. In the more “stupid, stupid, stupid” case, i’ve also seen things like:

initials = ["Foo". "Bar", "Baz"].map(&:first).map(&:upcase)  # Eww, unnecessary iterations...

Anways, Ruby does have function composition ([Proc#compose][2]), thanks to the Ruby facets project, but working directly with lambdas isn’t nearly as friendly as I desire. So, I spent 15 minutes to hack together a means that I like. Here’s what it looks like:

["foo", "bar", "baz"].map(&:first >> :upcase)

Which reads, map, using :first then :upcase. >> is left associative, like "foo".first.upcase, opposite of Haskell composition where foobar = foo . bar reads foo follows bar.

Also, the snippet linked below lets you do something like:

  class String
    compose(:initial, &:first >> :upcase)
  end
  "foo".initial   # => "F"

Now a re-implementation of ActiveSupport’s Module#delegate:

class Module
  def delegate(*methods)
    options = methods.pop
    unless options.is_a?(Hash) && to = options[:to]
      raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
    end

    methods.each do |method|
      compose(method, &to >> method)
    end
  end
end

Perhaps not necessary, but I found it interesting and fun to write. Thoughts?

compose.rb

Caching time_ago_in_words

Saturday, June 2nd, 2007

I’m a big fan of the “english” timestamps that so often get used in apps these days; “Sat Jun 02 17:55:56 -0700 2007″ means almost nothing to me without active thought. It is much nicer to read “about a minute ago”, it requires no processing on my part. As many of you know, rails bundles this functionality into a helper called time_ago_in_words, and it works wonderfully.

The only issue is that you can’t page cache these timestamps. If you do, “one minute ago” won’t be right in short order. So, I worked up a little remix for time_ago_in_words that solves this problem.

The secret sauce is Javascript, as usual. cacheable_time_ago_in_words outputs a javascript call that does the translation:

  module CachedDateHelper
    def cachable_time_ago_in_words(from)
      js_call = javascript_tag "document.write(time_ago_in_words(#{(from.to_i * 1000).to_json}) + ' ago');"
      <<-EOS
        #{js_call}
        
      EOS
    end
  end

And then, on the javascript side:

  function time_ago_in_words(from) {
   return distance_of_time_in_words(new Date().getTime(), from) 
  }

  function distance_of_time_in_words(to, from) {
    seconds_ago = ((to  - from) / 1000);
    minutes_ago = Math.floor(seconds_ago / 60)

    if(minutes_ago == 0) { return "less than a minute";}
    if(minutes_ago == 1) { return "a minute";}
    if(minutes_ago < 45) { return minutes_ago + " minutes";}
    if(minutes_ago < 90) { return " about 1 hour";}
    hours_ago  = Math.round(minutes_ago / 60);
    if(minutes_ago < 1440) { return "about " + hours_ago + " hours";}
    if(minutes_ago < 2880) { return "1 day";}
    days_ago  = Math.round(minutes_ago / 1440);
    if(minutes_ago < 43200) { return days_ago + " days";}
    if(minutes_ago < 86400) { return "about 1 month";}
    months_ago  = Math.round(minutes_ago / 43200);
    if(minutes_ago < 525960) { return months_ago + " months";}
    if(minutes_ago < 1051920) { return "about 1 year";}
    years_ago  = Math.round(minutes_ago / 525960);
    return "over " + years_ago + " years" 
  }

You’ll notice there is no support for the rails-like include_seconds option. That is left as an exercise for the reader (ie. I don’t need to use it). Using this helper will let you page cache your views, greatly improving performance. It even has a fallback to absolute timestamps for those out there with a javascript disability.