26、RailsSpace 好友管理与 RESTful 博客开发指南

RailsSpace 好友管理与 RESTful 博客开发指南

1. 好友管理

在实现发送好友请求的功能后,接下来将构建好友管理的核心部分,包括接受、拒绝、取消好友请求以及删除好友关系。

1.1 has_many :through 关联

为了在用户中心展示 RailsSpace 用户的好友列表,我们希望能够遍历好友并为每个好友创建一个 HTML 表格行。以下是一个好友列表的 rhtml 示例:

<table>
<% @user.friends.each do |friend| %>
<tr>
<td><%= link_to thumbnail_tag(friend), profile_for(friend) %></td>
<td><%= link_to friend.name, profile_for(friend) %></td>
</tr>
<% end %>
</table>

同样,我们也需要为请求中的和待处理的好友创建类似的列表。

在 RailsSpace 中,之前的模型关联通常是一对一的,例如每个用户有一个 spec。但在当前情况下,一个用户可能有多个好友关系。Rails 提供了 has_many 关联来处理这种情况:

class User < ActiveRecord::Base
  has_one :spec
  has_one :faq
  has_many :friendships
  ...
end

通过 has_many :friendships ,我们可以通过 user.friendships 访问用户的好友关系数组。为了提取用户当前(已接受)的好友,我们可以遍历 user.friendships ,选择状态为 'accepted' 的好友关系,然后根据这些已接受用户的 friend_id 属性实例化一个用户数组。对于状态为 'requested' 'pending' 的用户,我们也可以进行相同的操作。

为了简化这些操作,我们可以使用 has_many :through 构造:

class User < ActiveRecord::Base
  ...
  has_many :friendships
  has_many :friends,
    :through => :friendships,
    :conditions => "status = 'accepted'"
  has_many :requested_friends,
    :through => :friendships,
    :source => :friend,
    :conditions => "status = 'requested'"
  has_many :pending_friends,
    :through => :friendships,
    :source => :friend,
    :conditions => "status = 'pending'"
  ...
end

通过在 has_many 调用中设置条件,我们可以让 Rails 根据 friendships 表中 status 列的值选择适合每种情况的好友。

为了让每个类别中的好友按合理顺序显示,我们可以为每个 has_many :through 声明添加 :order 选项:

class User < ActiveRecord::Base
  has_one :spec
  has_one :faq
  has_many :friendships
  has_many :friends,
    :through => :friendships,
    :conditions => "status = 'accepted'",
    :order => :screen_name
  has_many :requested_friends,
    :through => :friendships,
    :source => :friend,
    :conditions => "status = 'requested'",
    :order => :created_at
  has_many :pending_friends,
    :through => :friendships,
    :source => :friend,
    :conditions => "status = 'pending'",
    :order => :created_at
  ...
end
1.2 中心好友列表

接下来,我们将创建 _friends.rhtml 部分视图,这是一个包含已接受、请求中的和待处理好友行的表格,以及删除好友、接受、拒绝和取消好友请求的链接。

<table>
<tr>
<th colspan="3" align="left">
<%= pluralize(@user.friends.count, "RailsSpace friend") %>
</th>
</tr>
<% @user.friends.each do |friend| %>
<tr>
<td width="50">
<%= link_to thumbnail_tag(friend), profile_for(friend) %>
</td>
<td><%= link_to friend.name, profile_for(friend) %></td>
<td>
<% unless hide_edit_links? %>
<%= link_to "Delete",
{ :controller => "friendship", :action => "delete",
:id => friend.screen_name },
:confirm =>
"Really delete friendship with #{friend.name}?" %>
<% end %>
</td>
</tr>
<% end %>
<% unless @user.requested_friends.empty? or hide_edit_links? %>
<tr>
<th colspan="3" align="left">
<%= pluralize(@user.requested_friends.count, "requested friend") %>
</th>
</tr>
<% @user.requested_friends.each do |requester| %>
<tr>
<td><%= link_to thumbnail_tag(requester), profile_for(requester) %></td>
<td><%= link_to requester.name, profile_for(requester) %></td>
<td>
<%= link_to "Accept",
:controller => "friendship", :action => "accept",
:id => requester.screen_name %> /
<%= link_to "Decline",
{ :controller => "friendship", :action => "decline",
:id => requester.screen_name },
:confirm =>
"Really decline friendship with #{requester.name}?" %>
</td>
</tr>
<% end %>
<% end %>
<% unless @user.pending_friends.empty? or hide_edit_links? %>
<tr>
<th colspan="3" align="left">
<%= pluralize(@user.pending_friends.count, "pending friend") %>
</th>
</tr>
<% @user.pending_friends.each do |pending_friend| %>
<tr>
<td><%= link_to thumbnail_tag(pending_friend),
profile_for(pending_friend) %></td>
<td><%= link_to pending_friend.name,
profile_for(pending_friend) %></td>
<td><%= link_to "Cancel request",
{ :controller => "friendship", :action => "cancel",
:id => pending_friend.screen_name },
:confirm =>
"Cancel friendship request?" %></td>
</tr>
<% end %>
<% end %>
</table>

在列出好友之前,我们需要考虑有些用户可能没有上传头像的情况。我们可以编辑 Avatar 模型中的 thumbnail_url 方法,以便在头像不存在时返回默认缩略图:

class Avatar < ActiveRecord::Base
  ...
  def thumbnail_url
    thumb = exists? ? thumbnail_name : "default_thumbnail.png"
    "#{URL_STUB}/#{thumb}"
  end
  ...
end

我们可以将 public/images/rails.png 复制到 public/images/avatars/default_thumbnail.png 作为默认缩略图。

最后,我们需要在用户中心渲染这个部分视图:

...
My Bio:
<span class="edit_link">
<%= link_to "(edit)", :controller => "faq", :action => "edit" %>
</span>
<div id="bio" class="faq_answer">
<%= sanitize @faq.bio %>
</div>
<hr noshade />
<%= render :partial => "friendship/friends" %>
...
1.3 好友操作

与用户中心不同,个人资料列表只显示已接受的好友。为了实现这一点,我们需要编写接受、拒绝、取消和删除好友的操作。

每个操作的结构几乎相同,我们使用 setup_friends 前置过滤器来创建实例变量 @user @friend 。每个操作都会检查 @friend 是否在相关的好友列表中,如果是,则使用 Friendship 模型中的适当方法来接受或删除好友关系:

class FriendshipController < ApplicationController
  include ProfileHelper
  before_filter :protect, :setup_friends
  ...
  def accept
    if @user.requested_friends.include?(@friend)
      Friendship.accept(@user, @friend)
      flash[:notice] = "Friendship with #{@friend.screen_name} accepted!"
    else
      flash[:notice] = "No friendship request from #{@friend.screen_name}."
    end
    redirect_to hub_url
  end

  def decline
    if @user.requested_friends.include?(@friend)
      Friendship.breakup(@user, @friend)
      flash[:notice] = "Friendship with #{@friend.screen_name} declined"
    else
      flash[:notice] = "No friendship request from #{@friend.screen_name}."
    end
    redirect_to hub_url
  end

  def cancel
    if @user.pending_friends.include?(@friend)
      Friendship.breakup(@user, @friend)
      flash[:notice] = "Friendship request canceled."
    else
      flash[:notice] = "No request for friendship with #{@friend.screen_name}"
    end
    redirect_to hub_url
  end

  def delete
    if @user.friends.include?(@friend)
      Friendship.breakup(@user, @friend)
      flash[:notice] = "Friendship with #{@friend.screen_name} deleted!"
    else
      flash[:notice] = "You aren't friends with #{@friend.screen_name}"
    end
    redirect_to hub_url
  end

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

通过定义这些操作,好友请求消息中的电子邮件链接和用户中心 URL 上的链接都可以正常工作。

1.4 测试好友请求

最后,我们可以编写一个简单的测试来验证好友请求功能:

require File.dirname(__FILE__) + '/../test_helper'
require 'friendship_controller'
# Re-raise errors caught by the controller.
class FriendshipController; def rescue_action(e) raise e end; end
class FriendshipControllerTest < Test::Unit::TestCase
  include ProfileHelper
  fixtures :users, :specs

  def setup
    @controller = FriendshipController.new
    @request = ActionController::TestRequest.new
    @response = ActionController::TestResponse.new
    @user = users(:valid_user)
    @friend = users(:friend)
    # Make sure deliveries aren't actually made!
    ActionMailer::Base.delivery_method = :test
  end

  def test_create
    # Log in as user and send request.
    authorize @user
    get :create, :id => @friend.screen_name
    assert_response :redirect
    assert_redirected_to profile_for(@friend)
    assert_equal "Friend request sent.", flash[:notice]

    # Log in as friend and accept request.
    authorize @friend
    get :accept, :id => @user.screen_name
    assert_redirected_to hub_url
    assert_equal "Friendship with #{@user.screen_name} accepted!",
      flash[:notice]
  end
end

运行这个测试:

> ruby test/functional/friendship_controller_test.rb
Loaded suite test/functional/friendship_controller_test
Started
.
Finished in 0.180971 seconds.
1 tests, 5 assertions, 0 failures, 0 errors
2. RESTful 博客开发

在完成登录和认证系统后,RailsSpace 已经有了很大的发展。现在,我们将为每个用户添加一个简单的博客功能。我们将使用 REST 开发风格来构建这些博客。

2.1 REST 简介

REST(Representational State Transfer)是一种用于开发分布式、网络系统和软件应用程序的架构风格,特别是用于万维网和 Web 应用程序。REST 强调组件交互的可扩展性、接口的通用性、组件的独立部署以及中间组件的使用,以减少交互延迟、加强安全性并封装遗留系统。

在 Web 应用程序的上下文中,REST 为一种开发风格提供了理论基础,这种开发风格可以产生简洁且高度结构化的代码,同时为应用程序和客户端之间提供统一的接口。RESTful Web 应用程序通过 HTTP 协议支持的四个基本操作(POST、GET、PUT 和 DELETE)进行交互。

2.2 REST 与 CRUD

使用 REST 原则开发 Rails 应用程序意味着利用 HTTP 方法(POST、GET、PUT、DELETE)与关系数据库的传统 CRUD(Create、Read、Update、Delete)操作之间的自然对应关系。

与传统的 controller/action/id 方法不同,REST 认为只有四个操作(CRUD 操作),这些操作不是 URL 的显式部分,而是隐式在 HTTP 请求中。这对我们应用程序的结构有深远的影响。

让我们以用户规格的 Spec 控制器为例,看看使用 Rails 实现的 REST 会是什么样子。在传统的 RailsSpace 中,我们使用以下 URL 来编辑用户规格:

/spec/edit

这个 URL 实际上根据上下文执行四种不同的操作:使用 GET 请求调用该操作会返回一个用于创建或编辑规格的表单,而使用 POST 请求则会完成创建或编辑操作。

如果使用 REST 原则实现规格,RESTful URL 没有操作,但总是需要一个控制器。在我们的例子中,这将是 Specs 控制器。执行规格的基本 CRUD 操作涉及向 Specs 控制器发送适当的 HTTP 请求,并为读取、更新和删除操作提供规格 ID。

以下是 RESTful 规格的 URL 和对应的操作:
| DB 操作 | 响应器 | HTTP 方法 | URL 路径 | 辅助函数 |
| ---- | ---- | ---- | ---- | ---- |
| C(创建) | create | POST | /specs | specs_path |
| R(读取) | show | GET | /specs/1 | spec_path(1) |
| U(更新) | update | PUT | /specs/1 | spec_path(1) |
| D(删除) | destroy | DELETE | /specs/1 | spec_path(1) |
| R(列表) | index | GET | /specs | specs_path |
| R(新建) | new | GET | /specs/new | new_spec_path |
| R(编辑) | edit | GET | /specs/1;edit | edit_spec_path(1) |

为了处理这种新的路由风格,Rails 的 REST 实现添加了 map.resources 方法。对于 RESTful 规格,我们的路由文件如下:

ActionController::Routing::Routes.draw do |map|
  ...
  # Named routes.
  map.hub 'user', :controller => 'user', :action => 'index'
  map.profile 'profile/:screen_name', :controller => 'profile', :action => 'show'
  # REST resources.
  map.resources :specs
  # Install the default route as the lowest priority.
  map.connect ':controller/:action/:id'
end
2.3 URL 修饰符

在 REST 中,虽然我们可以使用 GET 请求获取显示规格的页面,但不能使用 GET 请求获取创建或编辑规格的页面。为了解决这个问题,我们可以添加修饰符。

例如,要创建一个新规格,我们可以使用以下 URL:

/specs/new

要显示一个现有规格的编辑表单,我们可以使用以下 URL:

/specs/1;edit

除了 new edit ,通常还会提供一个 index 修饰符,用于列出所有规格。

如果某些控制器需要除默认修饰符之外的其他修饰符,Rails 可以很容易地自定义。例如,我们可以为每个规格创建一个特殊的管理页面:

map.resources :specs, :member => { :admin => :get }

这样, admin_spec_path(1) 将返回 /specs/1;admin

2.4 URL 中的分号

RESTful URL 中使用分号来分隔 ID 和修饰符。虽然分号在 URL 中看起来很奇怪,但它是必要的,以避免嵌套 RESTful 资源时的歧义。

例如,如果我们使用斜杠作为分隔符,可能会导致无法区分 URL 中的单词是指控制器还是修饰符。Rails 设计者选择分号作为分隔符,以避免这种冲突。

修饰符通常是形容词,用于描述资源的某个方面。保持形容词修饰符、名词控制器和动词操作之间的区别具有很大的概念力量。

如果定义动词修饰符,通常意味着我们应该引入另一个控制器并使用 CRUD 操作。例如,如果我们想允许用户标记他们喜欢的用户的规格,我们可以定义一个 Tags 控制器,并使用 POST 请求来创建标签:

/specs/1/tags
2.5 响应不同格式和免费 API

REST 的一个方面是根据请求期望的格式,用不同的格式响应不同的请求。在 Rails 中,我们可以通过简单地在 URL 中添加文件扩展名来实现这一点。例如,使用以下 URL:

/specs/1.xml

将返回 XML 格式的数据。

通过观察应用程序暴露给用户的部分(即 URL),我们已经可以很好地了解应用程序的行为。这实际上就是应用程序编程接口(API)的定义。

RailsSpace 好友管理与 RESTful 博客开发指南

3. RESTful 博客的进一步实现

在了解了 REST 的基本概念和如何应用于用户规格之后,接下来我们将深入探讨如何将 REST 应用到博客开发中。

3.1 博客资源的 RESTful 设计

对于博客,我们同样遵循 REST 的原则,定义其 CRUD 操作对应的 URL 和 HTTP 方法。假设我们有一个 Blogs 控制器,以下是其可能的 RESTful 资源设计:
| DB 操作 | 响应器 | HTTP 方法 | URL 路径 | 辅助函数 |
| ---- | ---- | ---- | ---- | ---- |
| C(创建) | create | POST | /blogs | blogs_path |
| R(读取) | show | GET | /blogs/1 | blog_path(1) |
| U(更新) | update | PUT | /blogs/1 | blog_path(1) |
| D(删除) | destroy | DELETE | /blogs/1 | blog_path(1) |
| R(列表) | index | GET | /blogs | blogs_path |
| R(新建) | new | GET | /blogs/new | new_blog_path |
| R(编辑) | edit | GET | /blogs/1;edit | edit_blog_path(1) |

相应地,我们需要在路由文件中添加对博客资源的支持:

ActionController::Routing::Routes.draw do |map|
  ...
  # REST resources.
  map.resources :blogs
  ...
end
3.2 博客文章的嵌套资源

博客通常包含多篇文章,因此我们可以将文章设计为博客的嵌套资源。假设我们有一个 Posts 控制器,以下是博客文章的 RESTful 资源设计:
| DB 操作 | 响应器 | HTTP 方法 | URL 路径 | 辅助函数 |
| ---- | ---- | ---- | ---- | ---- |
| C(创建) | create | POST | /blogs/1/posts | blog_posts_path(1) |
| R(读取) | show | GET | /blogs/1/posts/2 | blog_post_path(1, 2) |
| U(更新) | update | PUT | /blogs/1/posts/2 | blog_post_path(1, 2) |
| D(删除) | destroy | DELETE | /blogs/1/posts/2 | blog_post_path(1, 2) |
| R(列表) | index | GET | /blogs/1/posts | blog_posts_path(1) |
| R(新建) | new | GET | /blogs/1/posts/new | new_blog_post_path(1) |
| R(编辑) | edit | GET | /blogs/1/posts/2;edit | edit_blog_post_path(1, 2) |

在路由文件中添加对博客文章嵌套资源的支持:

ActionController::Routing::Routes.draw do |map|
  ...
  map.resources :blogs do
    map.resources :posts
  end
  ...
end
3.3 实现博客和文章的控制器

接下来,我们需要实现 BlogsController PostsController ,以处理相应的 RESTful 请求。以下是一个简单的 BlogsController 示例:

class BlogsController < ApplicationController
  before_filter :set_blog, only: [:show, :edit, :update, :destroy]

  def index
    @blogs = Blog.all
  end

  def show
  end

  def new
    @blog = Blog.new
  end

  def create
    @blog = Blog.new(blog_params)
    if @blog.save
      redirect_to blog_path(@blog), notice: 'Blog created successfully.'
    else
      render :new
    end
  end

  def edit
  end

  def update
    if @blog.update(blog_params)
      redirect_to blog_path(@blog), notice: 'Blog updated successfully.'
    else
      render :edit
    end
  end

  def destroy
    @blog.destroy
    redirect_to blogs_path, notice: 'Blog deleted successfully.'
  end

  private

  def set_blog
    @blog = Blog.find(params[:id])
  end

  def blog_params
    params.require(:blog).permit(:title, :description)
  end
end

PostsController 的实现与 BlogsController 类似,只是需要处理嵌套资源的逻辑。以下是一个简单的 PostsController 示例:

class PostsController < ApplicationController
  before_filter :set_blog
  before_filter :set_post, only: [:show, :edit, :update, :destroy]

  def index
    @posts = @blog.posts
  end

  def show
  end

  def new
    @post = @blog.posts.build
  end

  def create
    @post = @blog.posts.build(post_params)
    if @post.save
      redirect_to blog_post_path(@blog, @post), notice: 'Post created successfully.'
    else
      render :new
    end
  end

  def edit
  end

  def update
    if @post.update(post_params)
      redirect_to blog_post_path(@blog, @post), notice: 'Post updated successfully.'
    else
      render :edit
    end
  end

  def destroy
    @post.destroy
    redirect_to blog_posts_path(@blog), notice: 'Post deleted successfully.'
  end

  private

  def set_blog
    @blog = Blog.find(params[:blog_id])
  end

  def set_post
    @post = @blog.posts.find(params[:id])
  end

  def post_params
    params.require(:post).permit(:title, :content)
  end
end
4. 总结与展望

通过以上的步骤,我们完成了 RailsSpace 中好友管理和 RESTful 博客的开发。好友管理部分实现了好友请求的发送、接受、拒绝、取消和删除等功能,并且通过 has_many :through 关联简化了好友列表的管理。RESTful 博客开发部分,我们深入了解了 REST 的核心原则,并将其应用到博客和文章的设计中,实现了博客和文章的 CRUD 操作。

以下是整个开发过程的 mermaid 流程图:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始]):::startend --> B(好友管理):::process
    B --> B1(has_many :through 关联):::process
    B --> B2(中心好友列表):::process
    B --> B3(好友操作):::process
    B --> B4(测试好友请求):::process
    A --> C(RESTful 博客开发):::process
    C --> C1(REST 简介):::process
    C --> C2(REST 与 CRUD):::process
    C --> C3(URL 修饰符):::process
    C --> C4(URL 中的分号):::process
    C --> C5(响应不同格式和免费 API):::process
    C --> C6(博客资源的 RESTful 设计):::process
    C --> C7(博客文章的嵌套资源):::process
    C --> C8(实现博客和文章的控制器):::process
    B4 --> D([结束]):::startend
    C8 --> D

展望未来,我们可以进一步扩展这些功能。例如,在好友管理方面,可以添加好友分组、好友动态等功能;在博客开发方面,可以添加评论、点赞、分享等社交功能,以及支持更多的文章格式和媒体类型。通过不断地优化和扩展,RailsSpace 将成为一个功能更加丰富、用户体验更好的社交平台。

同时,REST 的应用不仅限于博客和好友管理,我们可以将其应用到更多的功能模块中,进一步提高代码的可维护性和可扩展性,为用户提供更加稳定和高效的服务。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值