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


93

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

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

답변:


172

레일스 6

Rails 6 은이 기능을 제공 하는 upsert및 upsert_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_by와 find_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.