跳至内容 跳至搜索

Action Controller Base

Action Controller 是 Rails 中 Web 请求的核心。它们由一个或多个在请求时执行的 action 组成,然后要么渲染一个模板,要么重定向到另一个 action。Action 被定义为控制器中的一个公共方法,它将通过 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

默认情况下,Action 会在执行完 action 中的代码后,在 app/views 目录中渲染一个与控制器和 action 名称对应的模板。例如,PostsController 的 index action 默认会渲染 app/views/posts/index.html.erb 模板,并在填充 @posts 实例变量后进行渲染。

与 index 不同,create action 不会渲染模板。在完成其主要目的(创建新帖子)后,它会进行重定向。这种重定向通过返回一个外部 302 Moved HTTP 响应来实现,该响应将用户带到 index action。

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

请求

对于每个请求,路由器会确定 controlleraction 键的值。这些键决定了调用哪个控制器和 action。其余的请求参数、会话(如果可用)以及包含所有 HTTP 标头的完整请求都通过访问器方法提供给 action。然后执行 action。

完整的请求对象可通过 request 访问器获得,主要用于查询 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 执行的 action 将在 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" } }。如果 address 输入的名称是 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 的会话中存储敏感信息。

响应

每个 action 都会产生一个响应,其中包含要发送到用户浏览器的标头和文档。实际的响应对象是通过使用 render 和 redirect 自动生成的,不需要用户干预。

渲染

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

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

这些对象随后会自动在视图中可用

Title: <%= @post.title %>

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

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

有关编写 ERB 和 Builder 模板的更多信息,请参阅 ActionView::Base

重定向

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

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 级别的重定向,它将导致浏览器发出第二个请求(一个 GET 请求到 show action),而不是在一个请求中同时调用“create”和“show”的内部重新路由。

有关 redirect_to 及其选项的更多信息,请参阅 ActionController::Redirecting

调用多个重定向或渲染

一个 action 只能执行一次渲染或一次重定向。再次尝试这样做将导致 DoubleRenderError

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

如果您需要在某个条件下重定向,请确保添加“return”来停止执行。

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

常量

模块 = [ 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, RateLimiting, AllowBrowser, Streaming, DataStreaming, HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods, HttpAuthentication::Token::ControllerMethods, DefaultHeaders, Logging, AbstractController::Callbacks, Rescue, Instrumentation, 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 @_marked_for_same_origin_verification @_rendered_format )
 

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

类公共方法

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 223
def self.without_modules(*modules)
  modules = modules.map do |m|
    m.is_a?(Symbol) ? ActionController.const_get(m) : m
  end

  MODULES - modules
end