Rails创建或更新魔术?

时间:2013-09-11 16:56:25

标签: ruby-on-rails activerecord

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

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

7 个答案:

答案 0 :(得分:148)

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

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

例如,如果找不到名为“Roger”的用户,则会将name设置为“Roger”的新用户实例进行实例化。

user = User.where(name: "Roger").first_or_initialize
user.email = "email@example.com"
user.save

或者,您可以使用find_or_initialize_by

user = User.find_or_initialize_by(name: "Roger")

在Rails 3中。

user = User.find_or_initialize_by_name("Roger")
user.email = "email@example.com"
user.save

您可以使用某个区块,但该区块仅在记录为新时才会运行。

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

如果您想使用块而不管记录的持久性,请在结果上使用tap

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

答案 1 :(得分: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

并像

一样使用它
User.where(email: "a@b.com").update_or_create(name: "Mr A Bbb")

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

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

答案 2 :(得分:9)

将此添加到您的模型中:

def self.update_or_create_by(args, attributes)
  obj = self.find_or_create_by(args)
  obj.update(attributes)
  return obj
end

有了这个,你可以:

User.update_or_create_by({name: 'Joe'}, attributes)

答案 3 :(得分:1)

您可以在以下一个语句中执行此操作:

CachedObject.where(key: "the given key").first_or_create! do |cached|
   cached.attribute1 = 'attribute value'
   cached.attribute2 = 'attribute value'
end

答案 4 :(得分:1)

老问题,但把我的解决方案扔进戒指是为了完整。 当我需要特定的查找时,我需要这个,但如果不存在则需要不同的创建。

def self.find_by_or_create_with(args, attributes) # READ CAREFULLY! args for finding, attributes for creating!
        obj = self.find_or_initialize_by(args)
        return obj if obj.persisted?
        return obj if obj.update_attributes(attributes) 
end

答案 5 :(得分:1)

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

Model.upsert(column_name: value)

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

答案 6 :(得分:0)

sequel gem添加了update_or_create方法,似乎可以帮助您查找。