27 March, 2008

New Gem: Avatar

I released Avatar version 0.0.3 today. This gem offers avatar support for a variety of sources. It's not Rails-specific, but to use it in a Rails app, do something like the following.

In app/helpers/people_helper.rb:

class PeopleHelper
  include Avatar::View::ActionViewSupport

  def default_avatar_url(size)
    req = controller.request
    "#{req.protocol}#{req.host_with_port}#{image_path("/images/avatar_default_#{size}.png")}"
  end
end

app/views/people/show.html.erb:

<%= avatar_tag(@current_user, :size => 40, :default => default_avatar_url(:small) %>

The default settings will check for a Gravatar for @current_user.email. There are other implementations, including one that works with the file_column plugin. I'll be happy to add more implementations; the project is hosted on GitHub.

11 March, 2008

Shelves: Rack Extensions

I think Christian's Rack is fantastic. It's such a good idea, in fact, that Ab5tract and I are starting a GitHub projct called Shelves for Rack extensions. Our mission is to do request and response support for all the non-HTTP types of interaction that a website needs to do these days. We envision SMS, Email, and Twitter queries, and maybe even Port Knocking. Shelves will be modular, with lots of support classes for new request/response media.

More info as we get near a usable version. If anyone wants to contribute, drop us a line.

01 March, 2008

The Final Word on Rails Association Extension

There has been a great deal of debate on how to extend Rails Associations. Ryan discusses the "block" method:

class Organization < ActiveRecord::Base
  has_many :people do
    def find_active
      find(:all, :conditions => ["active = ?", true])
    end
  end
end

This is identical to the "extend" method; in fact Rails converts a "block" into an "extend":

module FindActiveExtension
  def find_active
    find(:all, :conditions => ["active = ?", true])
  end
end

class Organization < ActiveRecord::Base
  has_many :people, :extend => FindActiveExtension
end

The commenters then debate the merits of the "class method" method:

class Person < ActiveRecord::Base
  def self.find_active
    find(:all, :conditions => ["active = ?", true])
  end
end

class Organization < ActiveRecord::Base
  has_many :people
end

In all three cases (really two), you can call something like:

organization.people.find_active

There are differences, though! I ran some experiments to find out how each works, and found that unless you have some particular reason not to, you should always use the "block" or "extend" version, for two reasons:

  1. The "class method" version does not allow manual caching.
    class Person < ActiveRecord::Base
      def self.find_active
        @active ||= find(:all, :conditions => ["active = ?", true])
      end
    end
    
    Will break, as Adam T. mentioned in the comments.
  2. The association version is slightly faster then the class version. I created the same has_many relationship to two pairs of classes (Person has_many Things; User has_many Widgets). I then added 6 sub-association selectors (red, green, blue, small, medium, large) in different ways: Thing had class methods; User had the same methods embedded in the has_many association. I then ran benchmarks on 100 users/people and 5000 things/widgets (with identical respective associations). The x1 times are for finding all of 6 sub-association lookups on every Person (Class) and User (Association). The x2 times are for finding and reloading all 6 (that is, to gain the benefit of any automatic caching done by Rails or the database). In absolute times:
                               user     system      total        real
    Class (x1):            4.650000   0.680000   5.330000 ( 11.320903)
    Association (x1):      4.530000   0.670000   5.200000 ( 10.861711)
    Class (x2):            9.240000   1.360000  10.600000 ( 21.934539)
    Association (x2):      9.000000   1.330000  10.330000 ( 21.066327)
    
    Dividing the bottom two rows in half:
                               user     system      total        real
    Class (x1):            4.650000   0.680000   5.330000 ( 11.320903)
    Association (x1):      4.530000   0.670000   5.200000 ( 10.861711)
    Class (x2)/2:          4.620000   0.680000   5.300000 ( 10.967270)
    Association (x2)/2:    4.500000   0.665000   5.165000 ( 10.533164)
    
    Dividing all rows by the number of queries performed (per/query time):
                               user     system      total        real
    Class (x1)/600:        0.007750   0.001133   0.008883 (  0.018868)
    Association (x1)/600:  0.007550   0.001117   0.008667 (  0.018103) 
    Class (x2)/1200:       0.007700   0.001133   0.008833 (  0.018279)
    Association (x2)/1200: 0.007500   0.001108   0.008608 (  0.017555)
    
    The association version is a little faster, and gains more from the automatic caching Rails does.
  3. You can check out the experiment at https://svn.u-presence.com/svn/experiments/association_vs_class/ (username guest, no password).