[ruby-on-rails] Rails가 마법을 만들거나 업데이트합니까?

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

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



답변

레일스 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


답변

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


답변

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

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)


답변

당신이 찾고 있던 마법이 추가되었습니다. 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 전용


답변

오래된 질문이지만 완전성을 위해 내 솔루션을 반지에 던졌습니다. 특정 찾기가 필요할 때 필요했지만 존재하지 않으면 다른 생성이 필요했습니다.

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


답변

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

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


답변

속편의 보석은 추가 update_or_create 당신이 찾고있는 무엇을 할 것으로 보인다 방법.