2009-05-28 14 views
48

Supongamos la siguiente migración DB en Ruby:¿Cómo se valida la unicidad de un par de ID en Ruby on Rails?

 
    create_table :question_votes do |t| 
     t.integer :user_id 
     t.integer :question_id 
     t.integer :vote 

     t.timestamps 
    end 

Supongamos además que deseo que las filas de la DB contienen (user_id, question_id) pares únicos. ¿Cuál es el polvo adecuado para poner en el modelo para lograr eso?

validates_uniqueness_of :user_id, :question_id
parece hacer que las filas sean únicas por ID de usuario y únicas por ID de pregunta, en lugar de exclusivas por el par.

+1

Nota: No he vuelto a este proyecto, por lo que no he tenido tiempo de probar las respuestas a continuación. Si alguien publica una respuesta que tiene la prueba más corta posible que demuestre una respuesta y el resultado de esa prueba, aceptaré esa respuesta. Gracias. – dfrankow

Respuesta

85
validates_uniqueness_of :user_id, :scope => [:question_id] 

si necesita incluir otra columna (o más), puede agregarla al alcance también. Ejemplo:

validates_uniqueness_of :user_id, :scope => [:question_id, :some_third_column] 
+0

¿debe el valor estar entre corchetes, sin llaves? – ahnbizcad

+3

@gwho ** Los corchetes ** denotan una ** matriz, una secuencia de valores **. ** Las llaves [**] indican un ** hash, un conjunto de pares clave-valor **. No estamos especificando claves aquí, solo valores. Entiendo que es un poco tarde y probablemente ya lo sepas, pero podría ayudar a los principiantes a venir aquí :) –

+0

Oh, no es un alcance lambda. – ahnbizcad

10

Excepto para escribir su propio método de validación, lo mejor que podría hacer con validates_uniqueness_of es la siguiente:

validates_uniqueness_of :user_id, :scope => "question_id" 

Esto comprobará que el user_id es único dentro de todas las filas con el mismo question_id como el registro que está intentando insertar.

Pero eso no es lo que quiere.

Creo que está buscando la combinación de :user_id y :question_id para ser único en toda la base de datos.

En ese caso lo que necesita hacer dos cosas:

  1. Danos tu método de validación.
  2. Cree una restricción en la base de datos porque todavía existe la posibilidad de que su aplicación procese dos registros al al mismo tiempo.
+0

¿Qué sucede si hago

 validate_uniqueness_of :user_id, :scope => :question_id validate_uniqueness_of :question_id, :scope => :user_id 
¿Es eso suficiente? – dfrankow

+0

No veo la diferencia entre 'validates_uniqueness_of' usando' scope' y la combinación es única. ¿Puedes darnos un ejemplo de cómo diferirían? – Leopd

25

Si usa mysql, puede hacerlo en la base de datos utilizando un índice único. Es algo así como:

add_index :question_votes, [:question_id, :user_id], :unique => true 

Esto va a lanzar una excepción cuando intenta guardar una combinación duplicado en marcha de question_id/user_id, por lo que tendrá que experimentar y averiguar qué excepción a capturar y manejar.

+0

Sí, pero la excepción no ayuda cuando solo desea un mensaje de error. – shingara

15

De RailsGuides. validates también funciona:

class QuestionVote < ActiveRecord::Base 
    validates :user_id, :uniqueness => { :scope => :question_id } 
end 
+0

Necesita actualización, enlace roto. –

+0

Puede editar, usted sabe –

9

La mejor manera es usar ambos, ya que los carriles no es 100% fiable cuando singularidad de validación vienen a través.

que puede utilizar:

validates :user_id, uniqueness: { scope: :question_id } 

y fuera el 100% en el lado seguro, añadir esta validación en su base de datos (MySQL ex)

add_index :question_votes, [:user_id, :question_id], unique: true 

y entonces se puede manejar en su controlador usando:

rescue ActiveRecord::RecordNotUnique 

Así que ahora está 100% seguro de que usted no tendrá un valor duplicado :)

+1

Esta debería ser la mejor respuesta, valida solo no es suficiente. Consulte este artículo para conocer los detalles de por qué: https://robots.thoughtbot.com/the-perils-of-uniqueness-validations – Eugene

2

Cuando está creando un nuevo registro, eso no funciona porque el id de su modelo principal aún no existe en el momento de las validaciones.

Esto debería funcionar para usted.

class B < ActiveRecord::Base 
    has_many :ab 
    has_many :a, :through => :ab 
end 

class AB < ActiveRecord::Base 
    belongs_to :b 
    belongs_to :a 
end 

class A < ActiveRecord::Base 
    has_many :ab 
    has_many :b, :through => :ab 

    after_validation :validate_uniqueness_b 

    private 
    def validate_uniqueness_b 
    b_ids = ab.map(&:b_id) 
    unless b_ids.uniq.length.eql? b_ids.length 
     errors.add(:db, message: "no repeat b's") 
    end 
    end 
end 

En el código anterior me sale todo b_id de colección de parámetros, a continuación, comparar si la longitud entre los valores únicos y b_id obtenidos son iguales.
Si son iguales significa que no hay repetición b_id.

Nota: no olvide agregar el único en las columnas de su base de datos.