Forum: Ruby on Rails 2 belongs_to to the same parent table

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.
Agathe A. (Guest)
on 2006-02-27 10:02
Hello!

I have 2 table: users and buddies
User: id, name, ...
Buddy: id, user_id, user_buddy_id, ...

So if you have 2 users 1,jack and 2,fred and fred is a buddy of jack,
there is a Buddy object: id=1, user_id=1, user_buddy_id=2


I can declare only one belongs_to in Buddy and one has_many in User. And
there is conflict if I had the second one (the first one is discarded)

class User
  has_many :buddies, :foreign_key => 'user_id'
 #has_many :buddies, :foreign_key => 'user_buddy_id' # does not work, if
added as last, the previous relationship on buddies drops
end

class Buddy
  belongs_to :user, :foreign_key => 'user_id'
 #belongs_to :user, :foreign_key => 'user_buddy_id' # does not work
end


Is there a solution existing in Rails that solves this problem? Or is
this design bad anyway?
I cannot think of any elegant solution. Any advice welcome!!

Thnx!!

agathe
Norman T. (Guest)
on 2006-02-27 12:24
(Received via mailing list)
Am Montag, den 27.02.2006, 09:02 +0100 schrieb agathe agathe:

> I have 2 table: users and buddies
> User: id, name, ...
> Buddy: id, user_id, user_buddy_id, ...
>
> So if you have 2 users 1,jack and 2,fred and fred is a buddy of jack,
> there is a Buddy object: id=1, user_id=1, user_buddy_id=2

>   belongs_to :user, :foreign_key => 'user_id'
>  #belongs_to :user, :foreign_key => 'user_buddy_id' # does not work
> end
>
>
> Is there a solution existing in Rails that solves this problem? Or is
> this design bad anyway?
> I cannot think of any elegant solution. Any advice welcome!!
>

Here is an interesting discussion about this kind of problem:

http://www.sitepoint.com/forums/showthread.php?t=309718

The solution is to do a self-join on your users table. If you have
further questions let me know.

--
Norman T.

http://blog.inlet-media.de
Agathe A. (Guest)
on 2006-02-27 12:44
Thanks!! I'm going to look into it

agathe

> Here is an interesting discussion about this kind of problem:
>
> http://www.sitepoint.com/forums/showthread.php?t=309718
>
> The solution is to do a self-join on your users table. If you have
> further questions let me know.
>
> --
> Norman T.
>
> http://blog.inlet-media.de
Agathe A. (Guest)
on 2006-02-28 11:42
So I got it to work as i wanted, until i realized that i cannot modify
the attributes of the association because the association table has 2
fields as primary key.


>> b = Buddy.find :first
=> #<Buddy:0x3ba05c8 @attributes={"user_buddy_id"=>"10",
"b_profile_id"=>"0", "user_id"=>"3", "b_date_created"=>nil,
"b_activated"=>"0"}>
>> b.b_activated = 1
=> 1
>> b.save!
ActiveRecord::StatementInvalid: #42S22Unknown column
'user_iduser_buddy_id' in 'where clause': UPDATE buddies SET
`b_profile_id` = 0, `b_date_created` = NULL, `user_buddy_id` = 10,
`b_activated` = 1, `user_id` = 3 WHERE id  = NULL


I haven't found anything about declaring the primary key of a table to a
set of keys.

Is the only solution to delete the record and create it again? or to
make the sql statement directly?

thanks for the help!

agathe
Agathe A. (Guest)
on 2006-02-28 17:56
It seems to work now as i want. I only rewrote a method update in Buddy
and created a 'second_key'. It's not all perfect but it works for me.

To wrap up what i have:

The 2 tables:

users:
-id
-username
-activated
-...

buddy:
-user_id
-user_buddy_id
-b_activated (0: at creation 1: when the user identified by
user_buddy_id has accepted to be buddy of the user identified by
user_id)
note: as you have a field also in user called 'activated', you have to
use another name otherwise one is dropped, you cannot access to both
'activated'

The 2 models:

class User < ActiveRecord::Base
  has_and_belongs_to_many :buddies,
                          :join_table => 'buddies',
                          :foreign_key => 'user_id',
                          :association_foreign_key => 'user_buddy_id',
                          # i don't need those because the relation is
                          #directional:
                          #:after_add => :create_reverse_association,
                          #:after_remove => :remove_reverse_association,
                          :class_name => 'User',
                          :conditions => 'b_activated=1'

# other convenient access
  has_and_belongs_to_many :sent_buddy_requests,
                          :join_table => 'buddies',
                          :foreign_key => 'user_id',
                          :association_foreign_key => 'user_buddy_id',
                          :class_name => 'User',
                          :conditions => 'b_activated=0'

  has_and_belongs_to_many :received_buddy_requests,
                          :join_table => 'buddies',
                          :foreign_key => 'user_buddy_id',
                          :association_foreign_key => 'user_id',
                          :class_name => 'User',
                          :conditions => 'b_activated=0'

  has_and_belongs_to_many :is_buddy_of,
                          :join_table => 'buddies',
                          :foreign_key => 'user_buddy_id',
                          :association_foreign_key => 'user_id',
                          :class_name => 'User',
                          :conditions => 'b_activated=1'

end


class Buddy < ActiveRecord::Base
# I have added a 'second_key' and copied more or less the code as it
came from
# the 'primary_key'. Only problem: in the update code
  def self.second_key
    reset_second_key
  end

  def self.set_second_key( value=nil, &block )
    define_attr_method :second_key, value, &block
  end

  def reset_second_key
    key = 'id2'
# I don't understand what this code is doing:
#    case primary_key_prefix_type
#      when :table_name
#        key =
Inflector.foreign_key(class_name_of_active_record_descendant(self),
false)
#      when :table_name_with_underscore
#        key =
Inflector.foreign_key(class_name_of_active_record_descendant(self))
#    end
    set_second_key(key)
    key
  end


  set_primary_key "user_id"
  set_second_key "user_buddy_id"

  def update
    att_quoted = attributes_with_quotes(false)
    att_quoted.delete(self.class.second_key)
    connection.update(
      "UPDATE #{self.class.table_name} " +
      "SET #{quoted_comma_pair_list(connection, att_quoted)} " +
      "WHERE #{self.class.primary_key} = #{quote(id)} " +
      " AND #{self.class.second_key} = #{quote(user_buddy_id)}",
      "#{self.class.name} Update"
    )
  end
end


Now I can do this:
>> a = User.find_by_username 'agathe'
>> n = User.find_by_username 'newthing'
>> a.buddies.push_with_attributes(n,{:b_activated => 0})
>> n.received_buddy_requests.size
=> 1
>> a.sent_buddy_requests[0]
=> #<User:0x3b45008 @attributes={"user_buddy_id"=>"11",
"username"=>"newthing",  "id"=>"11", "user_id"=>"3",
"b_activated"=>"0"}>
>> n.received_buddy_requests[0]
=> #<User:0x3b6d538 @attributes={"user_buddy_id"=>"11",
"username"=>"agathe",  "user_id"=>"3", "id"=>"3", "b_activated"=>"0"}>
>> b= Buddy.find_first
=> #<Buddy:0x3aa38f0 @attributes={"user_buddy_id"=>"11", "user_id"=>"3",
"b_activated"=>"0"}>
>> b.b_activated = 1
=> 1
>> b.save!
=> true

Note: you never get a Buddy object if you do ie a.buddies[0], you get a
user object, so the changes made on that object won't be real. You need
to make the changes on the Buddy object directly, i.e on b =
Buddy.find_first


heepee
This topic is locked and can not be replied to.