2009-05-19 12 views
28
create_table :categories_posts, :id => false do |t| 
    t.column :category_id, :integer, :null => false 
    t.column :post_id, :integer, :null => false 
end 

tengo una tabla de unión (como anteriormente) con columnas que hacen referencia a una correspondiente categorías mesa y unas mensajes de mesa. Quería imponer una restricción única en la clave compuesta category_id, post_id en categories_posts join table. Pero Rails no es compatible con esto (creo).En una tabla de combinación, ¿cuál es la mejor solución para la ausencia de una clave compuesta de Rails?

Para evitar la posibilidad de filas duplicadas en mis datos con la misma combinación de category_id y post_id, ¿cuál es la mejor solución para la ausencia de una clave compuesta en Rails?

Mis supuestos aquí:

  1. El número predeterminado de Auto-columna (id: entero) no haría nada para proteger mis datos en esta situación.
  2. ActiveScaffold puede proporcionar una solución pero no estoy seguro de si es una exageración incluirlo en mi proyecto simplemente por esta única función , sobre todo si hay una respuesta más elegante.

Respuesta

33

Agregue un índice único que incluya ambas columnas. Eso evitará que inserte un registro que contenga un par duplicado category_id/post_id.

add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post' 
+0

Gracias. Después de leer varias publicaciones de blog, pensé que no era posible un índice compuesto y que esta sintaxis no existía. –

+0

Esto dará una experiencia de interfaz de usuario pobre si el usuario intenta ingresar un registro duplicado. –

+1

@Larry: ¿No podría seguir utilizando la lógica de validación de su respuesta y combinarla con esta sintaxis de Rails para índices compuestos? –

5

que aplicar ambos de los siguientes cuando tengo este problema en rieles:

1) Usted debe tener un índice compuesto único declarado en el nivel de base de datos para asegurarse de que el DBMS no permitirán que un duplicado registro creado

2) Proporcionar Mensajes de error más suaves que simplemente lo anterior, añadir una validación para el modelo de rieles:

validates_each :category_id, :on => :create do |record, attr, value| 
    c = value; p = record.post_id 
    if c && p && # If no values, then that problem 
       # will be caught by another validator 
    CategoryPost.find_by_category_id_and_post_id(c, p) 
    record.errors.add :base, 'This post already has this category' 
    end 
end 
+0

Según la respuesta de tvanoffson, un índice compuesto es de hecho posible y él ha proporcionado la sintaxis para ello. En ese caso, su sugerencia de declarar el índice compuesto en el nivel de la base de datos (si bien es una buena sugerencia) sería innecesario. Mi suposición era que los índices compuestos no eran posibles en Rails, pero quizás era incorrecto. –

+2

La solución de Tvanfosson es la misma que mi número 1: el índice se está declarando en el nivel db, no en el nivel Rails o Ruby. Los raíles solo podrán informar un "error de SQL" cuando se envíe un dup. Es por eso que también quiere validar en el modelo (a nivel de Rails). –

+0

Cuando dijo "en el nivel de la base de datos", pensé que tenía la intención de crearlo en la base de datos sin usar el método add_index en Rails. La razón de mi pregunta era que no sabía que los índices compuestos eran posibles. Pero no veo por qué no puedo agregar su lógica de validación a la sintaxis de la respuesta de tvanoffson. Lo siento si eso es lo que pretendías. Pero no entendí eso de tu respuesta. –

9

creo que se puede encontrar más fácil para validar singularidad de uno de los campos con la otra como una alcance:

de la API:

validates_uniqueness_of(*attr_names) 

valida si el valor de los atributos especificados son únicos en todo el sistema. Útil para asegurarse de que solo un usuario pueda llamarse "davidhh".

class Person < ActiveRecord::Base 
    validates_uniqueness_of :user_name, :scope => :account_id 
    end 

También se puede validar si el valor de los atributos especificados es único en función de varios parámetros de ámbito. Por ejemplo, asegurándose de que un maestro solo puede estar en el horario una vez por semestre para una clase en particular.

class TeacherSchedule < ActiveRecord::Base 
    validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id] 
    end 

Cuando se crea el registro, se realiza una comprobación para asegurarse de que no existe ningún registro en la base de datos con el valor dado para el atributo especificado (que se asigna a una columna). Cuando se actualiza el registro, se realiza la misma comprobación, pero sin tener en cuenta el registro en sí.

opciones de configuración:

* message - Specifies a custom error message (default is: "has already been taken") 
* scope - One or more columns by which to limit the scope of the uniquness constraint. 
* case_sensitive - Looks for an exact match. Ignored by non-text columns (true by default). 
* allow_nil - If set to true, skips this validation if the attribute is null (default is: false) 
* if - Specifies a method, proc or string to call to determine if the validation should occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The method, proc or string should return or evaluate to a true or false value. 
+0

Esto es genial. Gracias. –

+0

como dijo @izap en su respuesta: la validación no es infalible debido a una posible condición de carrera entre las consultas SELECT e INSERT/UPDATE. –

1

una solución se puede añadir tanto el índice como la validación en el modelo.

Así que en la migración tiene: add_index: categories_posts, [: category_id,: post_id],: unique => true

Y en el modelo: validates_uniqueness_of: category_id,: Ámbito => [: category_id ,: post_id] validates_uniqueness_of: post_id,: ámbito => [: category_id,: post_id]

8

es muy difícil a recomendar el enfoque "correcto".

1) El enfoque pragmático

Uso validador y no añada índice compuesto único. Esto le da buenos mensajes en la interfaz de usuario y simplemente funciona.

class CategoryPost < ActiveRecord::Base 
    belongs_to :category 
    belongs_to :post 

    validates_uniqueness_of :category_id, :scope => :post_id, :message => "can only have one post assigned" 
end 

También puede agregar dos índices separados en sus tablas de unión para acelerar las búsquedas:

add_index :categories_posts, :category_id 
add_index :categories_posts, :post_id 

Tenga en cuenta (según el libro carriles de 3 vías) la validación no es infalible, porque de una posible condición de carrera entre las consultas SELECT e INSERT/UPDATE. Se recomienda utilizar una restricción única si debe estar absolutamente seguro de que no hay registros duplicados.

2) El enfoque de balas

En este enfoque que queremos poner una restricción en el nivel de base de datos. Por lo que significa crear un índice compuesto:

add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post' 

gran ventaja es una gran base de datos de la integridad, la desventaja no es muy útil informar al usuario de error. Tenga en cuenta que al crear un índice compuesto, el orden de las columnas es importante.

Si coloca columnas menos selectivas como columnas delanteras en el índice y pone la mayoría de las columnas selectivas al final, otras consultas que tienen condiciones en columnas de índice no líderes también pueden aprovechar INDEX SKIP SCAN. Es posible que necesite agregar un índice más para aprovecharlos, pero esto depende en gran medida de la base de datos.

3) combinación de ambos

Uno puede leer acerca de la combinación de ambos, pero me suelen gustar sólo el número uno.

+0

Esto se debe considerar el mejor absente, ya que propone el modelo y la información de datos. –

+0

No estoy de acuerdo con la recomendación de usar solo el número uno, pero como se mencionan todos los pros y contras, esto aún merece un voto positivo. – kronn

Cuestiones relacionadas