Validar a exclusividade de várias colunas

193

Existe uma maneira de validar que um registro real é único e não apenas uma coluna? Por exemplo, um modelo / tabela de amizade não deve ter vários registros idênticos, como:

user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20
re5et
fonte
7
perdoe-me se estou sendo densa, mas como isso ajudaria nessa situação?
re5et 2/02
2
tente usar "validates_uniqueness_of" em seu modelo. se este não funciona a tentar criar um índice em que você pode criar uma migração de feilds que inclui uma declaração como add_index: mesa, [: column_a,: column_b],: unique => true)
Harry Joy
1
@HarryJoy, ele perguntou Is there a rails-way way. E você oferece a ele caminhos não-trilhos, mas padrão. The Active Record way claims that intelligence belongs in your models, not in the database.
Verde
2
Infelizmente, validates :field_name, unique: trueé propenso a condições de corrida, portanto, mesmo contra trilhos, é preferível uma restrição real. @HarryJoy Vou votar uma resposta descrevendo a maneira de restrição.
Pooyan Khosravi
1
melhor resposta, então todo o observado abaixo é um presente stackoverflow.com/a/34425284/1612469 pois traz uma outra camada para ter certeza que tudo irá funcionar corretamente
Aleks

Respostas:

319

Você pode selecionar uma validates_uniqueness_ofchamada da seguinte maneira.

validates_uniqueness_of :user_id, :scope => :friend_id
Dylan Markow
fonte
83
Só queria acrescentar que você pode passar vários parâmetros de escopo, caso precise validar a exclusividade em mais de 2 campos. Ou seja: scope => [: friend_id,: group_id]
Dave Rapin
27
Estranho que você não pode dizer validates_uniqueness_of [:user_id, :friend_id]. Talvez isso precise ser corrigido?
Alexey
12
Alexey, validates_uniqueness_of [: user_id,: friend_id] fará a validação de cada um dos campos listados - e é o comportamento documentado e esperado
Nikita Hismatov
71
No Rails 4, isso se torna: valida: user_id, singularidade: {escopo:: friend_id}
Marina Martin
3
Você provavelmente deseja adicionar um erro personalizado como, por exemplo,: message => 'já tem esse amigo.'
Laffuste 15/05
137

Você pode usar validatespara validar uniquenessem uma coluna:

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

A sintaxe para a validação em várias colunas é semelhante, mas você deve fornecer uma matriz de campos:

validates :attr, uniqueness: {scope: [:attr1, ... , :attrn]}

No entanto , as abordagens de validação mostradas acima têm uma condição de corrida e não podem garantir consistência. Considere o seguinte exemplo:

  1. os registros da tabela de banco de dados devem ser únicos por n campos;

  2. várias ( duas ou mais ) solicitações simultâneas, tratadas por processos separados cada ( servidores de aplicativos, servidores de trabalho em segundo plano ou o que você estiver usando ), acessam o banco de dados para inserir o mesmo registro na tabela;

  3. cada processo em paralelo valida se há um registro com os mesmos n campos;

  4. a validação para cada solicitação é aprovada com êxito e cada processo cria um registro na tabela com os mesmos dados.

Para evitar esse tipo de comportamento, deve-se adicionar uma restrição exclusiva à tabela db. Você pode configurá-lo com o add_indexauxiliar para um (ou vários) campos executando a seguinte migração:

class AddUniqueConstraints < ActiveRecord::Migration
  def change
   add_index :table_name, [:field1, ... , :fieldn], unique: true
  end
end

Advertência : mesmo depois de definir uma restrição exclusiva, duas ou mais solicitações simultâneas tentarão gravar os mesmos dados no banco de dados, mas, em vez de criar registros duplicados, isso criará uma ActiveRecord::RecordNotUniqueexceção, que você deve tratar separadamente:

begin
# writing to database
rescue ActiveRecord::RecordNotUnique => e
# handling the case when record already exists
end 
potashin
fonte