Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails API: ActiveSupport::CurrentAttributes(翻訳)

概要

MITライセンスに基づいて翻訳・公開いたします。

原文にあるABC順メソッドリストは省略しました。

Rails API: ActiveSupport::CurrentAttributes(翻訳)

ActiveSupport::CurrentAttributesは、スレッド分離の属性シングルトンを提供する抽象スーパークラスであり、個別のリクエストの直前と直後に自動的にリセットされます。これにより、リクエストごとにあらゆる属性をシステム全体で手軽に利用可能な状態で維持できるようになります。

以下の完全なアプリに似たコード例では、Currentクラスを深い場所で受け渡したりせずに、グローバルなリクエストごとにアクセス可能な形で手軽に利用する方法が示されています。

# 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 :authenticate
  end

  private
    def authenticate
      if authenticated_user = User.find_by(id: cookies.encrypted[:user_id])
        Current.user = authenticated_user
      else
        redirect_to new_session_url
      end
    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

注意: Currentのようなグローバルなシングルトンを使いすぎると、結果としてモデルが複雑になってしまいがちです。Currentを使うときは、アカウントやユーザーやリクエストの詳細情報といったトップレベルのグローバル情報を少数にとどめて利用する必要があります。Currentに固定する情報は、全リクエストのほぼすべてのアクションで使われるものだけにしておく必要があります。特定のコントローラでしか使わない属性をCurrentに押し込めるようになると、いずれ混乱が生じます。

🔗 includeされるモジュール

🔗 属性

[RW]
attributes

🔗 publicクラスメソッド

🔗 after_reset(*methods, &block)

resetsのエイリアス。

🔗 attribute(*names)

クラスとインスタンスの両方のアクセサメソッドが与えられる属性を1つ以上宣言します。

🔗 before_reset(*methods, &block)

指定したこのコールバックを、インスタンスでresetが呼び出される直前に呼び出します。このメソッドは、現在の値に依存している外部のコラボレーター(Time.zoneなど)をリセットするのに使います。

🔗 instance()

このスレッド内で、このクラスのシングルトンインスタンスを返します。インスタンスが存在しない場合は作成されます。

🔗 new()

🔗 resets(*methods, &block)

インスタンスでresetが呼び出された後に、このコールバックを呼び出します。Time.zoneなどの外部コラボレーターをリセットするのに使います。

after_resetエイリアスもあります。

🔗 publicインスタンスメソッド

🔗 reset

すべての属性をリセットします。リクエストごとのシングルトンとして利用する場合は、アクションの直前と直後にこのメソッドを呼び出す必要があります。

🔗 set(set_attributes)

渡したブロック内にある1つ以上の属性を公開します。ブロックが終了すると、元の古い値が返されます。以下は、リクエストサイクルの外でCurrentの属性を設定しなければならなくなった場合の一般的な利用例です。

class Chat::PublicationJob < ApplicationJob
  def perform(attributes, room_number, creator)
    Current.set(person: creator) do
      Chat::Publisher.publish(attributes: attributes, room_number: room_number)
    end
  end
end

関連記事

Railsの`CurrentAttributes`は有害である(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。