Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Model design: Users have friends which are users

I'm looking to make sure my methods are correct before pursuing this association. The implementation sounds too complicated, so I think there must be something wrong with my plan. I am using structured (SQL) data storage, conforming to the rails storage conventions. What I have is a User model, it has an email address, password_digest, and name in the Schema.

class User < ActiveRecord::Base
  has_many :posts
end

I'd like to implement a has_many association to a friends collection, so that Users can belong_to Users (as friends). I'm hoping to be able to have User.last.friends.last return a User object when properly built and populated.

I believe I can create a model for this association like:

Class Friend < ActiveRecord::Base
  belongs_to :user
  belongs_to :friendlies, class: 'User'
end
Class User < ActiveRecord::Base
  has_many :posts
  has_many :friends
  has_many :friendly, class: 'Friend'
end

But I think that will require me to add an as to the models and query using User.last.friends.last.user So what I was thinking is this is a has_and_belongs_to_many relationship. Can I get away with the following (or something similar):

class User < ActiveRecord::Base
  has_and_belongs_to_many :friends, class: 'User'
end

I found this:

class User < ActiveRecord::Base
  has_many :user_friendships
  has_many :friends, through: :user_friendships


class UserFriendship < ActiveRecord::Base
  belongs_to :user
  belongs_to :friend, class_name: 'User', foreign_key: 'friend_id'

And this(self proclaimed 'standard'):

has_many :friendships, :dependent => :destroy
has_many :friends, :through => :friendships, :dependent => :destroy
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id", :dependent => :destroy
has_many :inverse_friends, :through => :inverse_friendships, :source => :user, :dependent => :destroy

Which I assume requires a Friendship model. I don't feel like I need a friendship model. I think that the class UserFriendship method looks right, but it requires an additional model. Now onto the questions:

  1. Can I do a has_and_belongs_to_many relationship with a table that relates user to friends which are users, without incurring an additional model to do so?

  2. Is it prudent to have an additional model 'in case' additional requirements pop up later?

like image 793
Andy Gauge Avatar asked Dec 10 '15 17:12

Andy Gauge


2 Answers

What you're looking for is something called a self referential join, which means that you can reference the same model with a different association:

#app/models/user.rb
class User < ActiveRecord::Base
   has_and_belongs_to_many :friendships,
      class_name: "User", 
      join_table:  :friendships, 
      foreign_key: :user_id, 
      association_foreign_key: :friend_user_id
end

You'd have to create a habtm join table to have a reference:

$ rails g migration CreateFriendshipsTable

#db/migrate/create_friendships_table____.rb
class CreateFriendShipsTable < ActiveRecord::Migration
   def change
      create_table :friendships, id: false do |t|
        t.integer :user_id
        t.integer :friend_user_id
      end

      add_index(:friendships, [:user_id, :friend_user_id], :unique => true)
      add_index(:friendships, [:friend_user_id, :user_id], :unique => true)
   end
end

$ rake db:migrate

There is a great reference here: Rails: self join scheme with has_and_belongs_to_many?


Is it prudent to have an additional model 'in case' additional requirements pop up later?

Only if you know you're going to have extra attributes. If not, you can get away with the habtm for as long as you want.

like image 157
Richard Peck Avatar answered Sep 21 '22 06:09

Richard Peck


You can try this

--friendship.rb

class Friendship < ApplicationRecord
  belongs_to :user
  belongs_to :friend, :class_name => 'User'
end

--user.rb

class User < ApplicationRecord
  has_many :friendships
  has_many :friends, through: :friendships
end

--db migrate xxxx_create_friendship.rb

class CreateFriendships < ActiveRecord::Migration[5.0]
  def change
    create_table :friendships do |t|
      t.belongs_to :user
      t.belongs_to :friend, class: 'User'
      t.timestamps
    end
  end
end
like image 45
Khoa Thai Avatar answered Sep 24 '22 06:09

Khoa Thai