1 October 2009

Accessing current_user from within a model: just don't do it

I came across a situation where I thought I wanted to access the current_user from within a model, but it was just a case of mistaken thinking. Making session variables like current_user accessible to a model is a break with convention, and breaking with conventions is usually a bad idea. So, the rule is: don’t do it unless you have exhausted all other options. There is a probably an easier way of doing what you are trying to do.

Here was my situation. I had users logging in to an application and signing up for various groups. The relationship between models was as follows:

class Users < ActiveRecord::Base
  has_many :memberships, :dependent => :destroy
  has_many :groups, :through => :memberships

class Groups < ActiveRecord::Base
  has_many :memberships, :dependent => :destroy
  has_many :users, :through => :memberships

class Memeberships < ActiveRecord::Base
  belongs_to :user
  belongs_to :group

When a user created a new group, I wanted them to be added automatically to the group through a membership table, so at first I did something like this:

@group = Group.create(params[:group])

But, in doing this, I had already made an elementary mistake. Assuming the current_user is accessible to the controller, something like this would obviously have been better:

@group = current_user.groups.create(params[:group])

In other words, if I had written current_user.groups.create instead of just Group.create, the Membership table would have been updated automatically.

As an alternative, say I wanted to set up the current_user as the owner of the group, I could possibly do this:

@group = current_user.own_groups.create(params[:group])

Instead of adding the current_user as a member of the new group, I could make the current_user the owner (i.e. it’s the current_user’s ‘own group’). This would also need the following new line in the User model:

has_many :own_groups, :through => :memberships, :class_name => 'Group', :foreign_key => 'group_id'

And in the Group model:

has_one :owner, :through => :memberships, :class_name => 'User', :foreign_key => "id"

However, there is another—and my preferred—way of doing this. Rather than have the relationship defined in the Group model as a foreign key, the ‘ownership’ of the group can be defined in the Membership model, as a kind of membership.

This keeps the responsibility where it probably belongs, in the relationship (i.e. Membership) model. It also makes it easier to change the definition of ‘ownership’ at a future date, so that a group could have more than one ‘owner’. In other words, it’s a more flexible design.

d. sofer