ruby-on-rails nested-forms validates-uniqueness-of

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.