Resources

Download Source Code

Summary

# Terminal
rails g controller welcome index
rails g controller dashboard index
rails g model friendship user:references friend:references status:integer
rails g devise:install
rails g devise User

# Gemfile
gem 'devise'

# application.html.erb
    ...
    <div class='container'>
      <%= render 'layouts/navigation' %>
      <%= yield %>
    </div>
    ...

# layouts/_navigation.html.erb
<ul class='navigation'>
  <% if user_signed_in? %>
    <li><%= link_to current_user.email, edit_user_registration_path %></li>
    <li><%= link_to 'Logout', destroy_user_session_path, method: :delete %></li>
  <% else %>
    <li><%= link_to 'Login', new_user_session_path %></li>
  <% end %>
</ul>

# config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  authenticated :user do
    root 'dashboard#index', as: :authenticated_root
  end
  root 'welcome#index'
end

# 20181015003610_create_friendships.rb
class CreateFriendships < ActiveRecord::Migration[5.2]
  def change
    create_table :friendships do |t|
      t.references :user
      t.references :friend
      t.integer :status, default: 0, limit: 1

      t.timestamps
    end
  end
end

# models/friendship.rb
class Friendship < ApplicationRecord
  belongs_to :user
  belongs_to :friend, class_name: 'User'
  enum status: { pending: 0, requested: 1, accepted: 2, blocked: 3 }
  # id: 1
  # friend: 2

  # user_id: 1, friend_id: 2, status: :requested
  # user_id: 2, friend_id: 1, status: :pending
end

# models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_many :friendships
  has_many :friends, -> { where friendships: { status: :accepted }}, through: :friendships
  has_many :requested_friends, -> { where friendships: { status: :requested }}, through: :friendships, source: :friend
  has_many :pending_friends, -> { where friendships: { status: :pending }}, through: :friendships, source: :friend
  has_many :blocked_friends, -> { where friendships: { status: :blocked }}, through: :friendships, source: :friend

  # has_many :friendships_inverse, class_name: 'Friendship', foreign_key: :friend_id
  # has_many :friends_inverse, through: :friendships_inverse, source: :user

  # def all_friends
  #   friends + friends_inverse
  # end

  def friend_request(friend)
    unless self == friend || Friendship.where(user: self, friend: friend).exists?
      transaction do
        Friendship.create(user: self, friend: friend, status: :pending)
        Friendship.create(user: friend, friend: self, status: :requested)
      end
    end
  end

  # jane.accept_request(john)
  # john.accept_request(jane) ! bad
  def accept_request(friend)
    transaction do
      Friendship.find_by(user: self, friend: friend, status: [:requested])&.accepted!
      Friendship.find_by(user: friend, friend: self, status: [:pending])&.accepted!
    end
  end
end

# Rails Console
user1 = User.first
user2 = User.second
user1.friend_request(user2)
user2.accept_request(user1)