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

时间:2014-05-15 20:08:35

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

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

后台:我有一个API驱动的rails 3应用程序,它利用DataMapper(DM)和DM适配器与API进行交互。每个DM模型都有一个相应的REST-ish API端点,就像使用rails scaffolding一样。 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 *会在请求结束时将nil单例上的所有属性设置为ApiHeaders,以防止请求之间的标头泄漏。

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

4。自定义代码

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

摘要

从会话中将这些讨厌的API令牌放入应用程序的内容中,最简单的方法是什么?它们可以随每个API请求一起发送?我希望有一个比上面列出的更好的解决方案。

* after_action

的别名

2 个答案:

答案 0 :(得分:4)

我使用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

答案 1 :(得分:0)

使用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分钟,也可以在请求结束时使用通配符清除缓存。