Rails update_attributes sans sauvegarde?

386

Existe-t-il une alternative à update_attributes qui ne sauvegarde pas l'enregistrement?

Je pourrais donc faire quelque chose comme:

@car = Car.new(:make => 'GMC')
#other processing
@car.update_attributes(:model => 'Sierra', :year => "2012", :looks => "Super Sexy, wanna make love to it")
#other processing
@car.save

BTW, je sais que je peux @car.model = 'Sierra', mais je veux les mettre à jour tous sur une seule ligne.

tybro0103
la source
que voulez-vous dire par "ne pas sauvegarder l'enregistrement"?
Anatoly
update_attributes enregistre le modèle dans la base de données. Je me demande s'il existe une méthode similaire qui ne fonctionne pas.
tybro0103
3
attribue une méthode non destructive. Voir API pour plus de détails
Anatoly
3
Vous pouvez utiliser update_column (nom, valeur) Met à jour un seul attribut d'un objet, sans appeler save. 1. La validation est ignorée. 2. Les rappels sont ignorés. 3. La colonne updated_at / updated_on n'est pas mise à jour si cette colonne est disponible. apidock.com/rails/ActiveRecord/Persistence/update_column
Antoine
10
Pour 3.1+, utilisez assign_attributes apidock.com/rails/ActiveRecord/Base/assign_attributes
elado

Réponses:

597

Je crois que ce que vous recherchez est assign_attributes.

C'est fondamentalement la même chose que update_attributes mais cela ne sauvegarde pas l'enregistrement:

class User < ActiveRecord::Base
  attr_accessible :name
  attr_accessible :name, :is_admin, :as => :admin
end

user = User.new
user.assign_attributes({ :name => 'Josh', :is_admin => true }) # Raises an ActiveModel::MassAssignmentSecurity::Error
user.assign_attributes({ :name => 'Bob'})
user.name        # => "Bob"
user.is_admin?   # => false
user.new_record? # => true
Ajedi32
la source
Votre exemple est un peu trompeur car vous n'avez pas collé cette ligne du modèle attr_accessible :is_admin, :as => :admin:;)
Robin
@Robin Ou tout simplement: attr_protected :is_admin. Ou: attr_accessible :nameLe point étant que dans cet exemple,: is_admin est protégé. Je dois également noter que tenter d'attribuer en masse un attribut protégé avec .assign_attributessoulève effectivement un ActiveModel::MassAssignmentSecurity::Error, même si cela n'est pas illustré dans l'exemple.
Ajedi32
Oui, mais ma réplique provient du document auquel vous avez lié. Je dis juste que vous auriez dû copier / coller tout l'exemple. Mais oui, vous pouvez simplement dire qu'il est protégé.
Robin
@Robin Je mettrai à jour l'exemple pour être un peu plus précis. L'exemple dans les documents est également un peu trompeur, car il ne mentionne pas que user.assign_attributes({ :name => 'Josh', :is_admin => true })déclenche un message d'erreur et ne définit pas réellement la propriété de nom de l'utilisateur.
Ajedi32
7
assign_attributes est disponible à partir de Rails 3.1, vous ne pouvez donc pas l'utiliser si vous utilisez toujours une ancienne version de Rails.
Haegin
174

Vous pouvez utiliser assign_attributesou attributes=(ce sont les mêmes)

Mise à jour des méthodes de triche (pour Rails 6):

  • update= assign_attributes+save
  • attributes= = alias de assign_attributes
  • update_attributes = obsolète, alias de update

Source:
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/persistence.rb
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/attribute_assignment .rb

Une autre feuille de triche:
http://www.davidverhasselt.com/set-attributes-in-activerecord/#cheat-sheet

Yarin
la source
1
Clair et court. Merci.
freemanoid
1
en cas de .attributes = val, si votre modèle has_one et accepte_nested_attributes_pour un autre modèle, le passage de that_model_attributes (sans id) supprimera le modèle has_one existant, même si vous n'avez pas persisté (par exemple, enregistrer). Mais assign_attributes ne se comporte pas comme ça.
ClassyPimp
65

Vous pouvez utiliser la méthode des «attributs»:

@car.attributes = {:model => 'Sierra', :years => '1990', :looks => 'Sexy'}

Source: http://api.rubyonrails.org/classes/ActiveRecord/Base.html

attributes = (new_attributes, guard_protected_attributes = true) Vous permet de définir tous les attributs à la fois en passant un hachage avec des clés correspondant aux noms d'attributs (qui correspondent à nouveau aux noms de colonne).

Si guard_protected_attributes a la valeur true (valeur par défaut), les attributs sensibles peuvent être protégés de cette forme d'affectation en masse à l'aide de la macro attr_protected. Ou vous pouvez également spécifier les attributs accessibles avec la macro attr_accessible. Ensuite, tous les attributs non inclus ne seront pas autorisés à être affectés en masse.

class User < ActiveRecord::Base
  attr_protected :is_admin
end

user = User.new
user.attributes = { :username => 'Phusion', :is_admin => true }
user.username   # => "Phusion"
user.is_admin?  # => false

user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
user.is_admin?  # => true
bruno077
la source
7

Pour affecter en masse des valeurs à un modèle ActiveRecord sans enregistrer, utilisez les méthodes assign_attributesou attributes=. Ces méthodes sont disponibles dans Rails 3 et versions ultérieures. Cependant, il y a des différences mineures et des problèmes liés à la version à connaître.

Les deux méthodes suivent cette utilisation:

@user.assign_attributes{ model: "Sierra", year: "2012", looks: "Sexy" }

@user.attributes = { model: "Sierra", year: "2012", looks: "Sexy" }

Notez qu'aucune des méthodes n'effectuera de validations ou exécutera de rappels; les rappels et la validation auront lieu lors de l' saveappel.

Rails 3

attributes=diffère légèrement de assign_attributesdans Rails 3. attributes=vérifiera que l'argument qui lui est passé est un Hash, et retourne immédiatement s'il ne l'est pas; assign_attributesn'a pas un tel contrôle Hash. Consultez la documentation de l'API d'attribution d'attributs ActiveRecord pourattributes= .

Le code non valide suivant échouera silencieusement en retournant simplement sans définir les attributs:

@user.attributes = [ { model: "Sierra" }, { year: "2012" }, { looks: "Sexy" } ]

attributes= se comportera silencieusement comme si les affectations avaient été effectuées avec succès, alors qu'en réalité, elles ne l'ont pas été.

Ce code non valide assign_attributeslèvera une exception lors d'une tentative de stringification des clés de hachage du tableau englobant:

@user.assign_attributes([ { model: "Sierra" }, { year: "2012" }, { looks: "Sexy" } ])

assign_attributesdéclenchera une NoMethodErrorexception pour stringify_keys, indiquant que le premier argument n'est pas un hachage. L'exception elle-même n'est pas très informative sur la cause réelle, mais le fait qu'une exception se produit est très important.

La seule différence entre ces cas est la méthode utilisée pour l'affectation en masse: attributes=réussit en silence et assign_attributesdéclenche une exception pour informer qu'une erreur s'est produite.

Ces exemples peuvent sembler artificiels, et ils le sont dans une certaine mesure, mais ce type d'erreur peut facilement se produire lors de la conversion de données à partir d'une API, ou même simplement en utilisant une série de transformation de données et en oubliant Hash[]les résultats de la finale .map. Conservez quelques lignes de code 50 ci-dessus et 3 fonctions supprimées de votre attribution d'attribut, et vous avez une recette pour l'échec.

La leçon avec Rails 3 est la suivante: utilisez toujoursassign_attributes au lieu de attributes=.

Rails 4

Dans Rails 4, attributes=est simplement un alias pour assign_attributes. Consultez la documentation de l'API d'attribution d'attributs ActiveRecord pourattributes= .

Avec Rails 4, les deux méthodes peuvent être utilisées de manière interchangeable. Le fait de ne pas passer un hachage comme premier argument entraînera une exception très utile:ArgumentError: When assigning attributes, you must pass a hash as an argument.

Validations

Si vous pré-volez des affectations en préparation à un save, vous pourriez également être intéressé par la validation avant l'enregistrement. Vous pouvez utiliser les méthodes valid?et invalid?pour cela. Les deux renvoient des valeurs booléennes. valid?renvoie true si le modèle non enregistré passe toutes les validations ou false dans le cas contraire. invalid?est tout simplement l'inverse devalid?

valid? peut être utilisé comme ceci:

@user.assign_attributes{ model: "Sierra", year: "2012", looks: "Sexy" }.valid?

Cela vous donnera la possibilité de gérer tous les problèmes de validation avant d'appeler save.

Michael Gaskill
la source