Future on Rails

Using Rails

Named Scope or Virtual Attribute?

leave a comment »

The nice thing with Ruby is that it adheres to a basic OO principle: attributes and methods of a class should not be distinguishable. This way the client of class doesn’t have to know, whether “User.full_name” is an attribute or a method. In Rails this could also be a database column, an association or a named scope (all methods dynamically created by Rails). It is very convenient, that all those magic methods and attributes look and act the same :). However, I recently stumbled across a pitfall with named scopes and virtual attributes:

In my application, every “User” can have several “Bands”, of which he is the owner. Furthermore he can belong to several “Bands” as a member:

class User
  has_many :band_members,     :foreign_key => 'member_id'
  has_many :bands_as_member,  :through => :band_members, :source => 'band'
  has_many :bands_as_owner,   :class_name => 'Band', :foreign_key => 'owner_id'
  has_many :bands_as_member, :through => :band_members
end
class Band
  has_many    :band_members
  has_many    :members, :through => :band_members
  belongs_to  :owner, :class_name => 'User'
end

I used the rather long association names in the User model on purpose. If I would have called one of them simply “band”, I could really only receive part of the user’s bands through this one association. What I need to get ALL bands of a user (i.e. those which he owns and of which he is a member), I will either have to define a named scope or a vritual attribute combining all bands. Initially I thought it wouldn’t make a difference, since both approaches result in something called “User.bands” which returns all bands of the user.

Imagine, we would have modelled this as a virtual attribute (as I did, because I found it easier to think up than a SQL query 😉 ):

class User

  def bands

    self.bands_as_owner + self.bands_as_member

  end

end

This thing returns an array of all bands. What’s wrong with this approach? Well, this virtual attribute doesn’t act exactly like a named scope or an association. If you try the followin in a typical Band controller, you get an error:

def destroy
  @band = current_user.bands.find(params[:id])
  @band.destroy
end

The error you get doesn’t really give a hint to what happens here. After reading some documentation about the internals of Rails, I came across the problem. The thing is: our virtual attribute returns a simple Ruby Array – which doesn’t come with the find method of Rails. In contrast, when Rails dynamically creates such a method for an association or a named scope, the returned object is a proxy only ACTING AS IF it was an Array. In addition it also enhances the object by Rails’ magical methods you know.

This difference between virtual attributes and named scopes in the case mentioned above can really bite you. So be aware of the subtle differences between both approaches.

Advertisements

Written by Matthias Orgler

April 30, 2009 at 2:19 pm

Posted in Uncategorized

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

%d bloggers like this: