【问题标题】:How to set variables across models for a single request in rails?如何为rails中的单个请求跨模型设置变量?
【发布时间】:2018-12-22 03:40:18
【问题描述】:

场景:我需要让模型访问存储在会话中的 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。一些元编程技巧

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

3。使用单例(当前首选的解决方案)

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

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

这是我目前首选的解决方案,但我不喜欢如果 after_filter 没有被调用,API 标头值可能会转移到其他请求中。我不知道在哪些情况下可能会发生这种情况(可能是应用程序错误?)这引起了人们的关注。我所知道的是值不一定会随着请求而消失。

4。自定义代码

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

总结

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

* after_action 的别名

【问题讨论】:

  • 我使用过很多 API。我从未将我的 api 令牌传递给模型。您做出该设计决定的原因是什么?
  • 是的,请解释一下这个决定。我认为控制器会根据标题做出逻辑决策,而不是 User 模型。
  • 这是一个 REST API,每个模型都由一个端点支持。所以User.find(:id) 调用/api/users/:id。每个 DM 模型都通过一个适配器,该适配器调用 API 并将 json 响应转换为模型的实例。适配器是在应用程序级别配置的,但所需的大多数 API 标头都是特定于登录用户的。

标签: ruby-on-rails ruby ruby-on-rails-3 api session


【解决方案1】:

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

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

一旦对用户进行身份验证,您的控制器只需设置 User.currentUser.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

【讨论】:

  • 啊,很好,我更喜欢中间件清除call 上的RequestStore,而不是控制器过滤器重置值。我会在今天或明天将其插入,如果一切顺利,我会接受。谢谢!
【解决方案2】:

使用 Thread.current,它从请求传递到模型(注意,如果在您的请求中使用子线程,这会中断)。您可以将要共享的属性存储在 cattr_accessor 或 rails 缓存中:

在 cattr_accessor 中

class YourClass
    cattr_accessor :my_var_hash

...
# and in your controller

# set the var
YourClass.my_var_hash = {} if YourClass.my_var_hash.nil?
YourClass.my_var_hash[Thread.current.object_id] = {}
YourClass.my_var_hash[Thread.current.object_id][your_var] = 100

... and in your model

lvalue = YourClass.my_var_hash[Thread.current.object_id][your_var]

注意,如果您使用此方法,您还需要将其中一个哈希值设置为时间戳,并通过删除旧密钥来做一些内务处理,b/c 您最终会用完所有系统内存如果你不做家务

带缓存:

# in your controller
@var = Rails.cache.fetch("#{Thread.current.object_id}_var_name") do 
    return 100 # do your work here to create the var value and return it
end

# in your model
lvalue = Rails.cache.fetch(("#{Thread.current.object_id}_var_name")

然后您可以将缓存过期时间设置为 5 分钟,或者您可以在请求结束时通配符清除缓存。

【讨论】:

    猜你喜欢
    • 2019-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-27
    • 2012-03-18
    • 2019-03-06
    相关资源
    最近更新 更多