将 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 = user
和 user
是一个 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,- 并保持方法本身简洁明了。
我正在尝试构建一个辅助方法,它将采用关键字参数并根据一些逻辑对它们进行处理,这是一个示例:
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 = user
和 user
是一个 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,- 并保持方法本身简洁明了。