web-dev-qa-db-ja.com

Railsマジックを作成または更新しますか?

キーでインデックス付けされたジェネリックシリアル化オブジェクトを格納するCachedObjectというクラスがあります。このクラスにcreate_or_updateメソッドを実装してほしい。オブジェクトが見つかるとそれを更新し、見つからない場合は新しいオブジェクトを作成します。

Railsでこれを行う方法はありますか、または独自のメソッドを記述する必要がありますか?

85
kid_drew

「upsert」(データベースが同じ操作でupdateまたはinsertステートメントを実行する)タイプのステートメントを探している場合は、そうではありません。初期状態では、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 protected]"
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 protected]"
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 protected]"
  user.save
end
152
Mohamad

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: "[email protected]").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
29
montrealmike

これをモデルに追加します。

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)
9
Karen

探していた魔法がRails 6に追加されました。これで、アップサート(更新または挿入)できます。

Model.upsert(column_name: value)

注:一括更新または一括作成には、upsert_allを使用できます

3
Imran

次のような1つのステートメントで実行できます。

CachedObject.where(key: "the given key").first_or_create! do |cached|
   cached.attribute1 = 'attribute value'
   cached.attribute2 = 'attribute value'
end
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
1
Aeramor

sequel gemは pdate_or_create メソッドを追加します。これはあなたが探していることをするようです。

0
KurtPreston