是否在观察者中访问会话是个好主意?

时间:2020-03-06 14:43:21  来源:igfitidea点击:

我想在Ruby on Rails应用程序中记录用户的操作。

到目前为止,我有一个模型观察器,可在更新和创建后将日志插入数据库。为了存储哪个用户执行了已记录的操作,我需要访问该会话,但这是有问题的。

首先,它打破了MVC模型。其次,技术范围从骇人的技巧到怪异的技巧,甚至可能将实现与Mongrel服务器绑定在一起。

什么是正确的方法?

解决方案

我们认为它打破了MVC是对的。我建议在控制器中使用回调,主要是因为在某些情况下(例如,调用了save的模型但验证失败),我们不希望观察者记录任何内容。

我发现这是一个非常有趣的问题。我会在这里大声思考一下...

最终,我们面临的一个决定是违反设计模式可接受的实践,以实现一组特定的功能。所以,我们必须问自己

1)有哪些可能的解决方案不违反MVC模式

2)有哪些可能的解决方案会违反MVC模式

3)哪个选项最好?我认为设计模式和标准实践非常重要,但是同时如果坚持它们会使代码更复杂,那么正确的解决方案很可能就是违反实践。有人可能不同意我的观点。

让我们首先考虑#1.

我不由自主地想到以下可能的解决方案

A)如果我们真的对谁在执行这些操作感兴趣,是否应该以任何方式将此数据存储在模型中?它将使观察者可以使用此信息。而且,这还意味着ActiveRecord类的任何其他前端调用程序都具有相同的功能。

B)如果我们对了解谁创建了一个条目并没有真正的兴趣,但是更想记录Web操作本身,那么我们可以考虑"观察"控制器操作。自从我了解了Rails源代码以来已经有一段时间了,所以我不确定谁的ActiveRecord :: Observer可以"观察"模型,但是我们可能可以将其适应于控制器观察者。从这个意义上讲,我们不再需要观察模型,而是向该观察者进行会话和其他控制器类型的数据信息是有意义的。
C)最简单的解决方案,以最少的"结构",是在正在观察的操作方法的末尾简单地删除日志记录代码。

现在考虑选项2,这违反了MVC惯例。

A)如我们所建议,我们可以找到使模型观察者可以访问Session数据的方法。我们已将模型与业务逻辑相结合。

B)在这里想不到其他任何人:)

如果不希望进一步了解我的项目,我个人的意愿是1A(如果我希望将人员添加到记录中),或者是1C(如果我仅对几个地方感兴趣)。如果我们确实想要一个适用于所有控制器和操作的健壮日志记录解决方案,则可以考虑使用1B。

让模型观察者发现会话数据有点"麻烦",如果我们尝试在任何其他项目/情况/上下文中使用模型,则可能会中断。

嗯,这是一个棘手的情况。我们几乎必须违反MVC才能使其正常工作。

我会做这样的事情:

class MyObserverClass < ActiveRecord::Observer
  cattr_accessor :current_user # GLOBAL VARIABLE. RELIES ON RAILS BEING SINGLE THREADED

  # other logging code goes here
end

class ApplicationController
  before_filter :set_current_user_for_observer

  def set_current_user_for_observer
    MyObserverClass.current_user = session[:user]
  end
end

它有点hacky,但是它比我见过的许多其他核心rails都更hacky。

要使其具有线程安全性,我们需要做的所有事情(仅在无论如何都在jruby上运行才有意义)是将cattr_accessor更改为适当的方法,并将其数据存储在线程本地存储中

过去,当执行此类操作时,我倾向于扩展User模型类以包含"当前用户"的概念

查看以前的答案,我看到了在会话中存储实际活动记录用户的建议。这有几个缺点。

  • 它将一个可能很大的对象存储在会话数据库中
  • 这意味着用户的副本在所有时间(或者直到强制注销之前)都被"缓存"。这意味着直到该用户注销并重新登录,该用户状态的任何更改都不会被识别。例如,这意味着尝试禁用该用户将等待他注销并重新登录。这可能不是我们想要的行为。

这样,在请求开始时(在过滤器中),我们可以从会话中获取user_id并读取用户,并设置User.current_user。

像这样的东西

class User
  cattr_accessor :current_user
end

class Application
  before_filter :retrieve_user

  def retrieve_user
    if session[:user_id].nil?
      User.current_user = nil
    else
      User.current_user = User.find(session[:user_id])
    end
  end
end

从那时起,它应该是微不足道的。

我找到了一种干净的方法来执行我选择的答案所建议的操作。

http://pjkh.com/articles/2009/02/02/creating-an-audit-log-in-rails

该解决方案使用AuditLog模型以及TrackChanges模块向任何模型添加跟踪功能。仍然需要我们在更新或者创建时向控制器添加一行。