Friday, February 29, 2008

Counting Records in Ruby on Rails

I was looking through our RoR code and I came across

  def estimate_remaining
    documents.inject(0.0) {sum, d sum + d.estimate_remaining}
  end
It turns out that this can be changed to
  def estimate_remaining
    documents.to_a.sum(&:estimate_remaining)
  end
thanks to some extensions to Enumerable in Active Support. I find this much easier to read; the former requires you to know some serious Ruby, though the latter takes advantage of a symbol trick that I don't really understand (&:estimate_remaining). (Thanks to this blog for the .to_a trick)

I was hoping to do something similar to another method,

  def documents_completed
    i = 0; documents.each {d i += 1 if d.completed}; i #.nitems
  end
which counts the number of documents under the parent project that are completed. A quick look in the Rails API documentation shows that sum() is the only aggregate method they added to Enumerable. So I decided to add count() myself. I shamelessly borrowed the sum() code and modified it to look like so:
module Enumerable

  # Calculates a count by evaluating elements. Examples:
  #
  #  payments.count { p p.price > 50 }
  #  payments.count(&:overdue)
  #
  # This is instead of payments.inject { sum, p p.overdue ? sum + 1 : sum  }
  #
  # The default identity (sum of an empty list) is zero.
  # However, you can override this default:
  #
  # [].count(Payment.new(0)) { p p.price > 50 } # => Payment.new(0)
  #
  def count(identity = 0, &block)
    return identity unless size > 0

    if block_given?
      map(&block).count
    else
      inject(0) { sum, element element ? sum + 1 : sum }
      #i = 0; map { element i += 1 if element}; i #This may be faster
    end
  end


end
I saved this in [PathToRailsApp]/config/initializers/count.rb (Rails 2 environment). Now my code to count completed documents looks like this:
  def documents_completed
    documents.to_a.count(&:completed)
  end

Much cleaner! Obviously, this method is only useful if you need to count records that satisfy some sort of criteria (otherwise .length would suffice). Also, this might be more efficient done via SQL, but in my case Document.completed is a Ruby method (or "virtual column"), not an SQL column.

Things to Come

A quick note on the kinds of things I'm into, and what I hope to put on this blog. At work, I use:

Most of the posts will probably relate to these in some way or another. At home, I use more software for different activities. I'm hoping that I'll get some posts on topics like home/computer audio, and making music on the cheap, watching HDTV on your computer, etc. I'd also like to throw in reviews/promos for stuff that I use and think is great. Obviously I'm not planning on spending my life comparing products, so these kinds of posts are likely to be biased. I'm the type of person who will put up with annoyances if utility is there.

Thursday, February 28, 2008

New Blog

This is the start of my technology/informational blog. This is something that I've wanted to do for a while, but just got around to starting. I see that just getting it set up has taken me way too long. I'm hoping to set aside a bit of time each week to do some blogging. We'll see how that goes.