This Year's Entry

Posted on May 18, 2009

Valerie's Mom's coming for a visit next week, and so, in keeping with tradition, it's time for a little Perquackey smack talk:

Yonder bridge doth arch and span
Over river, treacherous land,
Up from rocks on which are dashed
Remains of jumpers,
Life's check cashed.

Oh, their lives they cut so short,
Seeking damages, retorts,
Ere they learned that winner's bray
Rings naught but hollow; their decay
Haphazards forth in
Od'rous ruin,
Odious runes ghast dark and grey.

Decrypt ye there the message lying,
Kindred spirits' fate decrying,
Ne'er again sweet light of day
Oversundring their dismay.
Withhold your taunts, you French ka-nigget,
Step down from tower high and bri'g it.
Nine, nay, ten dice all that matter
On this table, curs'd idle patter.
Blessed be the man whose letters speak
Out for themselves, and if they're weak,
Unbridled fury does not seek.

Nine days until the tourney fair, ten
Dice plus three of color rare
Suffice. And may the sweet light glare.

Default Association Attributes in ActiveRecord

Posted on March 2, 2009

I cringe a little bit when I see a controller like this:

class Admin::Subscribers::InvitationsController < Admin::ApplicationController
  before_filter :load_subscriber
  before_filter :build_invitation, :only => [:new, :create]

  def build_invitation
    @invitation = @subscriber.invitations.build(params[:invitation])
    @invitation.email = @subscriber.email # *shudder*
  end
end

While it’s almost okay, it irks me to have to explicitly set the Invitations’s email address in the controller12.

An Insight

What I really want is to set the email address when I make an Invitation for a Subscriber. There should only be two ways to say that:

@subscriber.invitations.build
@subscriber.invitations.create

A Solution

So, let’s place the responsibility for setting the Invitation’s email address directly inside the Subscriber’s has_many association:

class Subscriber < ActiveRecord::Base
  has_many :invitations, :as => :source, :extend => DefaultAssociationAttributes do
    def default_attributes
      { :email => proxy_owner.email }
    end
  end
end

module DefaultAssociationAttributes
  def build_record(attrs, &block)
    super(with_default_attributes(attrs), &block)
  end

  def create_record(attrs, &block)
    super(with_default_attributes(attrs), &block)
  end

  def with_default_attributes(attrs)
    default_attributes.merge((attrs || {}).symbolize_keys)
  end
end

And then clean up our controller:

class Admin::Subscribers::InvitationsController < Admin::ApplicationController
  before_filter :load_subscriber
  before_filter :build_invitation, :only => [:new, :create]

  def build_invitation
    @invitation = @subscriber.invitations.build(params[:invitation])
  end
end

Feedback?

I’m feeling pretty happy with this idea, so I’m thinking I’ll keep the code around and maybe throw a small gem up on GitHub.

UPDATE: The gem’s now in my GitHub account as default_association_attributes.

I may have missed another solution though, so I’d love to hear from you—how have you solved this problem? How have you felt about your solution?


  1. Having Invitation hang onto its own email address allows it to be re-used in non-Subscriber contexts—like when a User wants to create an Invitation for a friend.

  2. Somehow, using a custom method like Subscriber#build_invitation_with_email doesn’t feel much better—then I have to remember to use it instead of the default.

Using Authlogic and ActiveResource

Posted on February 19, 2009

Out of the box, Authlogic identifies the current user by looking for four different values:

This is pretty awesome—most things you’d like to do Just Work.

But I’d like to make one small adjustment, since I’m using ActiveResource and following the example of the Highrise API: rather than doing HTTP basic authentication with the user’s login and password, I’d like to use an API key instead.

It turns out Authlogic makes this fairly easy:

class UserSession < Authlogic::Session::Base
  # Adjust how Authlogic identifies the current user.
  # The default setting is :params, :session, :cookie, :http_auth.
  find_with :session, :cookie, :api_key
  
  def valid_api_key?
    controller.authenticate_with_http_basic do |api_key, _|
      self.unauthorized_record = search_for_record("find_by_#{single_access_token_field}", api_key)
      self.persisting = false
      self.valid?
    end.tap do |authenticated|
      self.persisting = true unless authenticated
    end
  end
end

So now I can write a Widget Resource class like this:

class Widget < ActiveResource::Base
  self.site = 'http://localhost:3000/'
  self.user = 'MY_API_TOKEN'
end

Or poke around with curl:

# Create a new, empty Widget.
curl --user   'MY_API_TOKEN:X'                \
     --header 'Content-Type: application/xml' \
     --data   '<widget></widget>'             \
     http://localhost:3000/widgets.xml