Szyny 6
Railsy 6 dodały metody upsert
i upsert_all
, które zapewniają tę funkcjonalność.
Model.upsert(column_name: value)
[upsert] Nie tworzy instancji żadnych modeli ani nie wyzwala wywołań zwrotnych ani walidacji modułu Active Record.
Szyny 5, 4 i 3
Nie, jeśli szukasz typu instrukcji „upsert” (gdzie baza danych wykonuje aktualizację lub instrukcję wstawiania w tej samej operacji). Po wyjęciu z pudełka, Railsy i ActiveRecord nie mają takiej funkcji. Możesz jednak użyć górnego klejnotu.
W przeciwnym razie możesz użyć: find_or_initialize_by
lub find_or_create_by
, które oferują podobną funkcjonalność, jednak kosztem dodatkowego trafienia do bazy danych, co w większości przypadków nie stanowi żadnego problemu. Więc jeśli nie masz poważnych problemów z wydajnością, nie użyłbym tego klejnotu.
Na przykład, jeśli nie zostanie znaleziony żaden użytkownik o nazwie „Roger”, tworzona jest instancja nowego użytkownika z name
ustawieniem „Roger”.
user = User.where(name: "Roger").first_or_initialize
user.email = "[email protected]"
user.save
Alternatywnie możesz użyć find_or_initialize_by
.
user = User.find_or_initialize_by(name: "Roger")
W Railsach 3.
user = User.find_or_initialize_by_name("Roger")
user.email = "[email protected]"
user.save
Możesz użyć bloku, ale blok działa tylko wtedy, gdy rekord jest nowy .
User.where(name: "Roger").first_or_initialize do |user|
user.save
end
User.find_or_initialize_by(name: "Roger") do |user|
user.save
end
Jeśli chcesz użyć bloku niezależnie od trwałości rekordu, użyj tap
na wyniku:
User.where(name: "Roger").first_or_initialize.tap do |user|
user.email = "[email protected]"
user.save
end
find_or_initialize_by
ifind_or_create_by
akceptują blok. Myślałem, że chodziło Ci o to, że niezależnie od tego, czy rekord istnieje, czy nie, blok zostanie przekazany z obiektem rekordu jako argumentem w celu wykonania aktualizacji.W Rails 4 możesz dodać do konkretnego modelu:
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
i używaj go jak
User.where(email: "[email protected]").update_or_create(name: "Mr A Bbb")
Lub jeśli wolisz dodać te metody do wszystkich modeli umieszczonych w inicjatorze:
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
źródło
assign_or_new
zwróci pierwszego wiersza w tabeli, jeśli istnieje, a następnie ten wiersz zostanie zaktualizowany? Wydaje się, że robi to dla mnie.User.where(email: "[email protected]").first
zwróci nil, jeśli nie zostanie znaleziony. Upewnij się, że maszwhere
lunetęupdated_at
nie zostanie dotknięty, ponieważassign_attributes
jest używanyDodaj to do swojego modelu:
def self.update_or_create_by(args, attributes) obj = self.find_or_create_by(args) obj.update(attributes) return obj end
Dzięki temu możesz:
User.update_or_create_by({name: 'Joe'}, attributes)
źródło
Magia, której szukałeś, została dodana w
Rails 6
Teraz możesz upsert (zaktualizować lub wstawić). Do jednorazowego użytku:Model.upsert(column_name: value)
W przypadku wielu rekordów użyj upsert_all :
Model.upsert_all(column_name: value, unique_by: :column_name)
Uwaga :
źródło
Stare pytanie, ale wrzucam moje rozwiązanie do ringu dla kompletności. Potrzebowałem tego, gdy potrzebowałem konkretnego znaleziska, ale innego stworzenia, jeśli nie istnieje.
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
źródło
Możesz to zrobić w jednym oświadczeniu w ten sposób:
CachedObject.where(key: "the given key").first_or_create! do |cached| cached.attribute1 = 'attribute value' cached.attribute2 = 'attribute value' end
źródło
Sequel gem dodaje update_or_create sposób, który wydaje się robić to, co szukasz.
źródło