Rails가 마법을 만들거나 업데이트합니까?


93

CachedObject키로 인덱싱 된 일반 직렬화 된 객체를 저장 하는 클래스 가 있습니다. 이 클래스가 create_or_update메서드 를 구현하기를 원합니다 . 개체가 발견되면 업데이트하고, 그렇지 않으면 새 개체를 만듭니다.

Rails에서이 작업을 수행 할 수있는 방법이 있습니까? 아니면 내 자신의 방법을 작성해야합니까?

답변:


172

레일스 6

Rails 6 은이 기능을 제공 하는 upsertupsert_all메소드를 추가했습니다 .

Model.upsert(column_name: value)

[upsert] 모델을 인스턴스화하지 않으며 Active Record 콜백 또는 유효성 검사를 트리거하지도 않습니다.

레일 5, 4, 3

"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

또는 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

블록을 사용할 수 있지만 블록은 레코드가 new 인 경우에만 실행됩니다 .

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
문서가 가리키는 소스 코드는 이것이 의미하는 방식으로 작동하지 않음을 보여줍니다. 블록은 해당 레코드가 존재하지 않는 경우에만 새 메서드로 전달됩니다. Rails에는 "upsert"마법이없는 것 같습니다. 두 개의 Ruby 문으로 분리해야합니다. 하나는 객체 선택을위한 것이고 다른 하나는 속성 업데이트를위한 것입니다.
sameers

@sameers 나는 당신이 의미하는 바를 이해하지 못합니다. 내가 무엇을 암시한다고 생각하십니까?
Mohamad

1
오 ... 이제 무슨 뜻인지 알겠습니다. 두 가지 형태 find_or_initialize_byfind_or_create_by블록 을 모두 수용합니다. 레코드가 있는지 여부에 관계없이 업데이트를 수행하기 위해 레코드 개체를 인수로 사용하여 블록이 전달된다는 것을 의미한다고 생각했습니다.
sameers

3
대답은 아니지만 API가 약간 오해의 소지가 있습니다. 블록이 전달 될 것으로 예상하므로 이에 따라 생성 / 업데이트 할 수 있습니다. 대신, 우리는 그것을 별도의 문장으로 나누어야합니다. 우우. <3 Rails :)
Volte

32

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

1
assign_or_new테이블의 첫 번째 행이 있으면 반환 하지 않고 해당 행이 업데이트됩니까? 나를 위해 그렇게하는 것 같습니다.
steve klein 2015

User.where(email: "a@b.com").first찾지 못하면 nil을 반환합니다. where스코프 가 있는지 확인하십시오.
montrealmike

그냥 메모는 말을 updated_at하기 때문에 닿을 수없는 assign_attributes사용되는
lshepstone가

그것은 당신이 assing_or_new 사용하고있다되지 않습니다하지만 당신은 때문에 저장 update_or_create를 사용할 경우
montrealmike

13

이것을 모델에 추가하십시오.

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)

두 번째 부분은 작동하지 않습니다. 업데이트 할 레코드의 ID없이 클래스 수준에서 단일 레코드를 업데이트 할 수 없습니다.
Aeramor

1
obj = self.find_or_create_by (args); obj.update (속성); return obj; 작동합니다.
veeresh yh

12

당신이 찾고 있던 마법이 추가되었습니다. Rails 6 Now you can upsert (update or insert). 단일 레코드 사용 :

Model.upsert(column_name: value)

여러 레코드의 경우 upsert_all 사용 하십시오 .

Model.upsert_all(column_name: value, unique_by: :column_name)

참고 :

  • 두 방법 모두 Active Record 콜백 또는 유효성 검사를 트리거하지 않습니다.
  • unique_by => PostgreSQL 및 SQLite 전용

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

0

다음과 같이 하나의 문으로 할 수 있습니다.

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

9
이것은 작동하지 않습니다. 원래 레코드가 발견되면 반환하기 때문입니다. OP는 레코드가 발견 되더라도 항상 값을 변경하는 솔루션을 요청합니다.
JeanMertz

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.