Raylar sihir yaratır mı yoksa günceller mi?


97

CachedObjectAnahtar tarafından indekslenmiş genel serileştirilmiş nesneleri depolayan bir sınıfım var . Bu sınıfın bir create_or_updateyöntem uygulamasını istiyorum . Bir nesne bulunursa, onu günceller, aksi takdirde yeni bir tane oluşturur.

Bunu Rails'de yapmanın bir yolu var mı yoksa kendi yöntemimi mi yazmam gerekiyor?

Yanıtlar:


180

Raylar 6

Rails 6 , bu işlevi sağlayan bir upsertve upsert_allyöntemi ekledi .

Model.upsert(column_name: value)

[upsert] Herhangi bir modeli somutlaştırmaz veya Aktif Kayıt geri aramalarını veya doğrulamalarını tetiklemez.

Raylar 5, 4 ve 3

Bir "upert" (veritabanının aynı işlemde bir güncelleme veya ekleme ifadesi yürüttüğü) türde bir ifade arıyorsanız değil. Kutudan çıkar çıkmaz, Rails ve ActiveRecord'un böyle bir özelliği yoktur. Bununla birlikte , yükseltici cevheri kullanabilirsiniz .

Aksi takdirde, ek bir veritabanı isabeti pahasına da olsa benzer işlevsellik sunan : find_or_initialize_byveya kullanabilirsiniz find_or_create_by, ki çoğu durumda bu neredeyse hiç sorun teşkil etmez. Bu yüzden ciddi performans endişeleriniz yoksa mücevheri kullanmam.

Örneğin, "Anlaşıldı" adında bir kullanıcı bulunmazsa, "Anlaşıldı" olarak nameayarlanmış yeni bir kullanıcı örneği oluşturulur .

user = User.where(name: "Roger").first_or_initialize
user.email = "email@example.com"
user.save

Alternatif olarak kullanabilirsiniz find_or_initialize_by.

user = User.find_or_initialize_by(name: "Roger")

Raylarda 3.

user = User.find_or_initialize_by_name("Roger")
user.email = "email@example.com"
user.save

Bir blok kullanabilirsiniz, ancak blok yalnızca kayıt yeniyse çalışır .

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

Kaydın kalıcılığına bakılmaksızın bir blok kullanmak istiyorsanız tap, sonucu kullanın :

User.where(name: "Roger").first_or_initialize.tap do |user|
  user.email = "email@example.com"
  user.save
end


1
Belgenin işaret ettiği kaynak kodu, bunun sizin ima ettiğiniz şekilde çalışmadığını gösterir - blok, yalnızca karşılık gelen kayıt yoksa yeni bir yönteme aktarılır. Rails'de "yükseltme" sihri yok gibi görünüyor - bunu iki Ruby deyimine ayırmanız gerekiyor, biri nesne seçimi ve diğeri öznitelik güncellemesi için.
sameers

@sameers Ne demek istediğini anladığımdan emin değilim. Ne ima ettiğimi sanıyorsun?
Mohamad

1
Oh ... Şimdi ne demek istediğini anlıyorum - her ikisi de oluşur find_or_initialize_byve find_or_create_bybir blok kabul edilir. Kaydın var olup olmadığına bakılmaksızın, güncellemeyi yapmak için bir bloğun kayıt nesnesiyle birlikte argüman olarak aktarılacağını kastettiğinizi düşündüm.
sameers

3
Bu biraz yanıltıcıdır, mutlaka yanıt değil, API'dir. Bloğun ne olursa olsun geçilmesi beklenir ve bu nedenle buna göre oluşturulabilir / güncellenebilir. Bunun yerine, onu ayrı ifadelere ayırmalıyız. Boo. <3 Raylar :)
Volte

32

Rails 4'te belirli bir modele ekleyebilirsiniz:

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

ve gibi kullan

User.where(email: "a@b.com").update_or_create(name: "Mr A Bbb")

Veya bu yöntemleri bir başlatıcıya yerleştirilen tüm modellere eklemeyi tercih ederseniz:

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
Olmaz assign_or_newVar olduğundan ve daha sonra bu satır güncellenecektir eğer tablodaki ilk satır dönmek? Bunu benim için yapıyor gibi görünüyor.
steve klein

User.where(email: "a@b.com").firstbulunmazsa sıfır döndürür. Mutlaka bir olduğundan emin olun wherekapsamı
montrealmike

Kullanıldığı updated_atiçin dokunulmayacağını söyleyen bir notassign_attributes
lshepstone

Assing_or_new kullanmayacaksınız, ancak kaydetme nedeniyle update_or_create kullanırsanız kullanacaktır
montrealmike

13

Bunu modelinize ekleyin:

def self.update_or_create_by(args, attributes)
  obj = self.find_or_create_by(args)
  obj.update(attributes)
  return obj
end

Bununla şunları yapabilirsiniz:

User.update_or_create_by({name: 'Joe'}, attributes)

İkinci kısım çalışmayacağım. Güncellenecek kaydın kimliği olmadan sınıf düzeyindeki tek bir kayıt güncellenemez.
Aeramor

2
obj = self.find_or_create_by (değiştirgeler); obj.update (öznitelikler); iade obj; çalışacak.
veeresh yh

12

Aradığınız sihir eklenmiştir Rails 6 Şimdi yükseltebilirsiniz (güncelleyebilir veya ekleyebilirsiniz). Tek kayıt kullanımı için:

Model.upsert(column_name: value)

Birden çok kayıt için upsert_all kullanın :

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

Not :

  • Her iki yöntem de Aktif Kayıt geri çağırmalarını veya doğrulamalarını tetiklemez
  • unique_by => Yalnızca PostgreSQL ve SQLite

Bunu üretimde kullanan var mı? 6.0.33 Rails kullanarak benim için beklendiği gibi çalışmıyor. Bir kaydım varsa [#<Model id: 1, other_model_fk: 33, some_property: 'foo'>]ve ararsak Model.upsert(other_model_fk: 33, some_property: 'bar')yeni bir satır eklenir. (FK sütunu bu arada dizine eklenmiştir). Beklediğimiz davranış "Bir nesne bulunursa onu günceller, aksi takdirde yeni bir tane oluşturur." çalışıyor gibi görünmüyor. Yeni bir nesne yaratıyor. Burada neyi özlüyorum?
B. Bulpett

1

Eski soru ama çözümümü eksiksizlik için ringe atmak. Buna, belirli bir bulmaya ihtiyacım olduğunda ihtiyacım vardı, ancak yoksa farklı bir yaratıma ihtiyacım vardı.

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

Bunu tek bir ifadeyle yapabilirsiniz:

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

9
Bu işe yaramaz, çünkü orijinal kaydı yalnızca bir tane bulunursa geri döndürecektir. OP, bir kayıt bulunsa bile değeri her zaman değiştiren bir çözüm ister.
JeanMertz

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.