validates_uniqueness_of dans détruits rails modèles imbriqués
-
03-10-2019 - |
Question
J'ai un modèle de projet qui accepte les attributs imbriqués pour la tâche.
class Project < ActiveRecord::Base
has_many :tasks
accepts_nested_attributes_for :tasks, :allow_destroy => :true
end
class Task < ActiveRecord::Base
validates_uniqueness_of :name end
Validation Unicité dans le modèle de travail donne problème lors de mise à jour du projet.
En édition de projet supprimer une tâche T1, puis ajouter une nouvelle tâche avec le même nom T1, validation unique limite l'économie du projet.
params hachage ressemble à quelque chose comme
task_attributes => { {"id" =>
"1","name" => "T1", "_destroy" =>
"1"},{"name" => "T1"}}
Validation sur la tâche se fait avant de détruire la tâche ancienne. Par conséquent idée de validation fails.Any comment valider telle qu'elle ne considère pas la tâche à détruire?
La solution
Andrew France a créé un patch dans cette fil , où la validation se fait en mémoire.
class Author
has_many :books
# Could easily be made a validation-style class method of course
validate :validate_unique_books
def validate_unique_books
validate_uniqueness_of_in_memory(
books, [:title, :isbn], 'Duplicate book.')
end
end
module ActiveRecord
class Base
# Validate that the the objects in +collection+ are unique
# when compared against all their non-blank +attrs+. If not
# add +message+ to the base errors.
def validate_uniqueness_of_in_memory(collection, attrs, message)
hashes = collection.inject({}) do |hash, record|
key = attrs.map {|a| record.send(a).to_s }.join
if key.blank? || record.marked_for_destruction?
key = record.object_id
end
hash[key] = record unless hash[key]
hash
end
if collection.length > hashes.length
self.errors.add_to_base(message)
end
end
end
end
Autres conseils
Si je comprends bien, l'approche de Reiner sur la validation en mémoire ne serait pas pratique dans mon cas, comme je l'ai beaucoup de « livres », 500K et de plus en plus. Ce serait un grand succès si vous voulez apporter tout en mémoire.
La solution que je suis venu avec est à:
Placez la condition unique dans la base de données (que j'ai trouvé est toujours une bonne idée, comme dans mon expérience Rails ne fait pas toujours un bon travail ici) en ajoutant ce qui suit à votre fichier de migration dans db / migrate /:
add_index :tasks [ :project_id, :name ], :unique => true
Dans le contrôleur, placez la sauvegarde ou update_attributes dans une transaction, et sauver l'exception de base de données. Par exemple.
def update
@project = Project.find(params[:id])
begin
transaction do
if @project.update_attributes(params[:project])
redirect_to(project_path(@project))
else
render(:action => :edit)
end
end
rescue
... we have an exception; make sure is a DB uniqueness violation
... go down params[:project] to see which item is the problem
... and add error to base
render( :action => :edit )
end
end
end
Pour Rails 4.0.1, cette question est marqué comme étant fixé par cette demande de traction, https: //github.com/rails/rails/pull/10417
Si vous avez une table avec un index de champ unique, et vous marquez un enregistrement pour la destruction, et vous construisez un nouveau record avec la même valeur que la champ unique, puis lorsque vous appelez enregistrer, un niveau de base de données index unique erreur sera levée.
Personnellement, cela ne fonctionne toujours pas pour moi, donc je ne pense pas qu'il est encore complètement fixé.
La réponse de Rainer Blessing est bonne. Mais il vaut mieux quand on peut marquer les tâches sont dupliqués.
class Project < ActiveRecord::Base
has_many :tasks, inverse_of: :project
accepts_nested_attributes_for :tasks, :allow_destroy => :true
end
class Task < ActiveRecord::Base
belongs_to :project
validates_each :name do |record, attr, value|
record.errors.add attr, :taken if record.project.tasks.map(&:name).count(value) > 1
end
end
Pourquoi utilisez-vous pas: champ
class Task < ActiveRecord::Base
validates_uniqueness_of :name, :scope=>'project_id'
end
cela va créer la tâche unique pour chaque projet.