8

我正在开发一个使用本地 REST API 插入新数据的 Rails 3.2.8 应用程序。插入逻辑对于每个端点都是通用的,并且非常简单地调用 Model.save。

对于其中一种模型类型,我想首先检查是否存在预先存在的记录,如果存在,则更新而不是插入。如果代码允许我在控制器级别进行交互,这将通过使用 find_or_create_by 很容易,但是(我认为)我唯一的选择是覆盖模型中的保存方法或使用 before_save 回调。

我正在努力寻找一种方法来完成这项工作,因为模型内部对 save 或 update_attributes 的任何调用都会导致无限循环(原因很明显)。

有没有办法利用 before_save 或覆盖 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
4

1 回答 1

10

你似乎需要find_or_create_by_*方法。

为避免循环,您不应将其放在save方法中,而应放在以下两个位置之一:

选项 1:控制器级别

在您实例化此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.....

选项 2:类方法

如果您发现自己将上述代码添加到多个控制器,更好的方法是定义一个新的类方法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
于 2012-11-05T20:11:00.373 回答