slammin’ extensions

One of the cooler things about Ruby is the way you can extend objects with methods for a specific case. It really makes you feel like you’re slinging code, rather than just organising objects. Rails makes really good use of this ability with Association extensions.

Association extensions allow you to do cool things like:

class Person < ActiveRecord::Base
  
  has_many :messages do
    
    def mark_as_read!
      find(:all).read!
    end
    
  end
  
end

So now you can set all the messages for a particular person as “read” by calling:

oliver.messages.mark_as_read!

Cool, huh? (There’s more in the documentation if you’re interested).

A common antipattern I see is something like the following:

oliver.add_message!( new_message )

Being called to add a new message onto a Person’s “messages” association, when you’ve got some extra logic in add_message! which the default ActiveRecord “<<" method doesn't do for you. For example, you might need to send an email to notify the user they've got new messages.

Hey man, don’t forget the slam operator!

a = []
a << 1 << 2 << 3
puts a
#=> [1, 2, 3]

Well, I call it the “slam” operator, but it’s more correctly called the “push” operator. Or the bitwise left shift, depending on what kind of object you’re calling it on. Slam sounds way cooler though.

You can use the slam operator for your association extensions like this:

class Person < ActiveRecord::Base
  
  has_many :messages do
    
    def << new_message
      returning proxy_owner.messages do |m|
        m.add_message!(new_message)
      end
    end
    
  end
  
end

oliver.messages << a_brand_new_message

Super cool! You could also move the add_message! logic inside that extension, or make it private - force everyone to use the slam!

(PS. “proxy_owner” refers to the owner of the association - in this example, “oliver”. We use that “returning” block so that the << method returns the proxy_owner's messages collection and behaves like the same method on Array. It also means we can chain slams! CH-CH-CHAIN SLAM!)

(PPS. Don’t forget Assocation Callbacks - like :after_add or :before_add - for simple cases :) Don’t get complex unless you’ve got to!)


what other people thought

Doesn’t this work already? Or are you saying that add_message! takes objects other than of the Message class?

Ben, November 17th, 2008 at 11:43 am

Ha, as Ben quite rightly points out, “<<” already works by default on association extension. I was meaning you should use this when there is some *extra logic* in the add_message! method. Updated post.

Nik, November 17th, 2008 at 11:44 am

Named scopes are pretty cool for this too. You can do essentially the same thing as in the association extensions.

I find it especially handy because you can defined the named scope on Message, and it can be used from any model that has_many :messages, not just the person/message association.

Jeremy, November 17th, 2008 at 2:17 pm

@jeremy: Yeah, named scope kicks ass. Especially with the lambda { … } syntax, you could pretty much do anything you could do here.

Nik, November 17th, 2008 at 4:24 pm

have your say