Rails覆盖保存以执行选择性更新

Jay*_*uat 7 ruby activerecord ruby-on-rails

我正在使用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
Run Code Online (Sandbox Code Playgroud)

Laa*_*aas 9

你似乎需要find_or_create_by_*方法.

To avoid the loop, you should not place this in save method, but in one of these two places:

Option 1: Controller level

In your controller where you instanciate this UserActivity instance, you instead write:

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.....
Run Code Online (Sandbox Code Playgroud)

Option 2: Class method

If you find yourself adding the above code to several contrllers, a better way would be to define a new class method in 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
Run Code Online (Sandbox Code Playgroud)

And in the controllers, obviously:

UserActivity.create_or_update_from_attrs(...)
Run Code Online (Sandbox Code Playgroud)

Override save

Of course, you can override the save method too, but this does duplicate Rails functionality (find_or_create_by...) and as such violates DRY and you could shoot yourself in your foot some time later when this conflicts with some other situation you run into, so I discourage the usage of this:

EDIT: updated to avoid infinite loop

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
Run Code Online (Sandbox Code Playgroud)