validates_uniqueness_of in distrutti rotaie modello nidificati
-
03-10-2019 - |
Domanda
Ho un modello di progetto che accetta gli attributi nidificati per Task.
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
convalida unicità nel modello Task dà problema durante l'aggiornamento del progetto.
In Modifica del progetto elimino un T1 compito e quindi aggiungere una nuova attività con lo stesso nome T1, convalida l'unicità limita il risparmio di progetto.
params hash aspetto qualcosa di simile
task_attributes => { {"id" =>
"1","name" => "T1", "_destroy" =>
"1"},{"name" => "T1"}}
La convalida il compito è fatto prima di distruggere il vecchio compito. Da qui la convalida fails.Any idea di come convalidare tale che essa non considera compito di essere distrutto?
Soluzione
Andrew France ha creato una patch in questa filo , in cui la validazione è fatto in memoria.
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
Altri suggerimenti
A quanto mi risulta, l'approccio di Reiner sulla convalida in memoria non sarebbe pratico nel mio caso, come ho un sacco di "libri", 500K e in crescita. Questo sarebbe un grande successo se si vuole portare tutto in memoria.
La soluzione mi è venuta è quello di:
Inserisci la condizione unicità nel database (che ho trovato è sempre una buona idea, come nella mia esperienza Rails non sempre fare un lavoro bene qui) aggiungendo quanto segue al file di migrazione in db / migrate /:
add_index :tasks [ :project_id, :name ], :unique => true
Nel controllore, posizionare il Salva o update_attributes all'interno di una transazione, e salvare l'eccezione del database. Per es.,
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
Per Rails 4.0.1, questo problema viene contrassegnato come fissato dalla presente richiesta di pull, https: //github.com/rails/rails/pull/10417
Se si dispone di una tabella con un indice di campo univoco, e segnare un record per la distruzione, e si crea un nuovo record con lo stesso valore come il campo univoco, quindi quando si chiama salvare, un indice univoco a livello di database sarà gettato errore.
non lo fa personalmente questo ancora lavoro per me, quindi non credo che sia ancora completamente risolto.
La risposta di Rainer Blessing è buona. Ma è meglio quando siamo in grado di segnare quali compiti sono duplicati.
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
Perché non si usa: portata ??p>
class Task < ActiveRecord::Base
validates_uniqueness_of :name, :scope=>'project_id'
end
questo creerà unica operazione per ogni progetto.