Skip to content

Commit

Permalink
Merge pull request #171 from hpi-swt2/feature/two-sided-contacts
Browse files Browse the repository at this point in the history
Two sided contacts
  • Loading branch information
Paula-Kli authored Feb 5, 2021
2 parents 2d80824 + 8ca23f0 commit d87e2d6
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 16 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ gem 'devise-bootstrap-views', '~> 1.1' # https://github.com/hisea/devise-bootstr
gem 'activestorage-validator'
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] # https://github.com/tzinfo/tzinfo-data
gem 'composite_primary_keys'

#
# Packaged JS, CSS libraries and helpers
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ GEM
xpath (~> 3.2)
childprocess (3.0.0)
coderay (1.1.3)
composite_primary_keys (12.0.6)
activerecord (~> 6.0.0)
concurrent-ruby (1.1.7)
crass (1.0.6)
debug_inspector (1.0.0)
Expand Down Expand Up @@ -305,6 +307,7 @@ DEPENDENCIES
bootstrap (~> 4.5, >= 4.5.2)
byebug
capybara (~> 3.33)
composite_primary_keys
devise (~> 4.7, >= 4.7.3)
devise-bootstrap-views (~> 1.1)
devise-i18n (~> 1.9, >= 1.9.2)
Expand Down
11 changes: 7 additions & 4 deletions app/controllers/contact_requests_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class ContactRequestsController < ApplicationController
before_action :authenticate_user!
before_action :find_user, only: [:accept]

def create
requested_user = User.find(params[:user_id])
Expand All @@ -18,10 +19,12 @@ def destroy
end

def accept
user = User.find(params[:id])
user.contacts << current_user
user.save
current_user.contact_requests.delete(params[:id])
@user.contacts << current_user
current_user.contact_requests.delete(@user)
redirect_to user_contact_requests_path(current_user), notice: t('user.contact_request.approved')
end

def find_user
@user = User.find(params[:id])
end
end
7 changes: 0 additions & 7 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,6 @@ def index
end
end

def add_contact
authenticate_user!
current_user.contacts << User.find(params[:id])
current_user.save
redirect_to root_path
end

def search
@users = User.search(params[:search]).where.not(id: current_user.id)
@users_to_add = @users.reject do |user|
Expand Down
19 changes: 19 additions & 0 deletions app/models/friendship.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# A class for handling contacts which ensures that the association is always symmetrical
class Friendship < ApplicationRecord
self.table_name = 'users_users'
self.primary_keys = :user_id, :contact_id

belongs_to :user, inverse_of: :friendships
belongs_to :contact, class_name: 'User', inverse_of: :friendships

after_create do |c|
unless Friendship.find_by(user_id: c.contact_id, contact_id: c.user_id)
Friendship.create(user_id: c.contact_id, contact_id: c.user_id)
end
end

after_destroy do |c|
reciprocal = Friendship.find_by(contact_id: c.user_id, user_id: c.contact_id)
reciprocal&.destroy
end
end
6 changes: 2 additions & 4 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@ class User < ApplicationRecord
has_many :activities, dependent: :delete_all
has_many :call_participants, dependent: :delete_all, inverse_of: :user
has_many :jitsi_calls, through: :call_participants
has_many :friendships, dependent: :destroy, inverse_of: :user
has_many :contacts, through: :friendships

# as we do not need to work with the relationship models as independent entities, `has_and_belongs_to_many` is fine
# https://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many
# rubocop:disable Rails/HasAndBelongsToMany
has_and_belongs_to_many :contacts,
class_name: 'User',
association_foreign_key: 'contact_id'

has_and_belongs_to_many :contact_requests,
class_name: 'User',
join_table: 'users_contact_requests',
Expand Down
1 change: 1 addition & 0 deletions spec/features/user_add_contact_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
click_button('✓')
expect(page).not_to have_text(requested_user.email)
expect(requesting_user.contacts).to include(requested_user)
expect(requested_user.contacts).to include(requesting_user)
end

it 'is possible to deny a contact request' do
Expand Down
33 changes: 33 additions & 0 deletions spec/models/friendship_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require 'rails_helper'

RSpec.describe Friendship, type: :model do
let(:users) { FactoryBot.create_list :user, 3 }

before { described_class.create(user_id: users[0].id, contact_id: users[1].id) }

it 'creates a symmetrical association' do
expect(described_class.find_by(user_id: users[0].id, contact_id: users[1].id)).to be_present
expect(described_class.find_by(user_id: users[1].id, contact_id: users[0].id)).to be_present
end

it 'deletes both parts of the association' do
described_class.find_by(user_id: users[0].id, contact_id: users[1].id).destroy!
expect(described_class.find_by(user_id: users[0].id, contact_id: users[1].id)).to be_blank
expect(described_class.find_by(user_id: users[1].id, contact_id: users[0].id)).to be_blank
end

describe 'multiple contacts' do
before { described_class.create(user_id: users[0].id, contact_id: users[2].id) }

it 'allows a user to have multiple contacts' do
expect(described_class.find_by(user_id: users[0].id, contact_id: users[1].id)).to be_present
expect(described_class.find_by(user_id: users[0].id, contact_id: users[2].id)).to be_present
end

it 'allows to only delete one of the contacts' do
described_class.find_by(user_id: users[0].id, contact_id: users[1].id).destroy!
expect(described_class.find_by(user_id: users[0].id, contact_id: users[1].id)).to be_blank
expect(described_class.find_by(user_id: users[0].id, contact_id: users[2].id)).to be_present
end
end
end
25 changes: 24 additions & 1 deletion spec/models/user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,30 @@
end

it 'destroys all participants when destroyed' do
expect { user.destroy }.to change(CallParticipant, :count).from(jitsi_calls.size).to(0)
expect { user.destroy }.to change(CallParticipant, :count).by(-jitsi_calls.size)
end
end

context 'with contacts' do
let(:contacts) { FactoryBot.create_list :user, 2 }

before do
user.save
contacts.each { |contact| user.friendships.create(contact: contact) }
end

it 'has associated contacts' do
expect(user.contacts).to include(*contacts)
end

it 'has friendships' do
expect(user.friendships.map(&:contact_id)).to include(*contacts.map(&:id))
end

it 'destroys all friendships when destroyed' do
user.destroy
expect(Friendship.where(user_id: user.id)).to be_empty
expect(Friendship.where(contact_id: user.id)).to be_empty
end
end
end

0 comments on commit d87e2d6

Please sign in to comment.