web-dev-qa-db-fra.com

Rails créer ou mettre à jour la magie?

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?

85
kid_drew

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
152
Mohamad

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
29
montrealmike

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)
9
Karen

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

3
Imran

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
1
Rajesh Kolappakam

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
1
Aeramor

La gemme suite ajoute une méthode pdate_or_create qui semble faire ce que vous cherchez.

0
KurtPreston