Ruby User.current 这种用法到底靠谱不靠谱?

ywencn · 2013年07月31日 · 最后由 lmorenbit 回复于 2013年09月27日 · 5168 次阅读
class User < ActiveRecord::Base
  def self.current
    Thread.current[:user]
  end
  def self.current=(user)
    Thread.current[:user] = user
  end
end

就是这种在Model里面拿current_user的方法。 最近似乎遇到了用户乱窜的情况。。获取错了似乎。

共收到 39 条回复

线程安全....

#1楼 @jasl 设置一下 config.threadsafe! 的话管用么?

干嘛要这么弄啊,下层的就让上层传过去嘛

#2楼 @ywencn 因为current是各自线程维护的,但是不一定这个用户每次都被某个线程 服务。。。

好不好用和项目有关, 小项目,无所谓, 大项目,不好。 我们之前就是用 User.current方式 导致,model层代码,严重依赖 User.current,导致系统极度混乱。 后来,改成了Controller current_user模式 ,世界从此清净了!

为什么要这样用,能说说用的场景吗?

#6楼 @small_fish__ 记录个操作记录什么的,这个最方便呀。 #5楼 @ery 每次都从controller里面传过去么?

#7楼 @ywencn controller: current_user.xxx.create ? 不知道这么弄有什么好不好。

线程是安全的。但是要注意 Thread.current[:xxx] 类似全局变量,小心在其他地方被修改了。起个不冲突的名字,应该没问题吧?

#7楼 @ywencn 对,每次都从Controller传递过去。 举个例子

class BooksController < ApplicationController
  def index
    @books = current_user.books
  end

  def create_1
    @book = current_user.books.create
  end

  def create_2
    @book = Book.create
    @book.user = current_user
  end
end

其实 从MVC框架逻辑上分析 current_user 属于 会话层,属于一个临时的状态,而非持久性的状态。 所以 它更应该 属于 Controller 层 不应该属于 Model 层

User.current 这种用法,从 redmine 里来的吧 XD

传送门 https://github.com/redmine/redmine/blob/2.3.2/app/models/user.rb#L590

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

def self.current
  Thread.current[:current_user] ||= User.anonymous
end

用户乱窜有可能是处理当前的请求时没有重新赋值,结果继续使用上一个请求的数据。

每次请求都必须给线程变量赋值 这样是线程安全的, Rails4 还专门引入了 ActiveSupport::PerThreadRegistry.

#10楼 @ery 恩,那不是想偷懒么。。

#14楼 @hooopo +1 分析的很好

这个问题曾经在rails bestpractices 上有激烈的争论: http://rails-bestpractices.com/posts/47-fetch-current-user-in-models

用户乱串 我们也遇到过, 因为后面的用户复用了之前的线程变量.

所以应该在每次请求结束后清除线程变量, 可以放到 application_controller 中

after_filter :clear_thread_variable

def clear_thread_variable
  Thread.current[:user] = nil
end

在 em-synchrony, celluloid, 或者线程池部署都行不通

另外 Thread local 在 Ruby 2.1 之前都是 Fiber local :

Fiber.new { Thread.current[:a] = 3 }.resume
Thread.current[:a] #=> nil

#20楼 @wxianfeng 恩,我来试试看。后来你们搞定了么?

这种方式在java的项目中常用,因为java都是线程模型的,但在ruby上,如果是unicorn中中并发模型,还是可以的,记得设置完后清除掉Thread上的变量否则下次请求或者异常情况下被设置错误的变量。但在Fiber并发下,可能就不行了。

偶们把session存数据库里,然后就不会有线程问题啦

遵守MVC也好,不遵守MVC也好,都要付出代价。

#24楼 @badboy 为什么把session存数据库里就没有线程问题了?

#26楼 @ywencn 他说的是Mnesia吧

#27楼 @hooopo 哈哈你妹,快说

@ywencn 就不能 Controller 传过去吗?

#26楼 @ywencn session 存数据库不太靠谱吧

#29楼 @ywencn 一提到session我就发现大多数人不懂web开发啊...

#32楼 @hooopo 求开专题

@ywencn session 存数据库怎么不靠谱,没什么区别啊

...session到底是怎么和线程扯上关系的?

#34楼 @tumayun 我哪里有说不靠谱。。。

@ywencn 额 回错了 应该是 @hbin 哈哈

你这个是singleton的问题的,你的activerecord是singleton的?

最近也同样遇到了这个问题,翻了几篇文章,觉得这篇讨论的挺详细的。 http://rails-bestpractices.com/posts/47-fetch-current-user-in-models 这里面有大段的关于Thread 取current_user 是否是好设计的讨论。@ywencn @hooopo 说得两种方式,其实比较像,第二种用attr_accessor比较巧妙的缓存了session[:user_id](可以是author, operator等等随意)

flyerhzm的其中之一的解释是 Thread.current is used to store and fetch the thread local variables. That means two threads have the same variables but these variables are referring to the different memory locations. If you see the rails 3.0.rc source codes, you will find rails also use Thread.current for time_zone, active_record_sql_runtime and etc.

我觉得ywencn说得是协作者修改创作者的操作记录的问题。所以hp说得第一种方案就不多说了。我的观点: 虽然hp的方式看起来更安全一点(也很巧妙,这是毋庸置疑的!)。我更喜欢Thread这种方式,从设计的角度来说,假如我有三处需要这个session[:user_id](如create, update, destroy),在controller每次都需要有一个{user_id: current_user.id},这实际上违反了rails的一些约定,另更多的重复会有更多的bug,controller(有可能多个)跟model层耦合的更紧,扩展性也会差一些。我觉得如果真觉得有不对,可以多写一些log追踪一下。

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