views:

534

answers:

1

I'm working on a project for my school on rails (don't worry this is not graded on code) and I'm looking for a clean way to traverse relationships in ActiveRecord.

I have ActiveRecord classes called Users, Groups and Assignments. Users and Groups have a HABTM relationship as well as Groups and Assignments. Now what I need is a User function get_group(aid) where "given a user, find its group given an assignment".

The easy route would be:

  def get_group(aid)
    group = nil
    groups.each { |g| group = g if g.assignment.find(aid).id == aid }
    return group
  end

Is there a cleaner implementation that takes advantage of the HABTM relationship between Groups and Assignments rather than just iterating? One thing I've also tried is the :include option for find(), like this:

  def get_group(aid)
   user.groups.find(:first, 
     :include => :assignments, 
     :conditions => ["assignments.id = ?", aid])
  end

But this doesn't seem to work. Any ideas?

+2  A: 

First off, be careful. Since you are using has_and_belongs_to_many for both relationships, then there might be more than one Group for a given User and Assignment. So I'm going to implement a method that returns an array of Groups.

Second, the name of the method User#get_group that takes an assignment id is pretty misleading and un-Ruby-like.

Here is a clean way to get all of the common groups using Ruby's Array#&, the intersection operator. I gave the method a much more revealing name and put it on Group since it is returning Group instances. Note, however, that it loads Groups that are related to one but not the other:

class Group < ActiveRecord::Base
  has_and_belongs_to_many :assignments
  has_and_belongs_to_many :users

  # Use the array intersection operator to find all groups associated with both the User and Assignment 
  # instances that were passed in
  def self.find_all_by_user_and_assignment(user, assignment)
    user.groups & assignment.groups
  end
end

Then if you really needed a User#get_groups method, you could define it like this:

class User < ActiveRecord::Base
  has_and_belongs_to_many :groups

  def get_groups(assignment_id)
    Group.find_all_by_user_and_assignment(self, Assignment.find(assignment_id))
  end
end

Although I'd probably name it User#groups_by_assignment_id instead.

My Assignment model is simply:

class Assignment < ActiveRecord::Base
  has_and_belongs_to_many :groups
end
nertzy