我想在我的 Ruby on Rails 应用程序中记录用户的操作。
到目前为止,我有一个模型观察器,它在更新和创建后将日志插入数据库。为了存储哪个用户执行了记录的操作,我需要访问会话,但这是有问题的。
首先,它打破了MVC模型。其次,技术范围从 hackish 到古怪,甚至可能将实现与 Mongrel 服务器捆绑在一起。
正确的做法是什么?
我想在我的 Ruby on Rails 应用程序中记录用户的操作。
到目前为止,我有一个模型观察器,它在更新和创建后将日志插入数据库。为了存储哪个用户执行了记录的操作,我需要访问会话,但这是有问题的。
首先,它打破了MVC模型。其次,技术范围从 hackish 到古怪,甚至可能将实现与 Mongrel 服务器捆绑在一起。
正确的做法是什么?
嗯,这是一个棘手的情况。您几乎必须违反 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 更改为正确的方法,并将其数据存储在线程本地存储中
我觉得这是一个非常有趣的问题。我会在这里大声思考一下......
最终,我们面临的是为了实现一组特定功能而违反设计模式可接受实践的决定。所以,我们必须问问自己
1)不违反MVC模式的可能解决方案是什么
2) 有哪些可能违反MVC 模式的解决方案
3) 哪个选项最好?我认为设计模式和标准实践非常重要,但同时如果坚持它们会使您的代码更加复杂,那么正确的解决方案很可能是违反实践。有些人可能不同意我的观点。
让我们首先考虑#1。
在我的脑海中,我会想到以下可能的解决方案
A)如果你真的对谁在执行这些操作感兴趣,这些数据是否应该以任何方式存储在模型中?它将使您的观察者可以使用此信息。这也意味着您的 ActiveRecord 类的任何其他前端调用者都可以获得相同的功能。
B)如果您对了解谁创建了条目并不真正感兴趣,但对记录 Web 操作本身更感兴趣,那么您可能会考虑“观察”控制器操作。自从我研究 Rails 源代码以来已经有一段时间了,所以我不确定他们的 ActiveRecord::Observer 是谁“观察”了该模型,但您也许可以将其调整为控制器观察者。从这个意义上说,您不再观察模型,向该观察者提供会话和其他控制器类型数据信息是有意义的。C) 具有最少“结构”的最简单的解决方案是简单地将日志记录代码放在您正在观看的操作方法的末尾。
现在考虑选项 #2,打破 MVC 实践。
A)正如您所建议的,您可以找到让模型观察者访问会话数据的方法。您已将模型与业务逻辑耦合。
B)在这里想不出其他人:)
如果我想将人员附加到记录中,我个人的倾向是 1A,如果我想将人员附加到记录,或者只有少数地方我有兴趣这样做,我个人的倾向是 1A。如果您真的想要为您的所有控制器和操作提供强大的日志记录解决方案,您可以考虑 1B。
让您的模型观察者查找会话数据有点“臭”,如果您尝试在任何其他项目/情况/上下文中使用您的模型,可能会中断。
你是对的,它打破了 MVC。我建议在您的控制器中使用回调,主要是因为在某些情况下(例如调用保存但验证失败的模型)您不希望观察者记录任何内容。
我找到了一种干净的方法来做我选择的答案所建议的事情。
http://pjkh.com/articles/2009/02/02/creating-an-audit-log-in-rails
该解决方案使用 AuditLog 模型和 TrackChanges 模块向任何模型添加跟踪功能。它仍然需要您在更新或创建时向控制器添加一行。
过去,在做这样的事情时,我倾向于扩展 User 模型类以包含“当前用户”的想法
查看先前的答案,我看到了将实际活动记录用户存储在会话中的建议。这有几个缺点。
因此,在请求开始时(在过滤器中),您从会话中获取 user_id 并读取用户,设置 User.current_user。
像这样的东西...
类用户 cattr_accessor :current_user 结尾 类应用 before_filter :retrieve_user def 检索用户 如果会话[:user_id].nil? User.current_user = nil 别的 User.current_user = User.find(session[:user_id]) 结尾 结尾 结尾
从那时起,它应该是微不足道的。