Tengo los modelos Account y User . Ambos modelos tienen un atributo de email .
Una cuenta has_many :users y un usuario belongs_to :account
Me gustaría validar la unicidad del email en ambos modelos cuando se crea una Account , por lo que el correo electrónico de la Account no es válido si lo toma un User (ya que el correo electrónico de la cuenta luego se convierte en el correo electrónico del usuario administrador).
Agregué un alcance a la restricción de email en el modelo Account pero no funciona (el formulario no se rechaza).
Modelo de cuenta:
has_many :users validates :email, uniqueness: { scope: :users, case_sensitive: false }¿Cuál es la forma correcta de implementar esto? ¿Necesito agregar un índice a la base de datos?
Una validación a nivel de aplicación en realidad no garantiza la unicidad de sus datos; simplemente detecta muchos de los casos en los que los usuarios intentan ingresar duplicados y proporciona comentarios.
Entonces, si bien podría implementar ingenuamente una validación personalizada que consulte ambas tablas, ni siquiera sobrevivirá a la condición de carrera causada por un ciudadano mayor que hace doble clic o cualquier método de inserción que eluda las validaciones.
Dado que no hay forma (AFAIK) de crear índices en las tablas, es posible que desee reestructurar su dominio y agregar un "propietario" (llámelo como quiera) a la tabla de cuentas:
class AddOwnerToAccounts < ActiveRecord::Migration[6.1] def change add_reference :accounts, :owner, null: false, foreign_key: { to_table: 'users' } end end class Account < ApplicationRecord has_many :users belongs_to :owner, class_name: 'User', inverse_of: :owned_accounts delegate :email, to: :owner end class User < ApplicationRecord belongs_to :account has_many :owned_accounts, class_name: 'Account', foreign_key: :owner_id, inverse_of: :owner endEste es un método alternativo basado en una tabla separada y una asociación polimórfica:
class CreateEmailAddresses < ActiveRecord::Migration[6.1] def change create_table :email_addresses do |t| t.string :email, unique: true t.references :entitity, polymorphic: true t.timestamps end add_index :email_addresses, [:entity_type, :entity_id], unique: true end end class EmailAddress < ApplicationRecord validates_uniqueness_of :email validates_uniqueness_of :entity_id, scope: :entity_type belongs_to :entity, polymorphic: true end class User < ApplicationRecord has_one :email_address, as: :entity, dependent: :destroy delegate :email, to: :email_address accepts_nested_attributes_for :email_address end class Account < ApplicationRecord has_one :email_address, as: :entity, dependent: :destroy delegate :email, to: :email_address accepts_nested_attributes_for :email_address endEvita tener que reestructurar su dominio o crear un usuario simplemente para crear un correo electrónico, pero causará problemas con las bibliotecas de autenticación como Devise, además de la falta de una clave externa real para garantizar la integridad referencial, lo que puede generar registros huérfanos.
En mi opinión, no es una gran solución, ya que lo más probable es que cree tantos problemas como los que resuelve.
Esto solo se puede lograr con un método de validación personalizado .
validate :email_unique_for_account_and_user private def email_unique_for_account_and_user if User.where(email: email).where.not(id: id).exists? || Account.where(email: email).where.not(id: id).exists? errors.add(:email, :taken) end endEn mi opinión, no es posible definir un índice único en varias tablas de bases de datos.