jfdimark
jfdimark

Reputation: 2369

Deleting a 'friend' that added you using self-referential association

I can implement reverse relationships, so if UserA adds UserB, then it shows UserA in B's profile, and visa versa. But I cannot figure out how to let UserB remove UserA as a friend, if UserA added UserB. I've tried so many different ways, but everytime I change something it moves the problem elsewhere! I can't tell if the fundamental issue is:

Code snippets below:

class FriendshipsController < ApplicationController

      def destroy
        @friendship = current_user.friendships.find(params[:id])
        @friendship.destroy
        flash[:notice] = "Removed friendship."
        redirect_to current_user
      end

In the view

        <% @user.inverse_friends.each do |inverse_friendship| %>
      <li>
        <%= inverse_friendship.name %>
        <%= link_to "remove", @user.inverse_friendships, :method => :delete, :class => "btn-small btn-danger" %><br />
        <%= image_tag inverse_friendship.avatar(:thumb) %>

My models:

class Friendship < ActiveRecord::Base
  belongs_to :user
  belongs_to :friend, class_name: 'User'
  attr_accessible :friend_id, :user_id
end 

 class User < ActiveRecord::Base

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

And routes:

    resources :friendships

  authenticated :user do
    root :to => 'home#index'
  end
  root :to => "home#index"
  devise_for :users, :controllers => { :registrations => :registrations }
  resources :users

Upvotes: 1

Views: 215

Answers (1)

numbers1311407
numbers1311407

Reputation: 34072

Your main problem is a:

a. how the FriendshipsController destroy method is defined

You're looking for the friendship in the current_user.friendships, but it's not there. It's in inverse_friendships.

You'd need to either check both associations, or let the controller know which one you're looking for. The latter is probably preferable since although they are the same class, they are different resources. Something like this maybe:

# In routes, route inverse friendships to the same controller, but with a
# different path (I'm routing everything here, you may not need that.)
resources :friendships
resources :inverse_friendships, :controller => 'friendships'


# Then in your friendships controller, use the path to determine which 
# collection you're working with:
#
def destroy
  @friendship = collection.find(params[:id])
  # ...
end

# the other collection methods would use the same collection, if you needed them,
# for example:
def create
  @friendship = collection.build(params[:friendship])
  # ..
end

protected

# simple case statement here, but you get the idea
def collection
  case request.path
  when /\/inverse_friendships/ then current_user.inverse_friendships
  else current_user.friendships
  end
end

Finally in your view you'd route to an inverse friendship like:

<%= link_to "remove", inverse_friendship_path(friendship), :method => :delete %>

A normal friendship could use the shorter form, or the full named route:

<%= link_to "remove", friendship, :method => :delete %>
OR
<%= link_to "remove", friendship_path(friendship), :method => :delete %>

EDIT: Searching both associations.

Of course if you wanted to keep it simple, and had no other use for inverse_friends being a separate resource, you could always just...

def destroy
  id, cid = params[:id], current_user.id

  # search both associations (two queries)
  @friendship = current_user.friendships.find_by_id(id) ||
                  current_user.inverse_friendships.find(id)

  # or query friendship looking for both types
  @friendship = Friendship.
                  where("user_id = ? OR friend_id = ?", cid, cid).find(id)

  # ...
end

Upvotes: 1

Related Questions