处理 Rails 中带有空对象的关联

Handling associations w/ null objects in Rails

我在 Rails 应用程序中使用 Null Object 模式来实现来宾用户帐户的概念。

像许多应用程序一样,我在 ApplicationController 上有一个名为 current_user 的方法。

在未登录用户的情况下,我想使用我的来宾用户空对象。

它在很多情况下都有效,但是 运行 变成了类似下面的东西 -

params.merge({ user: current_user })
MyModel.new(params)

当然这会失败,但有以下例外。

ActiveRecord::AssociationTypeMismatch: User expected, got GuestUser

我的问题是,有什么方法可以优雅地处理这种情况。空对象模式的想法是,您可以透明地交换这个空对象,并使其本质上是真实对象的鸭子类型。

对于在对象上调用的方法如何做到这一点很明显,但在这种情况下,我希望能够传入它并基本上将关联列设置为 null,而不是需要一大堆自定义逻辑(无论如何避免这是空对象模式的重点)。

多态关系不完全是。

如果您的 MyModel 为 user_id 接受 null,那么您可以

params.merge(user: current_user) unless current_user.is_a?(GuestUser)
MyModel.new(params)

快速回答:没有优雅的处理方式(我不确定如何量化优雅)。

您必须创建一个关注点,该关注点模仿空对象所基于的模型(用户)的持久化方法。您还必须编写方法来安抚 ActiveRecord,使关联的列成为 nil.

幸运的是,这个 use-case 已经 solved

在这里使用空对象模式绝对不是一个好主意,因为如果您希望用户在 "registering" 之前具有任何类型的持久性,则需要数据库生成的 ID 来建立关联并保持参照完整性。

允许在没有 user_id 的情况下创建 MyModel 本质上会创建一个孤立的记录,只会给您带来另一个问题,将其链接到屏幕后面的用户。这就是为什么您的模式首先不应允许它。

相反,您想在需要时创建来宾用户记录(例如当来宾用户将第一个项目添加到购物车时)并使用循环任务(例如 Cron 选项卡)定期清除垃圾记录。

我还会考虑您是否真的想将来宾用户设置为单独的 class,因为 STI 和多态性在加入时往往会变得非常混乱。只需使用时间戳列(记录激活帐户的时间)或枚举即可。

一个选择是覆盖 user= 方法,以便它知道 GuestUser 的存在(并且可以适当处理):

def user=(value)
  if value.is_a?(GuestUser)
    super(nil)
  else
    super
  end
end

Rails 中的所有 mass-assignment 方法(创建、更新等)将使用适当的 setter 来设置值。如果这是您应用程序中的常见模式,这很容易引起关注。

如果您不允许 niluser_id 列中,您可以灵活地执行一些操作,例如分配标记值,然后您也可以在访问器中使用它:

def user
  if user_id == GUEST_USER_ID
    GuestUser.new
  else
    super
  end
end

我遇到了类似的问题。我只是从分配对象到分配我在 Null 对象上设置为 nil 的 object.id。不过,我认为这是一种破解。