Rails创建或更新魔术?

kid*_*rew 88 activerecord ruby-on-rails

我有一个名为CachedObjectstore 的类,用于存储由key索引的通用序列化对象.我希望这个类实现一个create_or_update方法.如果找到一个对象,它将更新它,否则它将创建一个新对象.

有没有办法在Rails中执行此操作,还是我必须编写自己的方法?

Moh*_*mad 157

如果您正在寻找"upsert"(数据库在同一操作中执行更新或插入语句)类型的语句,则不是这样.开箱即用,Rails和ActiveRecord没有这样的功能.但是,您可以使用upsert gem.

否则,您可以使用:find_or_initialize_by或者find_or_create_by提供类似功能,虽然以额外的数据库命中为代价,在大多数情况下,这几乎不是问题.因此,除非您有严重的性能问题,否则我不会使用gem.

例如,如果没有找到名为"Roger"的用户,则实例化新用户实例,并将其name设置为"Roger".

user = User.where(name: "Roger").first_or_initialize
user.email = "email@example.com"
user.save
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用find_or_initialize_by.

user = User.find_or_initialize_by(name: "Roger")
Run Code Online (Sandbox Code Playgroud)

在Rails 3中.

user = User.find_or_initialize_by_name("Roger")
user.email = "email@example.com"
user.save
Run Code Online (Sandbox Code Playgroud)

您可以使用块,但只有在记录是新的时才会运行块.

User.where(name: "Roger").first_or_initialize do |user|
  # this won't run if a user with name "Roger" is found
  user.save 
end

User.find_or_initialize_by(name: "Roger") do |user|
  # this also won't run if a user with name "Roger" is found
  user.save
end
Run Code Online (Sandbox Code Playgroud)

如果要使用块而不管记录的持久性,请使用tap结果:

User.where(name: "Roger").first_or_initialize.tap do |user|
  user.email = "email@example.com"
  user.save
end
Run Code Online (Sandbox Code Playgroud)

  • 这有点误导,不一定是答案,而是API.人们会期望无论如何都要传递块,因此可以相应地创建/更新.相反,我们必须将其分解为单独的陈述.嘘.<3 Rails虽然:) (2认同)

mon*_*ike 29

在Rails 4中,您可以添加到特定模型:

def self.update_or_create(attributes)
  assign_or_new(attributes).save
end

def self.assign_or_new(attributes)
  obj = first || new
  obj.assign_attributes(attributes)
  obj
end
Run Code Online (Sandbox Code Playgroud)

并使用它

User.where(email: "a@b.com").update_or_create(name: "Mr A Bbb")
Run Code Online (Sandbox Code Playgroud)

或者,如果您希望将这些方法添加到初始化程序中的所有模型中:

module ActiveRecordExtras
  module Relation
    extend ActiveSupport::Concern

    module ClassMethods
      def update_or_create(attributes)
        assign_or_new(attributes).save
      end

      def update_or_create!(attributes)
        assign_or_new(attributes).save!
      end

      def assign_or_new(attributes)
        obj = first || new
        obj.assign_attributes(attributes)
        obj
      end
    end
  end
end

ActiveRecord::Base.send :include, ActiveRecordExtras::Relation
Run Code Online (Sandbox Code Playgroud)


Kar*_*ren 12

将此添加到您的模型:

def self.update_or_create_by(args, attributes)
  obj = self.find_or_create_by(args)
  obj.update(attributes)
  return obj
end
Run Code Online (Sandbox Code Playgroud)

有了它,你可以:

User.update_or_create_by({name: 'Joe'}, attributes)
Run Code Online (Sandbox Code Playgroud)

  • obj = self.find_or_create_by(args); obj.update(属性); 返回 obj ;会起作用。 (2认同)

Imr*_*ran 7

您一直在寻找的魔术已添加到“ Rails 6 现在您可以更新(更新或插入)”中了。

Model.upsert(column_name: value)
Run Code Online (Sandbox Code Playgroud)

注意:对于批量更新或创建,您可以使用 upsert_all


小智 5

通过链接find_or_initialize_byupdate,可以通过一种简单的方式实现这一点,从而避免(根据我的经验,通常)不必要的警告upsert,并且还可以最大程度地减少数据库调用。

例如:

Class.find_or_initialize_by(
  key: "foo",
  ...
).update(
  new_attribute: "bar",
  ...
)
Run Code Online (Sandbox Code Playgroud)

将返回您新创建或更新的对象。

值得注意的是,如果您的find_or_initialize_by属性与多个类实例匹配,则仅选择并更新“第一个”实例。