几周前,我写了一篇关于新添加的 ActionController::Responder 的文章,它只需在一个地方就能总结出您特定格式的应用程序行为。例如,默认的 HTML 行为是这样写的:
class ActionController::Responder
def to_html
if get?
render
elsif has_errors?
render :action => (post? ? :new : :edit)
else
redirect_to resource
end
end
end
以下是这个新层可以提供的灵活性的三个例子。
Responder 的一个简单但功能强大的用途是轻松地为您的所有资源应用 HTTP 缓存。为简单起见,我们只缓存那些不处理集合的 GET 请求。
module CachedResponder
def to_html
if get? && resource.respond_to?(:updated_at)
return if controller.fresh_when(:last_modified => resource.updated_at.utc)
end
super
end
end
一个常见的做法是,当创建、更新和销毁操作成功处理时,都会向用户显示一个闪存消息。我们可以很容易地将闪存消息从控制器中移除,让 Responder 借助 I18n 框架来处理它们。实现起来非常直接。
module FlashResponder
# If it's not a get request and the object has no errors, set the flash message
# according to the current action. If the controller is users/pictures, the
# flash message lookup for create is:
#
# flash.users.pictures.create
# flash.actions.create
#
def to_html
unless get? || has_errors?
namespace = controller.controller_path.split('/')
namespace << controller.action_name
flash[:success] = I18n.t(namespace.join("."), :scope => :flash,
:default => "actions.#{controller.action_name}", :resource => resource.class.human_name)
end
super
end
end
这时就会出现第一个问题:如果我不想在特定情况下添加闪存消息怎么办?这可以通过选项来解决,因为传递给 respond_with 的所有选项都会传递给 responder,我们也可以利用这一点。
class MyResponder < ActionController::Responder
def to_html
unless get? || has_errors? || options.delete(:flash) == false
namespace = controller.controller_path.split('/')
namespace << controller.action_name
flash[:success] = I18n.t(namespace.join("."), :scope => :flash,
:default => "actions.#{controller.action_name}", :resource => resource.class.human_name)
end
super
end
end
我们可以这样调用它:
class PostsController < ApplicationController
def create
@post = Post.create(params[:post])
respond_with(@post, :flash => false)
end
end
有些人会从头开始编写分页功能,有些人会在某个时候添加。尽管如此,分页更像是一种规则而不是例外。Rails 3 能处理这个问题吗?首先,让我们用 respond_with 来看看一个 index 操作。
class PostsController < ApplicationController
def index
@posts = Post.all
respond_with(@posts)
end
end
现在,当我们调用 Post.all 时,它返回一个 Post 集合数组,所以分页应该在检索集合之前完成。感谢 Emilio 以及 他集成 ActiveRelation 与 ActiveRecord 的工作,Post.all 将返回一个 ActiveRecord::Relation,它将被发送给 responder。
module PaginatedResponder
# Receives a relation and sets the pagination scope in the collection
# instance variable. For example, in PostsController it would
# set the @posts variable with Post.all.paginate(params[:page]).
def to_html
if get? && resource.is_a?(ActiveRecord::Relation)
paginated = resource.paginate(controller.params[:page])
controller.instance_variable_set("@#{controller.controller_name}", paginated)
end
super
end
end
然而,上面的代码肯定有问题。设置分页范围似乎更像是控制器的职责。所以我们可以这样重写:
module PaginatedResponder
def to_html
if get? && resource.is_a?(ActiveRecord::Relation)
controller.paginated_scope(resource)
end
super
end
end
class ApplicationController < ActionController::Base
def paginated_scope(relation)
instance_variable_set "@#{controller_name}", relation.paginate(params[:page])
end
hide_action :paginated_scope
end
和以前一样,您可以使用一些选项来定制默认的分页行为。
上面所有的例子都包含在模块中,这意味着我们的实际 responder 还没有被创建。
class MyResponder < ActionController::Responder
include CachedResponder
include FlashResponder
include PaginatedResponder
end
要激活它,我们只需要在我们的应用程序控制器中重写 responder 方法。
class ApplicationController < ActionController::Base
def paginated_scope(relation)
instance_variable_set "@#{controller_name}", relation.paginate(params[:page])
end
hide_action :paginated_scope
protected
def responder
MyResponder
end
end
尽管这些例子很简单,但它们展示了如何使用 Responder 轻松地使您的代码更加 DRY。您呢?是否已经想到了一个有趣的用例?