views:

103

answers:

2

I'm trying to tidy up my code by using named_scopes in Rails 2.3.x but where I'm struggling with the has_many :through associations. I'm wondering if I'm putting the scopes in the wrong place...

Here's some pseudo code below. The problem is that the :accepted named scope is replicated twice... I could of course call :accepted something different but these are the statuses on the table and it seems wrong to call them something different. Can anyone shed light on whether I'm doing the following correctly or not?

I know Rails 3 is out but it's still in beta and it's a big project I'm doing so I can't use it in production yet.

class Person < ActiveRecord::Base
  has_many :connections
  has_many :contacts, :through => :connections

  named_scope :accepted, :conditions => ["connections.status = ?", Connection::ACCEPTED]
  # the :accepted named_scope is duplicated
  named_scope :accepted, :conditions => ["memberships.status = ?", Membership::ACCEPTED]
end

class Group < ActiveRecord::Base
  has_many :memberships
  has_many :members, :through => :memberships
end

class Connection < ActiveRecord::Base
  belongs_to :person
  belongs_to :contact, :class_name => "Person", :foreign_key => "contact_id"
end

class Membership < ActiveRecord::Base
  belongs_to :person
  belongs_to :group
end

I'm trying to run something like person.contacts.accepted and group.members.accepted which are two different things. Shouldn't the named_scopes be in the Membership and Connection classes?

However if you try putting the named scopes in the Membership and Connection classes then you get this error (because Person.find(2).contacts returns an array of Persons which doesn't have an 'accepted' method:

>> Person.find(2).contacts.accepted
NoMethodError: undefined method `accepted' for #<Class:0x108641f28>

One solution is to just call the two different named scope something different in the Person class or even to create separate associations (ie. has_many :accepted_members and has_many :accepted_contacts) but it seems hackish and in reality I have many more than just accepted (ie. banned members, ignored connections, pending, requested etc etc)

A: 

You answered your own question:

Shouldn't the named_scopes be in the Membership and Connection classes?

Yes, they should be. This will let you call them as you wanted. It's also logically where they belong.

If you want something on Person that checks both, you can do something like:

named_scope :accepted, :conditions => ["connections.status = ? OR memberships.status = ?", Connection::ACCEPTED, Membership::ACCEPTED]

Maybe you want this to be an AND? not sure.

x1a4
No this doesn't work - you get an error - I've updated my question to show what the error is.>> Person.find(2).contacts.acceptedNoMethodError: undefined method `accepted' for #<Class:0x108641f28>
wakiki
and if you put both scopes into one as in your second suggestion you get an unknown column error:>> Person.find(2).contacts.acceptedActiveRecord::StatementInvalid: Mysql::Error: Unknown column 'memberships.status' in 'where clause': SELECT SQL_NO_CACHE `people`.* FROM `people` INNER JOIN `connections` ON `people`.id = `connections`.contact_id WHERE ((`connections`.person_id = 2)) AND ((connections.status = 0 OR memberships.status = 0) AND ((`connections`.person_id = 2)))
wakiki
You are missing the association from Person to Membership. Membership belongs to a Person, but Person does not have_one or have_many memberships. You may also need to use `:joins` in the named scope to make it work. What you want to do is definitely possible.
x1a4
A: 

I'm sure this is not the best way and I believe you can do this on person and group models, but I also believe the following will work for you:

# models
class Person < ActiveRecord::Base 
  has_many :connections 
  has_many :contacts, :through => :connections

  has_many :memberships
  has_many :groups, :through => :memberships
end 

class Group < ActiveRecord::Base 
  has_many :memberships 
  has_many :members, :through => :memberships
end 

class Connection < ActiveRecord::Base 
  belongs_to :person 
  belongs_to :contact, :class_name => "Person", :foreign_key => "contact_id" 

  named_scope :accepted, :conditions => ["status = ?", Connection::ACCEPTED] 
end 

class Membership < ActiveRecord::Base 
  belongs_to :person 
  belongs_to :group 

  named_scope :accepted, :conditions => ["status = ?", Membership::ACCEPTED] 
end 

# controller
# get person's accepted contacts
@person = Person.first
@person.connections.accepted.map(&:contact)

# get group's accepted members
@group = Group.first
@group.memberships.accepted.map(&:person)
j.