处理 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 来设置值。如果这是您应用程序中的常见模式,这很容易引起关注。
如果您不允许 nil
在 user_id
列中,您可以灵活地执行一些操作,例如分配标记值,然后您也可以在访问器中使用它:
def user
if user_id == GUEST_USER_ID
GuestUser.new
else
super
end
end
我遇到了类似的问题。我只是从分配对象到分配我在 Null 对象上设置为 nil 的 object.id
。不过,我认为这是一种破解。
我在 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 来设置值。如果这是您应用程序中的常见模式,这很容易引起关注。
如果您不允许 nil
在 user_id
列中,您可以灵活地执行一些操作,例如分配标记值,然后您也可以在访问器中使用它:
def user
if user_id == GUEST_USER_ID
GuestUser.new
else
super
end
end
我遇到了类似的问题。我只是从分配对象到分配我在 Null 对象上设置为 nil 的 object.id
。不过,我认为这是一种破解。