如何为rails中的单个请求跨模型设置变量?

agm*_*min 2 ruby api session ruby-on-rails ruby-on-rails-3

场景:我需要让模型访问存储在会话中的 API 令牌。

背景:我有一个 API 驱动的 Rails 3 应用程序,利用 DataMapper(DM) 和 DM 适配器与 API 进行交互。每个 DM 模型都有一个相应的 REST-ish API 端点,就像使用 Rails 脚手架所获得的那样。API 请求时需要各种标头,包括 API 令牌、密钥、ID 等。标头与请求的数据无关,它们仅用于授权和跟踪目的。许多这样的令牌存储在会话中。我想要一种干净的方法来使这些 API 标头在请求期间可用于任何模型。

可能的解决方案:

1. 将会话变量从控制器传递到模型

显而易见的答案是将哈希或其他对象中的令牌从控制器传递到模型。控制器操作可能具有以下内容:@user = User.find(params[:id], api_headers)

问题是需要重写任何模型方法来接受附加api_headers对象。不包括 Rails 和 DataMapper 定义的方法,应用程序模型中已经定义了数百种需要重写的方法。因此,我排除了重写的可能性,而且这似乎也不是一个好的解决方案,因为它需要覆盖大量荒谬的 DM 方法,如User#find上面的示例。

2.一些元编程技巧

我可以捕获ArgumentErrorDM 基类上的任何 ' 并检查最后一个参数是否是对象api_headers,然后将值设置为实例变量并调用请求的方法。这个思维练习已经让我在处理可选参数等方面感到畏缩。如果给予足够长的时间,我可能会创建一个功能性的弗兰肯斯坦,它应该让我被解雇,但可能不会。

3.使用单例(目前首选方案)

在应用程序控制器中设置 abefore_filter将会话存储的 API 标头转储到单例ApiHeaders对象中。然后,任何发出 API 请求的模型都可以获取具有所需 API 标头的单例。

after_filter应用程序控制器上的附加* 会在请求结束时将所有属性设置为单例,以防止请求之间泄漏标头nilApiHeaders

这是目前我的首选解决方案,但我不喜欢 API 标头值在after_filter未调用的情况下可能会转移到其他请求中。我不知道在哪些情况下可能会发生这种情况(也许是在应用程序错误中?),这引起了担忧。我所知道的是,这些价值观并不一定会随着请求而消失。

4. 自定义代码

放弃对 DataMapper 和自定义 API 适配器的支持,并手动进行所有 API 调用,传递所有必需的 API 标头。除了我没有时间进行这种级别的重写之外,如果您必须投入大量资源来支持自定义身份验证方案,为什么还要使用框架呢?

概括

将这些讨厌的 API 令牌从会话获取到应用程序内部的最干净方法是什么?这些令牌可以随每个 API 请求一起发送?我希望有一个比上面列出的更好的解决方案。

* 别名为after_action

And*_*ing 5

我使用request_store gem设置当前用户和用户模型上的请求信息,它只是线程本地存储上的一个小垫片,并进行了一些清理。

这使得可以通过 User 类从我的任何模型中获取信息。我有User.currentUser.request并且User.location可以在任何需要的地方使用。

您的控制器只需进行设置User.current,并User.request在对用户进行身份验证后即可。

用户模型示例:

# models/user.rb
require 'request_store'

class User

  def self.current
    RequestStore.store[:current_user]
  end

  def self.current=(user)
    RequestStore.store[:current_user] = user
  end

  def self.request
    RequestStore.store[:current_request]
  end

  def self.request=(request)
    # stash the request so things like IP address and GEO-IP based location is available to other models
    RequestStore.store[:current_request] = request
  end

  def self.location
    # resolve the location just once per request
    RequestStore.store[:current_location] ||= self.request.try(:location)
  end
end
Run Code Online (Sandbox Code Playgroud)