跳至内容 跳至搜索

Action Controller Base

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 操作将默认情况下渲染 app/views/posts/index.html.erb 模板,并在填充 @posts 实例变量后执行。

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

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

请求

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

完整的请求对象可以通过 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 执行的操作将在 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

如果需要根据某些条件进行重定向,请确保添加“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

常量

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

  MODULES - modules
end