我在我的Rails应用程序中使用Null Object模式来实现来宾用户帐户的概念。
像许多应用程序一样,我在ApplicationController
上有一个名为current_user
的方法。
对于未登录的用户,我想使用guest用户null对象。
它在许多情况下都有效,但之后遇到类似以下的情况 -
params.merge({ user: current_user })
MyModel.new(params)
当然这失败了,但有以下例外。
ActiveRecord::AssociationTypeMismatch: User expected, got GuestUser
我的问题是,优雅处理这种情况的方法是什么。 Null对象模式的想法是,您可以透明地交换此null对象,并使其基本上是真实对象的鸭子类型。
对于在对象上调用的方法,显然该如何做到这一点,但在这种情况下,我希望能够传递它并基本上将它设置为null
的关联列,而不是需要一大堆自定义逻辑(避免这无论如何都是空对象模式的重点)。
多态关系并不完全。
快速回答:没有一种优雅的方法可以解决这个问题(我不确定如何量化优雅)。
您将不得不创建一个模仿您的null对象所基于的模型的持久性方法(User)的问题。您还必须编写方法来安抚ActiveRecord,以使关联列成为nil
。
幸运的是,这个用例一直是solved
如果您的MyModel对user_id接受null,那么您可以这样做
params.merge(user: current_user) unless current_user.is_a?(GuestUser)
MyModel.new(params)
在这里使用null对象模式绝对不是一个好主意,因为如果你打算在“注册”之前让用户拥有任何类型的持久性,你需要数据库生成的id来构建关联并保持参照完整性。
允许在没有user_id的情况下创建MyModel
本质上会创建一个孤立的记录,并且只会给你另一个将它链接到屏幕后面的用户的问题。这就是为什么你的架构不应该首先允许它。
而是希望在需要时创建访客用户记录(例如,当访客用户将第一个项目添加到购物车时)并使用定期任务(如Cron选项卡)定期清理垃圾记录。
我还会考虑你是否真的想要将访客用户设置为单独的类,因为STI和多态性在加入时往往会变得非常混乱。只需使用时间戳列(激活帐户时的记录)或枚举。
一种选择是覆盖user=
方法,以便它知道GuestUser
的存在(并且可以适当地处理):
def user=(value)
if value.is_a?(GuestUser)
super(nil)
else
super
end
end
Rails中的所有批量赋值方法(创建,更新等)都将使用适当的setter来设置值。如果这是您的应用程序中的常见模式,则可以很容易地将其置于关注之中。
如果您不允许在nil
列中使用user_id
,您可以灵活地执行诸如分配标记值之类的操作,然后您可以在访问者中使用该值:
def user
if user_id == GUEST_USER_ID
GuestUser.new
else
super
end
end