validates_uniqueness_of en carriles modelo anidados destruidas
-
03-10-2019 - |
Pregunta
Tengo un modelo de proyecto que acepta atributos anidados para la tarea.
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
La unicidad de validación en el modelo de tareas da un problema durante la actualización del proyecto.
En edición del proyecto elimino una tarea T1 y luego añadir una nueva tarea con el mismo nombre T1, la singularidad de validación restringe el ahorro de proyecto.
params mirada de hash algo así como
task_attributes => { {"id" =>
"1","name" => "T1", "_destroy" =>
"1"},{"name" => "T1"}}
Validación en la tarea se realiza antes de la destrucción de la antigua tarea. De ahí la idea de validación fails.Any cómo validar tal que no se considera tarea a ser destruido?
Solución
Andrew Francia creó un parche en este hilo , donde la validación se realiza en la 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
Otros consejos
A mi entender, el enfoque de Reiner sobre la validación en la memoria no sería práctico en mi caso, ya que tengo un montón de "libros", 500K y en crecimiento. Eso sería un gran éxito si se quiere llevar a todos en la memoria.
La solución que se me ocurrió es:
Coloque la condición de unicidad en la base de datos (que he encontrado es siempre una buena idea, como en mi experiencia Carriles no siempre hacer un trabajo bueno aquí) añadiendo lo siguiente a su archivo de migración en db / migrate /:
add_index :tasks [ :project_id, :name ], :unique => true
En el controlador, coloque la parada o update_attributes dentro de una transacción, y rescatar la excepción de base de datos. Por ejemplo.,
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
Para rieles 4.0.1, este número se marca como siendo fijado por la presente solicitud de extracción, https: //github.com/rails/rails/pull/10417
Si usted tiene una tabla con un índice de campo único, y marcar un registro para la destrucción, y se genera un nuevo registro con el mismo valor que el campo único, a continuación, cuando se llama a salvar, un índice único nivel de base de datos será lanzado de error.
En lo personal esto todavía no funciona para mí, así que no creo que se fija por completo todavía.
La respuesta de Rainer Bendición es buena. Pero es mejor cuando podemos marcar las tareas que se duplican.
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
¿Por qué no utilizar: ámbito de aplicación
class Task < ActiveRecord::Base
validates_uniqueness_of :name, :scope=>'project_id'
end
Esto creará tarea única para cada proyecto.