Rails [我是搬运工] ActiveSupport::CurrentAttributes provides a thread-isolated attributes singleton

rocLv · 2017年06月23日 · 最后由 wootaw 回复于 2017年06月30日 · 1844 次阅读

原文地址: #29180 by DHH

使用场景:需要 persist 一些全局数据,如 request 中内容,user 之类

Abstract super class that provides a thread-isolated attributes singleton.

Primary use case is keeping all the per-request attributes easily available to the whole system.

The following full app-like example demonstrates how to use a Current class to facilitate easy access to the global, per-request attributes without passing them deeply around everywhere:

# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :account, :user
  attribute :request_id, :user_agent, :ip_address 

  resets { Time.zone = nil }

  def user=(user)
    super
    self.account = user.account
    Time.zone = user.time_zone
  end
end

# app/controllers/concerns/authentication.rb
module Authentication
  extend ActiveSupport::Concern

  included do
    before_action :set_current_authenticated_user
  end

  private
    def set_current_authenticated_user
      Current.user = User.find(cookies.signed[:user_id])
    end
end

# app/controllers/concerns/set_current_request_details.rb
module SetCurrentRequestDetails
  extend ActiveSupport::Concern

  included do
    before_action do
      Current.request_id = request.uuid
      Current.user_agent = request.user_agent
      Current.ip_address = request.ip
    end
  end
end  

class ApplicationController < ActionController::Base
  include Authentication
  include SetCurrentRequestDetails
end

class MessagesController < ApplicationController
  def create
    Current.account.messages.create(message_params)
  end
end

class Message < ApplicationRecord
  belongs_to :creator, default: -> { Current.user }
  after_create { |message| Event.create(record: message) }
end

class Event < ApplicationRecord
  before_create do
    self.request_id = Current.request_id
    self.user_agent = Current.user_agent
    self.ip_address = Current.ip_address
  end
end

A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result. Current should only be used for a few, top-level globals, like account, user, and request details. The attributes stuck in Current should be used by more or less all actions on all requests. If you start sticking controller-specific attributes in there, you're going to create a mess.

好东西。。。能正确写出线程安全的 Current 也不容易...

ericguo 回复

我是支持那文作者观点的...不过写 Current 的人大有人在,官方提供个版本防止他们写不好出出错吧

仔细看了一遍代码,说实话,没有发现这样做有很大的好处?功能比较微妙,大多数应用里面可能都不需要这么做,或者不需要这么复杂。

沒太理解,這好像只在 controller 裡用?那不能用實例變量麼?(比如 @request_id

franklinyu 回复

这么做的目的就是可以在 model 的代码中直接获取 current 的一些信息,这样的代码不必非得写到 controller 里

wootaw 回复

但感覺有點不滿足單一功能原則?Current 的信息從邏輯上來說就是屬於 Controller 管的…… 比如這種情況,我寧願在 controller 裡寫一個 create_event 函數,分享給所有需要用到 event 的控制器……

franklinyu 回复

不要教条主义嘛,这么做只是提供一种可能,不是说所有的场景都适合这么用。

记得几年前遇到过一个问题,用这种方式觉得比较合适:当时有一个用户权限的功能在我们多个项目中用到,而且逻辑都相似,后来准备把这部分代码提取出来做成一个 gem,这个 gem 的功能只是与 model 相关,不想就为了 current 就非得侵入 controller,所以后来找到的解决方式就类似于上面的那个方案(当然他这个更完善一些)

需要 登录 后方可回复, 如果你还没有账号请 注册新账号