How to model a mutual friendship in Rails

后端 未结 3 539
小蘑菇
小蘑菇 2021-01-03 09:02

I know this question has been asked before on Stack Overflow, but the answers aren\'t doing it for me in ways I can explain. My general approach was inspired by this tutoria

3条回答
  •  半阙折子戏
    2021-01-03 09:50

    This is also achievable with a single has_many :through association and some query fiddling:

    # app/models/friendship.rb
    
    class Friendship < ApplicationRecord
      belongs_to :user
      belongs_to :friend, class_name: 'User'
    end
    

     

    # app/models/user.rb
    
    class User < ApplicationRecord
      has_many :friendships,
        ->(user) { FriendshipsQuery.both_ways(user_id: user.id) },
        inverse_of: :user,
        dependent: :destroy
    
      has_many :friends,
        ->(user) { UsersQuery.friends(user_id: user.id, scope: true) },
        through: :friendships
    end
    

     

    # app/queries/friendships_query.rb
    
    module FriendshipsQuery
      extend self
    
      def both_ways(user_id:)
        relation.unscope(where: :user_id)
          .where(user_id: user_id)
          .or(relation.where(friend_id: user_id))
      end
    
      private
    
      def relation
        @relation ||= Friendship.all
      end
    end
    

     

    # app/queries/users_query.rb
    
    module UsersQuery
      extend self
    
      def friends(user_id:, scope: false)
        query = relation.joins(sql(scope: scope)).where.not(id: user_id)
    
        query.where(friendships: { user_id: user_id })
          .or(query.where(friendships: { friend_id: user_id }))
      end
    
      private
    
      def relation
        @relation ||= User.all
      end
    
      def sql(scope: false)
        if scope
          <<~SQL
            OR users.id = friendships.user_id
          SQL
        else
          <<~SQL
            INNER JOIN friendships
              ON users.id = friendships.friend_id
              OR users.id = friendships.user_id
          SQL
        end
      end
    end
    

    It may not be the simplest of them all but it's certainly the DRYest. It does not use any callbacks, additional records and associations, and keeps the association methods intact, including implicit association creation:

    user.friends << new_friend
    

    via gist.github.com

提交回复
热议问题