几周前,我写了新添加的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
这是常见做法,当成功处理 create、update 和 destroy 操作时,所有控制器都会向用户显示一个 flash 消息。我们可以轻松地从我们的控制器中删除这些 flash 消息,然后使用 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
然后出现了第一个问题:如果我不想在具体情况下添加 flash 消息,该怎么办?这可以使用选项解决,因为发送到 respond_with 的所有选项都发送到响应器,我们也可以利用它
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
,它将发送到响应器
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
同之前一样,你可以利用一些选项来自定义默认分页行为。
以上所有示例都包含在模块中,这意味着我们的实际响应器尚未创建
class MyResponder < ActionController::Responder
include CachedResponder
include FlashResponder
include PaginatedResponder
end
要激活它,我们只需在应用程序控制器中覆盖响应器方法
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 轻松精简代码。你呢?是否已经想到一个有趣的用例?