Rails 5,动作电缆和 current_user

Posted

技术标签:

【中文标题】Rails 5,动作电缆和 current_user【英文标题】:Rails 5, action cable and current_user 【发布时间】:2017-05-27 23:51:05 【问题描述】:

所以我有这个用例,我在控制器(使用 ApplicationController.renderer)中呈现一条消息,然后将其广播给几个用户。广播也在同一控制器内部执行。这两个动作都是在对某个对象执行更新时触发的。

问题是,我需要访问渲染视图中的 current_user 对象,当然,我不能将当前用户作为局部变量渲染它,因为这样消息将与广播消息的用户一起发送而不是看到该视图的最终用户。

因此,在阅读了几篇博文和 Rails 文档后,我将使用 cookie 的身份验证设置为由 action cable 支持。

我的问题是:如何在渲染视图中访问最终用户的对象 (current_user)?

目前,我的连接类看起来像这样。但是,如何使用此变量 (logged_user) 呈现该视图?

module ApplicationCable
  class Connection < ActionCable::Connection::Base
   identified_by :logged_user

   def connect
     self.logged_user = User.find_by(id: cookies.signed[:user_id])
   end
 end
end

我的控制器如下所示:

(...)

 def update 
   if @poll.update(poll_params)
     broadcast_message(render_message(@poll), @poll.id, @poll.room.id)
     (...)
   end
 end

 def broadcast_message(poll = , poll_id, room_id)
   ActionCable.server.broadcast 'room_channel', body: poll, id: poll_id, room_id: room_id
 end

 def render_message(poll).
   if poll.show_at.to_time <= Time.now
     ApplicationController.renderer.render(
       partial: 'rooms/individual_student_view_poll',
       locals: 
         poll: poll,
         room: @room
       )
   end
 end

(....)

所以基本上,我的最终目标是在消息被广播给它之后访问logged_user对象。

谢谢

【问题讨论】:

我也遇到了同样的问题,你在此期间发现了吗? 你可以渲染一个返回一些 javascript 的部分,它会从客户端触发一个新的请求来获取任何需要渲染的东西,这样这个请求就会被设置为适当的上下文。但是,假设您有 100 个用户订阅了该频道,那么他们每个人都会触发一个请求来获取该新内容。 【参考方案1】:

类似这样的:

ApplicationController.renderer.render(
  partial: 'rooms/individual_student_view_poll',
  locals: 
    poll: poll,
    room: @room
  ,
  assigns: 
    logged_user: logged_user
  
)

将在您的模板中提供,如下所示:

<% if defined? @logged_user %>
...
<% end %>

【讨论】:

好的,但是如何从控制器访问 var logged_user? 通过使用分配,logged_user: ... 变为 @logged_user。您的频道继承了 ApplicationCable::Channel,因此您可以从您的频道访问 self.logged_user 并通过“assigns”传输到您的视图 是的,但正如我之前所说,我是从控制器而不是从通道渲染的。我应该在频道上做吗?如果是,并且假设 render_message 方法驻留在通道中,我如何在控制器中访问它? 如果你想从控制器向特定用户广播,你可以简单地使用一个频道名称,例如结合其用户名。或者您可以将此代码集成到用户模型中。这取决于您的项目结构。 我要广播给所有用户。【参考方案2】:

我不得不解决同样的问题。我最终要做的是创建一个从ApplicationController 继承的模板渲染器,我可以将其配置为仅用于渲染模板,并在渲染之前分配current_user。看起来是这样的:

class TemplateRenderer < ApplicationController
  def self.with_current_user(user)
    @current_user = user
    self
  end

  def self.current_user
    @current_user
  end

  def current_user
    self.class.current_user
  end
  helper_method :current_user
end

有了这个,你可以简单地调用:

TemplateRenderer.with_current_user(logged_user).render(...)

现在在您的部分调用current_user 将访问定义在TemplateRenderer 上的辅助方法,该方法又将引用with_current_user 设置的类实例变量。

您可能已经注意到,如果您在渲染调用中使用 assigns 选项,则可以通过直接引用分配的实例 var (@current_user) 来访问该变量,但 不是 通过调用current_user 助手,即使该助手设置为返回@current_user 实例变量。在调用 render 之前有一个特殊的控制器/渲染器在类级别覆盖 current_user 可以解决这个问题。

为了方便起见,我让 with_current_user 返回类实例本身,以便它可以轻松地与 render 链接。

我确实为这门课的命名和位置而苦恼。 TemplateRenderer 描述了它的预期用途,但它继承自 ApplicationController,这将建议一个带有 Controller 后缀的名称。我现在选择在我的channels 目录中找到它并去掉后缀,因为它的唯一目的(在我的例子中)是为频道渲染模板。但我可以很容易地看到将它放在带有 Controller 后缀的控制器目录中的参数。

编辑:虽然上述答案可行,但它存在微妙的危险。如果您在第一次呼叫后再次呼叫TemplateRenderer.render,则仍会设置当前用户,这可能导致用户看到其他用户的私人内容。要解决此问题,您可以执行以下操作:

class TemplateRenderer < ApplicationController
  def self.render_for_user(user, *args)
    @current_user = user
    res = render(*args)
    @current_user = nil
    res
  end

  def self.current_user
    @current_user
  end

  def current_user
    self.class.current_user
  end
  helper_method :current_user
end

现在,打电话:

TemplateRenderer.render_for_user(logged_user, partial: "my_partial", **other_opts)

...将以current_user 为指定用户呈现,但在呈现后将current_user 设置为nil。

【讨论】:

以上是关于Rails 5,动作电缆和 current_user的主要内容,如果未能解决你的问题,请参考以下文章

如何关闭动作电缆中的连接?

如何在 ActionCable rails-5-api 应用程序中获取 current_user?

让动作电缆在 Sidekiq 作业中广播

检测到动作电缆的连接状态

如何仅更新 rails 6 中 current_user 的嵌套表单属性?

Rails - 将current_user传递给SQL查询