25、RailsSpace 社交网络中好友关系的实现与管理

RailsSpace 社交网络中好友关系的实现与管理

1. 好友关系的数据建模

在社交网络中,好友关系的建模是一个具有挑战性的问题。最初,我们可能会想到创建一个 friends 表来存储好友信息,但深入思考后会发现,这样做不仅可能需要在数据库中存储 Ruby 列表,而且会导致数据冗余,因为好友本质上就是用户。因此,我们需要一种更合理的方式来建模好友关系。

为了更好地理解好友关系,我们引入一个新用户 Baz Quux (用户名 bazquux )。假设 Foo Baz 发起好友请求,那么 Baz 会收到来自 Foo 的好友请求,而 Foo 的好友请求则处于待处理状态。如果 Baz 接受了请求,他们就正式成为好友。

在数据库表结构方面,我们可以使用 users 表和 friendships 表来模拟好友关系。 friendships 表用于连接不同的用户,每个好友关系由两行记录表示。例如, Foo (用户 ID 为 1)和 Baz (用户 ID 为 2)之间的潜在好友关系如下表所示:
| user_id | friend_id | status |
| ---- | ---- | ---- |
| 1 | 2 | ‘pending’ |
| 2 | 1 | ‘requested’ |

当好友关系被接受后,两行记录的 status 都将变为 'accepted' 。虽然使用两行记录来表示一个好友关系可能看起来有些浪费,但这样可以方便地区分请求发起者和接受者,并且在后续查询特定用户的好友列表时更加容易。

2. 构建 Friendship 模型

为了将上述抽象的好友关系建模具体化,我们需要创建一个 Friendship 模型。首先,使用以下命令生成 Friendship 模型:

> ruby script/generate model Friendship user_id:integer friend_id:integer \
status:string created_at:datetime accepted_at:datetime

该命令会生成相应的模型文件、测试文件和迁移文件。迁移文件 db/migrate/008_create_friendships.rb 如下:

class CreateFriendships < ActiveRecord::Migration
  def self.up
    create_table :friendships do |t|
      t.column :user_id, :integer
      t.column :friend_id, :integer
      t.column :status, :string
      t.column :created_at, :datetime
      t.column :accepted_at, :datetime
    end
  end

  def self.down
    drop_table :friendships
  end
end

运行 rake db:migrate 命令来更新数据库。

Friendship 模型的代码如下:

class Friendship < ActiveRecord::Base
  belongs_to :user
  belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
  validates_presence_of :user_id, :friend_id
end

这里, belongs_to :friend 告诉 Rails 将第二个用户视为 “好友”,并使用 friend_id 作为外键。实际上,Rails 会使用 User 模型和 users 表来存储相关信息。

3. 创建待处理的好友关系

我们可以使用 Rails 控制台来测试 Friendship 模型。首先,创建两个用户对象:

> ruby script/console
>> user = User.find_by_screen_name("foobar")
>> friend = User.find_by_screen_name("bazquux")

然后,创建待处理的好友关系:

>> Friendship.create(:user => user, :friend => friend, :status => 'pending')
>> Friendship.create(:user => friend, :friend => user, :status => 'requested')

我们可以使用以下代码来查询数据库中的好友关系:

>> Friendship.find_by_user_id_and_friend_id(user, friend)
>> Friendship.find_by_user_id_and_friend_id(friend, user)

需要注意的是,在创建和查询好友关系时,我们可以省略 .id ,Rails 会自动处理。

4. 实现好友请求方法

为了方便创建待处理的好友关系,我们在 Friendship 模型中添加一个类方法 request

class Friendship < ActiveRecord::Base
  belongs_to :user
  belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
  validates_presence_of :user_id, :friend_id

  # Return true if the users are (possibly pending) friends.
  def self.exists?(user, friend)
    not find_by_user_id_and_friend_id(user, friend).nil?
  end

  # Record a pending friend request.
  def self.request(user, friend)
    unless user == friend or Friendship.exists?(user, friend)
      transaction do
        create(:user => user, :friend => friend, :status => 'pending')
        create(:user => friend, :friend => user, :status => 'requested')
      end
    end
  end
end

request 方法中,我们使用事务来确保两个创建操作要么都成功,要么都失败,避免出现不完整的好友关系记录。同时,在创建好友关系之前,我们会检查用户是否相同以及好友关系是否已经存在。

5. 完善 Friendship 模型

除了好友请求方法,我们还需要实现接受好友请求和结束好友关系的方法。在 Friendship 模型中添加以下代码:

class Friendship < ActiveRecord::Base
  # ... 已有代码 ...

  # Accept a friend request.
  def self.accept(user, friend)
    transaction do
      accepted_at = Time.now
      accept_one_side(user, friend, accepted_at)
      accept_one_side(friend, user, accepted_at)
    end
  end

  # Delete a friendship or cancel a pending request.
  def self.breakup(user, friend)
    transaction do
      destroy(find_by_user_id_and_friend_id(user, friend))
      destroy(find_by_user_id_and_friend_id(friend, user))
    end
  end

  private
  # Update the db with one side of an accepted friendship request.
  def self.accept_one_side(user, friend, accepted_at)
    request = find_by_user_id_and_friend_id(user, friend)
    request.status = 'accepted'
    request.accepted_at = accepted_at
    request.save!
  end
end

accept 方法将待处理的好友关系更新为已接受状态, breakup 方法则用于删除好友关系。在 breakup 方法中,我们使用 destroy 方法而不是 delete 方法,因为 destroy 方法会触发模型的验证和相关回调函数。

6. 测试 Friendship 模型

为了确保 Friendship 模型的正确性,我们编写了以下测试代码:

require File.dirname(__FILE__) + '/../test_helper'
class FriendshipTest < Test::Unit::TestCase
  fixtures :users

  def setup
    @user = users(:valid_user)
    @friend = users(:friend)
  end

  def test_request
    Friendship.request(@user, @friend)
    assert Friendship.exists?(@user, @friend)
    assert_status @user, @friend, 'pending'
    assert_status @friend, @user, 'requested'
  end

  def test_accept
    Friendship.request(@user, @friend)
    Friendship.accept(@user, @friend)
    assert Friendship.exists?(@user, @friend)
    assert_status @user, @friend, 'accepted'
    assert_status @friend, @user, 'accepted'
  end

  def test_breakup
    Friendship.request(@user, @friend)
    Friendship.breakup(@user, @friend)
    assert !Friendship.exists?(@user, @friend)
  end

  private
  # Verify the existence of a friendship with the given status.
  def assert_status(user, friend, status)
    friendship = Friendship.find_by_user_id_and_friend_id(user, friend)
    assert_equal status, friendship.status
  end
end

运行测试代码:

> rake db:test:prepare
> ruby test/unit/friendship_test.rb

测试结果应该显示所有测试用例都通过。

7. 好友请求功能的实现

在 RailsSpace 中,我们希望用户能够通过点击每个用户资料页面上的好友请求链接来发送好友请求。为了实现这一功能,我们需要完成以下步骤:

7.1 生成 Friendship 控制器

使用以下命令生成 Friendship 控制器:

> ruby script/generate controller Friendship
7.2 添加好友请求链接

在用户资料页面的联系框中添加好友请求链接。代码如下:

<% if logged_in? and @user != @logged_in_user %>
  <div class="sidebar_box">
    <h2>
      <span class="header">Actions</span>
      <br clear="all" />
    </h2>
    <ul>
      <li><%= link_to "Email this user",
        :controller => "email", :action => "correspond",
        :id => @user.screen_name %></li>
      <li><%= friendship_status(@logged_in_user, @user) %>
        <% unless Friendship.exists?(@logged_in_user, @user) %>
          <br />
          <%= link_to "Request friendship with #{@user.name}",
            { :controller => "friendship", :action => "create",
              :id => @user.screen_name },
            :confirm =>
              "Send friend request to #{@user.name}?" %>
        <% end %>
      </li>
    </ul>
  </div>
<% end %>

这里,我们只在好友关系不存在时显示好友请求链接,并添加了一个 JavaScript 确认框来防止误操作。同时,我们使用 friendship_status 方法来显示当前好友关系的状态。

7.3 实现 friendship_status 方法

Friendship 辅助模块中实现 friendship_status 方法:

module FriendshipHelper
  # Return an appropriate friendship status message.
  def friendship_status(user, friend)
    friendship = Friendship.find_by_user_id_and_friend_id(user, friend)
    return "#{friend.name} is not your friend (yet)." if friendship.nil?
    case friendship.status
    when 'requested'
      "#{friend.name} would like to be your friend."
    when 'pending'
      "You have requested friendship from #{friend.name}."
    when 'accepted'
      "#{friend.name} is your friend."
    end
  end
end
7.4 配置 Profile 控制器

Profile 控制器中包含 Friendship 辅助模块:

class ProfileController < ApplicationController
  helper :avatar, :friendship
  # ... 已有代码 ...
end
7.5 实现 Friendship 控制器的 create 方法

Friendship 控制器中实现 create 方法,用于处理好友请求:

class FriendshipController < ApplicationController
  include ProfileHelper
  before_filter :protect, :setup_friends

  # Send a friend request.
  # We'd rather call this "request", but that's not allowed by Rails.
  def create
    Friendship.request(@user, @friend)
    UserMailer.deliver_friend_request(
      :user => @user,
      :friend => @friend,
      :user_url => profile_for(@user),
      :accept_url =>
        url_for(:action => "accept",
          :id => @user.screen_name),
      :decline_url => url_for(:action => "decline", :id => @user.screen_name)
    )
    flash[:notice] = "Friend request sent."
    redirect_to profile_for(@friend)
  end

  private
  def setup_friends
    @user = User.find(session[:user_id])
    @friend = User.find_by_screen_name(params[:id])
  end
end

create 方法中,我们首先调用 Friendship.request 方法更新数据库,然后使用 UserMailer 发送好友请求邮件。最后,我们显示一个提示信息并跳转到好友的资料页面。

7.6 实现 UserMailer 的 friend_request 方法

UserMailer 中实现 friend_request 方法,用于生成好友请求邮件:

class UserMailer < ActionMailer::Base
  # ... 已有代码 ...
  def friend_request(mail)
    subject 'New friend request at RailsSpace.com'
    from 'RailsSpace <do-not-reply@railsspace.com>'
    recipients mail[:friend].email
    body mail
  end
end

邮件内容如下:

Hello <%= @friend.name %>,
You have a new RailsSpace friend request from <%= @user.name %>.
View <%= @user.name %>'s profile:
<%= @user_url %>
Accept:
<%= @accept_url %>
Decline: <%= @decline_url %>
--The RailsSpace team

通过以上步骤,我们实现了 RailsSpace 中好友关系的建模、管理和好友请求功能。用户可以方便地发送和接受好友请求,从而建立自己的社交网络。

8. 好友请求功能的流程梳理

为了更清晰地理解好友请求功能的实现过程,我们可以用 mermaid 格式的流程图来展示其主要流程:

graph LR
    A[用户登录] --> B{点击好友请求链接}
    B -- 是 --> C{检查好友关系是否存在}
    C -- 否 --> D{确认发送请求}
    D -- 是 --> E[调用 Friendship.request 方法]
    E --> F[更新数据库好友关系记录]
    F --> G[调用 UserMailer 发送请求邮件]
    G --> H[显示提示信息]
    H --> I[跳转到好友资料页面]
    C -- 是 --> J[不显示请求链接]
    D -- 否 --> K[取消请求]
    B -- 否 --> L[不做操作]

这个流程图展示了从用户点击好友请求链接到最终完成请求发送的整个过程。当用户点击链接后,系统会先检查好友关系是否已经存在,如果不存在则弹出确认框,用户确认后会更新数据库并发送邮件,最后给出提示信息并跳转页面。

9. 数据库表结构总结

为了更好地理解好友关系的数据存储,我们总结一下涉及的数据库表结构:
| 表名 | 字段 | 类型 | 说明 |
| ---- | ---- | ---- | ---- |
| users | id | integer | 用户唯一标识 |
| | screen_name | string | 用户屏幕名称 |
| | email | string | 用户邮箱 |
| friendships | id | integer | 好友关系记录唯一标识 |
| | user_id | integer | 用户 ID,关联 users 表的 id |
| | friend_id | integer | 好友 ID,关联 users 表的 id |
| | status | string | 好友关系状态,取值为 ‘pending’、’requested’、’accepted’ |
| | created_at | datetime | 好友关系创建时间 |
| | accepted_at | datetime | 好友关系接受时间 |

这种表结构设计通过 friendships 表将 users 表中的用户关联起来,并且通过 status 字段来跟踪好友关系的不同状态。

10. 代码优化建议

虽然我们已经实现了好友关系的基本功能,但在实际应用中,还可以对代码进行一些优化:

10.1 错误处理

Friendship 模型的 request accept breakup 方法中,目前没有对可能出现的异常进行处理。例如,数据库操作可能会因为网络问题或数据冲突而失败。可以添加异常处理代码,确保在出现异常时能够给出合适的提示信息。示例如下:

class Friendship < ActiveRecord::Base
  # ... 已有代码 ...

  def self.request(user, friend)
    begin
      unless user == friend or Friendship.exists?(user, friend)
        transaction do
          create(:user => user, :friend => friend, :status => 'pending')
          create(:user => friend, :friend => user, :status => 'requested')
        end
      end
    rescue Exception => e
      Rails.logger.error("Friendship request failed: #{e.message}")
      # 可以在这里添加更多的错误处理逻辑,如返回错误信息给用户
    end
  end

  # 类似地对 accept 和 breakup 方法添加异常处理
end
10.2 性能优化

在查询好友关系时,频繁的数据库查询可能会影响性能。可以考虑使用缓存技术,例如 Rails 提供的缓存机制,将常用的好友关系查询结果缓存起来,减少数据库的访问次数。示例如下:

def friendship_status(user, friend)
  Rails.cache.fetch([user, friend, 'friendship_status']) do
    friendship = Friendship.find_by_user_id_and_friend_id(user, friend)
    return "#{friend.name} is not your friend (yet)." if friendship.nil?
    case friendship.status
    when 'requested'
      "#{friend.name} would like to be your friend."
    when 'pending'
      "You have requested friendship from #{friend.name}."
    when 'accepted'
      "#{friend.name} is your friend."
    end
  end
end
10.3 代码复用

Friendship 控制器和 UserMailer 中,有一些代码逻辑可以进一步封装和复用。例如,生成邮件内容的部分可以提取到一个单独的方法中,提高代码的可维护性。示例如下:

class UserMailer < ActionMailer::Base
  # ... 已有代码 ...
  def friend_request(mail)
    subject 'New friend request at RailsSpace.com'
    from 'RailsSpace <do-not-reply@railsspace.com>'
    recipients mail[:friend].email
    body generate_friend_request_body(mail)
  end

  private
  def generate_friend_request_body(mail)
    <<~HTML
      Hello #{mail[:friend].name},
      You have a new RailsSpace friend request from #{mail[:user].name}.
      View #{mail[:user].name}'s profile:
      #{mail[:user_url]}
      Accept:
      #{mail[:accept_url]}
      Decline: #{mail[:decline_url]}
      --The RailsSpace team
    HTML
  end
end
11. 总结

通过以上的实现和优化,我们成功地在 RailsSpace 中实现了好友关系的建模、管理和好友请求功能。从数据建模开始,我们通过创建 Friendship 模型和相应的数据库表结构,实现了对好友关系的有效存储和管理。然后,通过控制器和视图的配合,为用户提供了方便的好友请求界面。最后,通过测试代码确保了功能的正确性,并对代码进行了优化建议,以提高系统的性能和可维护性。

在实际应用中,还可以根据具体需求进一步扩展功能,例如添加好友分组、好友动态展示等。同时,要注意数据库的性能和数据的一致性,确保系统的稳定运行。希望本文能够帮助你理解和实现社交网络中的好友关系功能。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值