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

被折叠的 条评论
为什么被折叠?



