跳至内容 跳至搜索

Action Controller 基础

Action Controller 是 Rails 中 Web 请求的核心。它们由一个或多个在请求时执行的操作组成,然后要么呈现模板,要么重定向到其他操作。操作定义为控制器上的公共方法,该方法将通过 Rails 路由自动对 Web 服务器进行访问。

默认情况下,Rails 应用程序中的 ApplicationController 才继承自 ActionController::Base。所有其他控制器继承自 ApplicationController。这为您提供了一个类来配置诸如请求伪造保护和敏感请求参数过滤等内容。

示例控制器如下所示

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end

  def create
    @post = Post.create params[:post]
    redirect_to posts_path
  end
end

在执行操作中的代码后,操作默认会在与控制器和操作名称对应的 app/views 目录中呈现一个模板。例如,PostsController 的 index 操作会在填充 @posts 实例变量后默认呈现模板 app/views/posts/index.html.erb

与 index 不同,create 操作不会呈现模板。在执行其主要目的(创建新帖子)后,它会启动重定向。此重定向通过返回一个外部 302 已移动 HTTP 响应来实现,该响应将用户带到 index 操作。

这两种方法代表了 Action Controller 中使用的两种基本操作原型:获取并显示以及执行并重定向。大多数操作都是这些主题的变体。

请求

对于每个请求,路由器确定 controlleraction 键的值。这些值确定调用哪个控制器和动作。通过访问器方法,动作可以使用剩余的请求参数、会话(如果可用)以及带有所有 HTTP 标头的完整请求。然后执行动作。

可以通过请求访问器使用完整的请求对象,它主要用于查询 HTTP 标头

def server_ip
  location = request.env["REMOTE_ADDR"]
  render plain: "This server hosted at #{location}"
end

参数

所有请求参数,无论它们来自 URL 中的查询字符串还是通过 POST 请求提交的表单数据,都可以通过返回哈希的 params 方法获得。例如,通过 /posts?category=All&limit=5 执行的动作将在 params 中包含 { "category" => "All", "limit" => "5" }

还可以通过使用方括号指定键来构造多维参数哈希,例如

<input type="text" name="post[name]" value="david">
<input type="text" name="post[address]" value="hyacintvej">

包含这些输入的表单发来的请求将包括 { "post" => { "name" => "david", "address" => "hyacintvej" } }。如果地址输入被命名为 post[address][street],则 params 将包括 { "post" => { "address" => { "street" => "hyacintvej" } } }。嵌套的深度没有限制。

会话

会话允许你在请求之间存储对象。这对于尚未准备好持久化的对象非常有用,例如在多页流程中构建的 Signup 对象,或不太经常更改且始终需要的对象,例如需要登录的系统的 User 对象。但是,会话不应作为对象的缓存,因为对象很可能在不知情的情况下被更改。保持所有内容同步通常需要做大量工作,而这是数据库已经擅长的。

你可以使用访问哈希的 session 方法将对象放入会话中

session[:person] = Person.authenticate(user_name, password)

你可以通过相同的哈希再次检索它

"Hello #{session[:person]}"

要从会话中移除对象,你可以将单个键分配给 nil

# removes :person from session
session[:person] = nil

或者,你可以使用 reset_session 删除整个会话。

默认情况下,会话存储在加密的浏览器 cookie 中(参见 ActionDispatch::Session::CookieStore)。因此,用户将无法读取或编辑会话数据。但是,用户可以在 cookie 过期后仍保留一份 cookie 的副本,因此你应该避免在基于 cookie 的会话中存储敏感信息。

响应

每个操作都会产生一个响应,其中包含要发送到用户浏览器的标头和文档。实际的响应对象通过使用渲染和重定向自动生成,无需用户干预。

渲染

Action Controller 使用五种渲染方法之一向用户发送内容。最通用、最常见的方法是渲染模板。Action Pack 中包含 Action View,它支持渲染 ERB 模板。它会自动配置。控制器通过分配实例变量将对象传递给视图

def show
  @post = Post.find(params[:id])
end

然后这些变量会自动提供给视图

Title: <%= @post.title %>

你不必依赖于自动渲染。例如,可能导致渲染不同模板的操作将使用手动渲染方法

def search
  @results = Search.find(params[:query])
  case @results.count
    when 0 then render action: "no_results"
    when 1 then render action: "show"
    when 2..10 then render action: "show_many"
  end
end

ActionView::Base 中阅读有关编写 ERB 和 Builder 模板的更多信息。

重定向

重定向用于从一个操作移动到另一个操作。例如,在将博客条目存储到数据库的 create 操作后,我们可能希望向用户显示新条目。因为我们遵循良好的 DRY 原则(不要重复自己),所以我们将重用(并重定向到)我们假设已经创建的 show 操作。代码可能如下所示

def create
  @entry = Entry.new(params[:entry])
  if @entry.save
    # The entry was saved correctly, redirect to show
    redirect_to action: 'show', id: @entry.id
  else
    # things didn't go so well, do something else
  end
end

在这种情况下,在将新条目保存到数据库后,用户将被重定向到 show 方法,然后执行该方法。请注意,这是一个外部 HTTP 级重定向,它将导致浏览器发出第二个请求(对 show 操作的 GET),而不是在一次请求中同时调用“create”和“show”的内部重新路由。

ActionController::Redirecting 中了解有关 redirect_to 的更多信息以及你有哪些选项。

调用多个重定向或渲染

一个操作可能只包含一个渲染或一个重定向。再次尝试执行任一操作都将导致 DoubleRenderError

def do_something
  redirect_to action: "elsewhere"
  render action: "overthere" # raises DoubleRenderError
end

如果你需要根据某些条件进行重定向,那么务必添加“and return”来停止执行。

def do_something
  redirect_to(action: "elsewhere") and return if monkeys.nil?
  render action: "overthere" # won't be called if monkeys is nil
end
方法
W

常量

MODULES = [ AbstractController::Rendering, AbstractController::Translation, AbstractController::AssetPaths, Helpers, UrlFor, Redirecting, ActionView::Layouts, Rendering, Renderers::All, ConditionalGet, EtagWithTemplateDigest, EtagWithFlash, Caching, MimeResponds, ImplicitRender, StrongParameters, ParameterEncoding, Cookies, Flash, FormBuilder, RequestForgeryProtection, ContentSecurityPolicy, PermissionsPolicy, Streaming, DataStreaming, HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods, HttpAuthentication::Token::ControllerMethods, DefaultHeaders, Logging, # Before 回调也应尽早执行,因此 # 也将它们包含在底部。 AbstractController::Callbacks, # 将 rescue 附加到底部以尽可能多地进行包装。 Rescue, # 在底部添加仪器挂钩,以确保它们正确地对 # 所有方法进行仪器化。 Instrumentation, # Params 包装器应在仪器化之前,以便 # 在日志中正确显示它们 ParamsWrapper ]
 
PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + %i( @_params @_response @_request @_config @_url_options @_action_has_layout @_view_context_class @_view_renderer @_lookup_context @_routes @_view_runtime @_db_runtime @_helper_proxy )
 

定义一些不应传播到视图的内部变量。

类公共方法

without_modules(*modules)

快捷帮助器,返回 ActionController::Base 中包含的所有模块,但返回作为参数传递的模块除外

class MyBaseController < ActionController::Metal
  ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
    include left
  end
end

这可以更好地控制要排除的内容,并且可以更轻松地创建基本的控制器类,而无需手动列出所需的模块。

# File actionpack/lib/action_controller/base.rb, line 184
def self.without_modules(*modules)
  modules = modules.map do |m|
    m.is_a?(Symbol) ? ActionController.const_get(m) : m
  end

  MODULES - modules
end