将 AssociatedModel 或 AssociatedModel.id 分配给 ActiveRecordModel 的最佳方法?

Best way to assign a AssociatedModel or AssociatedModel.id to an ActiveRecordModel?

我正在尝试构建一个辅助方法,它将采用关键字参数并根据一些逻辑对它们进行处理,这是一个示例:

def update_with_logic(widget:, user:)
  # do some logic here
  widget.user = user
end

现在这工作得很好,但是,我想支持 user 也被设置为用户的 ID。

但是,如果我们将 user: 3 发送到我们的 update_with_logic 方法中,我会收到以下错误:

ActiveRecord::AssociationTypeMismatch: User(#47287814961100) expected, got 3 which is an instance of Integer(#47287745166420)

如果你尝试另一种方式:widget.user_id = useruser 是一个 ActiveRecord::Model,它会将 user_id 设置为 nil(而不是加注一个例外)

这让我感到惊讶,因为我可以在 ActiveRecord::Query 设置中毫无问题地执行以下操作:

userModel = User.find 3

# Looking up by user_id while sending a user model as the value
Widget.where(user_id: userModel).to_sql
# => SELECT "widgets".* FROM "widgets" WHERE "widgets"."user_id" = 3

# Looking up by user association while sending a user_id (integer) as the value
Widget.where(user: 3).to_sql
# => SELECT "widgets".* FROM "widgets" WHERE "widgets"."user_id" = 3

我试过用widget.attributes = { user: user },但是用user = 3效果一样。 (引发 ActiveRecord::AssociationTypeMismatch)。

我找到的唯一解决方案是执行以下操作:

widget.user_id = user.is_a?(User) ? user.id : user

上面的解决方案有效,但是,它感觉不像是一个干净的实现。我希望有人能推荐一些更符合 "The Rails Way®" ;)

的东西

您可以向 ActiveRecord 提供如此混乱的内容,只是因为它确实 some heavylifting 构建了正确的查询。字面上没有魔法 - 所有这些方便的多态性都以代码复杂性为代价。

您使用 try 的代码有点危险 - 如果提供的不是 User 实例而是任何其他模型怎么办(它也会响应 id)?你可以让它更防弹,例如

case user
when Integer
  widget.user_id = user
when User
  widget.user = user
else
  raise "BOOM!"
end

但这也不太理想——如果提供的 ID 的用户不存在怎么办?当然,我们可以很容易地添加检查(像 widget.user = User.find(user) 这样的事情),但是我们真的需要所有这些偶然的复杂性吗?

如果我是你,我会停下来问自己一个问题:为什么这个方法被调用不一致?如果我们希望它将用户分配给小部件,为什么我们不想提供用户而是提供其他东西(反之亦然)?真的无法避免吗?

在大多数现实情况下,答案是否定的:可以在调用此方法的地方进行必要的更改,以便它始终接收一致的参数 - 一个实例用户名或有效用户 ID,- 并保持方法本身简洁明了。