我正在使用Rails 3.2.8应用程序,该应用程序使用自行开发的REST API来插入新数据。插入逻辑对于每个端点都是通用的,并且在调用Model.save时非常简单。
对于其中一个模型类型,我想首先检查是否存在预先存在的记录,如果存在,则更新而不是插入。如果代码允许我在Controller级别进行交互,那么通过使用find_or_create_by会很容易,但是(我认为)我唯一的选择是覆盖模型中的save方法或使用before_save回调。
我正在努力想办法让这项工作成功,因为在模型中对save或update_attributes的任何调用只会导致无限循环(原因很明显)。
有没有办法利用before_save或以这样的方式覆盖保存,我可以先检查记录是否存在属性x和y,如果是,则检索该记录并执行更新,否则继续使用标准ActiveRecord保存?
这是我的代码,因为它目前位于Activity模型中,由于无限循环问题而无效:
def save
a = UserActivity.find_or_initialize_by_user_id_and_activity_id(user_id: user_id, activity_id: activity_id)
a.update_attributes start_at: start_at, end_at: end_at.....
end
答案 0 :(得分:8)
您似乎需要find_or_create_by_*
方法。
要避免循环,您不应将其放在save
方法中,而应放在以下两个位置之一:
在您实例化此UserActivity
实例的控制器中,您改为编写:
a = UserActivity.find_or_create_by_user_id_and_activity_id(user_id: user_id, activity_id: activity_id)
a.update_attributes start_at: start_at, end_at: end_at.....
如果您发现自己将上述代码添加到几个控制者,更好的方法是在UserActivity
中定义一个新的类方法:
class UserActivity
def self.create_or_update_from_attrs(user_id, activity_id, start_at, end_at...)
a = UserActivity.find_or_create_by_user_id_and_activity_id(user_id: user_id, activity_id: activity_id)
a.update_attributes start_at: start_at, end_at: end_at.....
end
end
显然在控制器中:
UserActivity.create_or_update_from_attrs(...)
当然,您也可以覆盖save
方法,但这确实会重复Rails功能(find_or_create_by...
),因此会违反DRY 和,您可以自己开枪一段时间之后,当这与你遇到的其他情况发生冲突时,我不鼓励使用这个:
编辑更新以避免无限循环
class UserActivity
def save
# If this is new record, check for existing and update that instead:
if new_record? && a = UserActivity.where(user_id: user_id, activity_id: activity_id).first
a.update_attributes start_at: start_at, end_at: end_at ...
return true # just to comply with Rails conventions
else
# just call super to save this record
super
end
end
end