ruby on rails - valida_unicidad_de en rieles de modelos anidados destruidos
ruby-on-rails nested-forms (5)
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 validación de unicidad en el modelo de tareas genera problemas al actualizar el proyecto.
En la edición del proyecto, elimino una tarea T1 y luego agrego una nueva tarea con el mismo nombre T1, la validación de exclusividad restringe el guardado de Proyecto.
params hash mirar algo así como
task_attributes => { {"id" =>
"1","name" => "T1", "_destroy" =>
"1"},{"name" => "T1"}}
La validación en la tarea se realiza antes de destruir la tarea anterior. Por lo tanto, la validación falla. ¿Alguna idea de cómo validar de tal manera que no considere que la tarea se destruya?
Andrew France creó un parche en este thread , 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
Como lo entiendo, el enfoque de Reiner sobre la validación en la memoria no sería práctico en mi caso, ya que tengo muchos "libros", 500 K y creciendo. Eso sería un gran éxito si quieres traer todo a la memoria.
La solución que se me ocurrió es:
Coloque la condición de unicidad en la base de datos (lo que encontré es siempre una buena idea, ya que en mi experiencia Rails no siempre hace un buen trabajo aquí) agregando lo siguiente a su archivo de migración en db / migrate /:
add_index :tasks [ :project_id, :name ], :unique => true
En el controlador, coloque los atributos de guardar o actualizar dentro de una transacción y rescatar la excepción de la base de datos. P.ej,
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
fin
La respuesta de Rainer Blessing es buena. Pero es mejor cuando podemos marcar qué tareas están duplicadas.
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
Para Rails 4.0.1, este problema está marcado como corregido por esta solicitud de extracción, https://github.com/rails/rails/pull/10417
Si tiene una tabla con un índice de campo único y marca un registro para su destrucción, y crea un nuevo registro con el mismo valor que el campo único, entonces cuando llame a guardar, se generará un error de índice único a nivel de base de datos.
Personalmente, esto todavía no funciona para mí, así que no creo que esté completamente arreglado todavía.
Ref this
¿Por qué no usas: alcance
class Task < ActiveRecord::Base
validates_uniqueness_of :name, :scope=>''project_id''
end
esto creará una tarea única para cada proyecto.