J'ai une classe appelée CachedObject
qui stocke des objets génériques sérialisés indexés par clé. Je veux que cette classe implémente un create_or_update
méthode. Si un objet est trouvé, il le mettra à jour, sinon il en créera un nouveau.
Existe-t-il un moyen de faire cela dans Rails ou dois-je écrire ma propre méthode?
Pas si vous recherchez un type d'instruction "upsert" (où la base de données exécute une mise à jour ou une instruction d'insertion dans la même opération). Hors de la boîte, Rails et ActiveRecord n'ont pas cette fonctionnalité. Vous pouvez utiliser la gemme psert .
Sinon, vous pouvez utiliser: find_or_initialize_by
ou find_or_create_by
, qui offrent des fonctionnalités similaires, bien qu’au prix d’une frappe supplémentaire dans la base de données, ce qui, dans la plupart des cas, ne pose aucun problème. Donc, à moins que vous n'ayez de graves problèmes de performances, je ne voudrais pas utiliser ce joyau.
Par exemple, si aucun utilisateur portant le nom "Roger" n'a été trouvé, une nouvelle instance d'utilisateur est instanciée avec son name
défini sur "Roger".
user = User.where(name: "Roger").first_or_initialize
user.email = "[email protected]"
user.save
Alternativement, vous pouvez utiliser find_or_initialize_by
.
user = User.find_or_initialize_by(name: "Roger")
Dans Rails 3.
user = User.find_or_initialize_by_name("Roger")
user.email = "[email protected]"
user.save
Vous pouvez utiliser un bloc, mais le bloc ne s'exécute que si l'enregistrement est nouveau .
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
Si vous souhaitez utiliser un bloc quelle que soit la persistance de l'enregistrement, utilisez tap
sur le résultat:
User.where(name: "Roger").first_or_initialize.tap do |user|
user.email = "[email protected]"
user.save
end
Dans Rails 4, vous pouvez ajouter un modèle spécifique:
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
et l'utiliser comme
User.where(email: "[email protected]").update_or_create(name: "Mr A Bbb")
Ou si vous préférez ajouter ces méthodes à tous les modèles insérés dans un initialiseur:
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
Ajoutez ceci à votre modèle:
def self.update_or_create_by(args, attributes)
obj = self.find_or_create_by(args)
obj.update(attributes)
return obj
end
Avec cela, vous pouvez:
User.update_or_create_by({name: 'Joe'}, attributes)
La magie que vous avez recherchée a été ajoutée dans Rails 6
Vous pouvez maintenant insérer (mettre à jour ou insérer).
Model.upsert(column_name: value)
Remarque: vous pouvez utiliser upsert_all
Vous pouvez le faire en une déclaration comme ceci:
CachedObject.where(key: "the given key").first_or_create! do |cached|
cached.attribute1 = 'attribute value'
cached.attribute2 = 'attribute value'
end
Vieille question mais jetter ma solution dans le ring pour la perfection. J'avais besoin de cela quand j'avais besoin d'une découverte spécifique mais d'une création différente si elle n'existait pas.
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
La gemme suite ajoute une méthode pdate_or_create qui semble faire ce que vous cherchez.