CachedObject
키로 인덱싱 된 일반 직렬화 된 객체를 저장 하는 클래스 가 있습니다. 이 클래스가 create_or_update
메서드 를 구현하기를 원합니다 . 개체가 발견되면 업데이트하고, 그렇지 않으면 새 개체를 만듭니다.
Rails에서이 작업을 수행 할 수있는 방법이 있습니까? 아니면 내 자신의 방법을 작성해야합니까?
답변
레일스 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
답변
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