Forum: Ruby on Rails Self-referential has_many :through relationship

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
C4dc94c893471878a105761a9207f29b?d=identicon&s=25 Zack Chandler (Guest)
on 2006-05-28 22:40
(Received via mailing list)
Hi,
  I have a self-referential has_many :through relationship setup to
track relationships between users.  Basically relationships are
modeled as a join table with an extra column 'relation'.

create table relationships (
  user_id    integer unsigned not null,
  friend_id  integer unsigned not null,
  relation    char(1) not null,
)

--- relations ---
f = friend
r = request to be a friend
b = blocked

My models look like this:

class Relationship < ActiveRecord::Base
  belongs_to :user
  belongs_to :friend, :class_name => 'User', :foreign_key => 'friend_id'
end

class User < ActiveRecord::Base
  has_many :relationships

  has_many :friendships, :class_name => 'Relationship', :foreign_key
=> 'user_id', :conditions => "relation = 'f'"
  has_many :potential_friendships, :class_name => 'Relationship',
:foreign_key => 'user_id', :conditions => "relation = 'r'"

  has_many :friends, :through => :friendships
  has_many :potential_friends, :through => :potential_friendships
end

Everything works great except the last potential_friends line.  It
seems unable to figure out to use the friend_id foreign key.  I have
tried to specified the :source param according to the docs.  Am I
missing something here?  The exact error is:

>> User.find(1).potential_friends
ActiveRecord::HasManyThroughSourceAssociationNotFoundError:
ActiveRecord::HasManyThroughSourceAssociationNotFoundError
        from
/usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.2/lib/active_record/reflection.rb:173:in
`check_validity!'
        from
/usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.2/lib/active_record/associations/has_many_through_association.rb:6:in
`initialize'
        from
/usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.2/lib/active_record/associations.rb:876:in
`potential_friends'
        from (irb):4

By the way I have read the excellent article over at
http://blog.hasmanythrough.com/articles/2006/04/21....
 Any ideas?

Thanks,
Zack

>> u.potential_friendships
=> [#<Relationship:0x247d44c @attributes={"relation"=>"r",
"user_id"=>"1", "friend_id"=>"22"}>, #<Relationship:0x247d410
@attributes={"relation"=>"r", "user_id"=>"1", "friend_id"=>"23"}>]
>> u.potential_friends
ActiveRecord::HasManyThroughSourceAssociationNotFoundError:
ActiveRecord::HasManyThroughSourceAssociationNotFoundError
        from
/usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.2/lib/active_record/reflection.rb:173:in
`check_validity!'
        from
/usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.2/lib/active_record/associations/has_many_through_association.rb:6:in
`initialize'
        from
/usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.2/lib/active_record/associations.rb:876:in
`potential_friends'
        from (irb):4

--------------- [ schema, classes] ---------------

create table relationships (
  user_id    integer unsigned not null,
  friend_id  integer unsigned not null,
  relation    char(1) not null,
  foreign key (user_id) references users(id) on update cascade on
delete cascade,
  foreign key (friend_id) references users(id) on update cascade on
delete cascade,
  primary key(user_id, friend_id)
) engine=innodb character set utf8;

class Relationship < ActiveRecord::Base
  belongs_to :user
  belongs_to :friend, :class_name => 'User', :foreign_key => 'friend_id'
end

class User < ActiveRecord::Base
  has_many :relationships

  has_many :friendships, :class_name => 'Relationship', :foreign_key
=> 'user_id', :conditions => "relation = 'f'"
  has_many :potential_friendships, :class_name => 'Relationship',
:foreign_key => 'user_id', :conditions => "relation = 'r'"

  has_many :friends, :through => :friendships
  has_many :potential_friends, :through => :potential_friendships
end
D5145c421cd25af6fa577c15219add90?d=identicon&s=25 unknown (Guest)
on 2006-05-29 01:50
(Received via mailing list)
C4dc94c893471878a105761a9207f29b?d=identicon&s=25 Zack Chandler (Guest)
on 2006-05-29 02:41
(Received via mailing list)
Thanks for the help - but if you re-read my post I mention that I've
already read this excellent blog entry (which helped me get everything
working except this one relationship).

Any other thoughts??

Zack
D5145c421cd25af6fa577c15219add90?d=identicon&s=25 unknown (Guest)
on 2006-05-29 03:46
(Received via mailing list)
oops. sorry. I kinda skimmed over it. Sorry I can't be of any help.
-N
9f0f89bbd9e1ecfbaab6584e429b7a2f?d=identicon&s=25 Josh Susser (jsusser)
on 2006-05-29 07:14
Hi Zack,

Glad to see you making use of my blog.  It looks like you're on the
right track, but I think your model has a couple problems. Don't worry
about it, as this is one of the trickier relationships to model.

The tricky part is that the join model table embodies friendship
relationships in two directions. In the English language friendship is
symmetrical, unlike a parent/child relationship. So you have to invent
the concepts of a friender and friendee. Call them whatever you like,
but you need to be able to distinguish a person who considers someone
else a friend from the someone who is considered a friend by that
person. That gives you have two ways for each person to be related by
friendship: 1) considering someone a friend, and 2) being considered a
friend. Those two relationships are what you need to model.

To model each relationship, you need a separate path through the join
model, each with a different foreign key. Look again at the example on
my blog and set something up that is equivalent to that. I suggest
starting simple and leaving out the different associations for actual
friends and potential friends. Once you get the basics working so that
each person can find his friends ane the people who call him a friend,
then you can work on potential friendships (in both directions, right?).
By the way, the conditions you are using for potential friendships look
fine, you just need to sort out the basic structure of the associations.

--
Josh Susser
http://blog.hasmanythrough.com
C4dc94c893471878a105761a9207f29b?d=identicon&s=25 Zack Chandler (Guest)
on 2006-05-29 18:01
(Received via mailing list)
Josh,

Thanks for the response.  Your blog has been a lot of help to clear up
the sometimes tricky has_many through associations.  I understand what
you mean when you say the "join model table embodies friendship
relationships in two directions."  However in this case I only care
about it in one direction.  In fact all the associations work fine in
my model except for the pontential_friends one.  From
./script/console:

>>User.find(1).relationships                     # works fine
>>User.find(1).friendships                        # works fine
>>User.find(1).potential_friendships      # works fine
>>User.find(1).friends                                # works fine
>>User.find(1).potential_friends              # crashes

I'm thinking that AR is unable to reflect on potential_friends and
determine to use the friend_id foreign key.  I'm normally able to poke
through the rails source fairly well but the code around the
association reflections is a bit hairy.

As it looks now I can either use a different join table for each
relationship which would be easy.  Or I can use custom finder sql for
the potential_friends relationship.

Any other thoughts would be greatly appreciated.

Thanks again for the help.
Zack
C4dc94c893471878a105761a9207f29b?d=identicon&s=25 Zack Chandler (Guest)
on 2006-05-29 18:41
(Received via mailing list)
All,
  Thanks for everyone how gave ideas on this!  It's working now - for
those who may have a similar situation:

# --- [ db ] ---
create table relationships (
  user_id                integer unsigned not null,
  other_user_id    integer unsigned not null,
  relation                char(1) not null,
  foreign key (user_id) references users(id) on update cascade on
delete cascade,
  foreign key (other_user_id) references users(id) on update cascade
on delete cascade,
  primary key(user_id, other_user_id)
) engine=innodb character set utf8;

create unique index relationships_unique_index on
relationships(user_id, other_user_id);

# --- [ user.rb snippet ] ---
class User < ActiveRecord::Base

  has_many :friendships, :class_name => 'Relationship', :foreign_key
=> 'user_id', :conditions => "relation = 'f'"
  has_many :potential_friendships, :class_name => 'Relationship',
:foreign_key => 'user_id', :conditions => "relation = 'r'"
  has_many :blocked_relationships, :class_name => 'Relationship',
:foreign_key => 'user_id', :conditions => "relation = 'b'"

  has_many :friends, :through => :friendships, :source =>
:berelationshipped
  has_many :potential_friends, :through => :potential_friendships,
:source => :berelationshipped
  has_many :blocked_users, :through => :blocked_relationships, :source
=> :berelationshipped
end

# --- [ relationship.rb snippet ] ---
class Relationship < ActiveRecord::Base
  belongs_to :relationshipped, :class_name => 'User', :foreign_key =>
'user_id'
  belongs_to :berelationshipped, :class_name => 'User', :foreign_key
=> 'other_user_id'
end

Zack
Bc7279484cc8b64ab5fd643832873955?d=identicon&s=25 cyx (Guest)
on 2006-05-29 19:08
Hello,

From my research on this it seems that defining has_many :through
associations require that you define an association in your Relationship
class.

in line 163 of reflections.rb

source_reflection_names.collect { |name|
through_reflection.klass.reflect_on_association(name) }.compact.first

what happens here (in your case) would be something like this

[:potential_friend, :potiential_friends].collect { |name|
Relationship.reflect_on_association(name) }.compact.first

since there is no potential_friend, or potential_friends defined in
Relationship, nil is returned hence the Exception.
This topic is locked and can not be replied to.