Evitar que Laravel agregue múltiples registros a una tabla dinámica

6 minutos de lectura

avatar de usuario
Alabama_

Tengo una relación de muchos a muchos configurada y funcionando, para agregar un artículo al carrito que uso:

$cart->items()->attach($item);

Lo que agrega un elemento a la tabla dinámica (como debería), pero si el usuario vuelve a hacer clic en el enlace para agregar un elemento que ya ha agregado, crea una entrada duplicada en la tabla dinámica.

¿Existe una forma integrada de agregar un registro a una tabla dinámica solo si aún no existe?

De lo contrario, ¿cómo puedo verificar la tabla dinámica para saber si ya existe un registro coincidente?

avatar de usuario
barryvdh

También puede utilizar el $model->sync(array $ids, $detaching = true) y deshabilite la separación (el segundo parámetro).

$cart->items()->sync([$item->id], false);

Actualización: desde Laravel 5.3 o 5.2.44, también puede llamar a syncWithoutDetaching:

$cart->items()->syncWithoutDetaching([$item->id]);

Que hace exactamente lo mismo, pero más legible 🙂

  • El segundo parámetro booleano para evitar la separación de ID no incluidos en la lista no se encuentra en la documentación principal de L5. Es muy bueno saberlo: parece ser la única forma de “adjuntar si aún no está adjunto” en una sola declaración.

    – Jasón

    12 de abril de 2016 a las 11:57

  • Esto debería aceptarse como la respuesta, es una forma mucho mejor de hacerlo que la respuesta aceptada.

    – Daniel

    9 mayo 2016 a las 21:25

  • Para novatos como yo: $cart->items()->sync([1, 2, 3]) construirá relaciones de muchos a muchos con los id en la matriz dada, 1, 2y 3, y elimine (o “separe”) todas las demás identificaciones que no estén en la matriz. De esta manera, solo existirán en la tabla los id dados en la matriz. Brilliant @Barryvdh usa un segundo parámetro no documentado para deshabilitar esa separación, por lo que no se eliminan las relaciones que no están en la matriz dada, pero solo se adjuntarán las identificaciones únicas. Consulte el documento “sincronización para conveniencia”. (Laravel 5.2)

    – identicon

    02/08/2016 a las 19:04


  • FYI, actualicé los documentos, por lo que 5.2 y superior: laravel.com/docs/5.2/… Y Taylor inmediatamente agregó add syncWithoutDetaching()que llama a sync() con false como segundo parámetro.

    – Barryvdh

    19 de agosto de 2016 a las 17:21

  • Laravel 5.5 laravel.com/docs/5.5/… syncWithoutDetaching() ¡trabajó!

    –Josh LaMar

    26 de septiembre de 2017 a las 14:26

avatar de usuario
Alejandro Butynski

Puede verificar la presencia de un registro existente escribiendo una condición muy simple como esta:

if (! $cart->items->contains($newItem->id)) {
    $cart->items()->save($newItem);
}

O/y puede agregar una condición de unicidad en su base de datos, arrojaría una excepción durante un intento de guardar un doblete.

También debería echar un vistazo a la respuesta más directa de Barryvdh.

  • El parámetro $id para el método attach() es mixto, puede ser un int o una instancia de modelo;) – ver github.com/laravel/framework/blob/master/src/Illuminate/…

    – Rob Gordin

    5 de julio de 2013 a las 8:08


  • @bagusflyer contains instrucción comprueba si la clave está presente en un objeto de la colección. Deberías tener un error en tu código.

    – Alejandro Butynski

    19 de mayo de 2014 a las 9:23

  • No me gusta esta solución porque fuerza una consulta adicional ($cart->items) He estado haciendo algo como: $cart->items()->where('foreign_key', $foreignKey)->count() Lo cual, bueno, en realidad también realiza una consulta adicional ‘^^ Pero no necesito recuperar e hidratar toda la colección a menos que realmente la necesite.

    – Duilio

    24 mayo 2015 a las 13:57


  • Así es, su solución está un poco más optimizada. Incluso puedes usar el exists() función en lugar de count() para la mejor optimización.

    – Alejandro Butynski

    26 mayo 2015 a las 13:25

  • y ¿qué pasa cuando estás eliminando algunas relaciones? esto solo agrega nuevas relaciones

    – Alireza

    22/09/2016 a las 15:56

El método @alexandre Butynsky funciona muy bien pero usa dos consultas sql.

Uno para verificar si el carrito contiene el artículo y otro para guardar.

Para usar solo una consulta, use esto:

try {
    $cart->items()->save($newItem);
}
catch(\Exception $e) {}

  • Esto es lo que hice en mi proyecto. Pero el problema es que no sabe si esta excepción es causada por la entrada duplicada o cualquier otra cosa.

    – Bagusflyer

    19 de mayo de 2014 a las 1:32

  • ¿Por qué arrojaría alguna excepción? sería si hay alguna clave única

    – Seiji Manoan

    12 de agosto de 2015 a las 19:28

avatar de usuario
adeguk Loggcity

Si bien todas estas respuestas son buenas porque las probé todas, una cosa aún queda sin respuesta o no se soluciona: el problema de actualizar un valor previamente verificado (sin marcar la casilla[es]). Tengo algo similar a la pregunta anterior, espero que quiera marcar y desmarcar las características de los productos en mi tabla de características del producto (la tabla dinámica). Soy un novato y me he dado cuenta de que ninguno de los anteriores hizo eso. Ambos son buenos cuando se agregan nuevas funciones, pero no cuando quiero eliminar funciones existentes (es decir, desmarcarlo)

Agradeceré cualquier aclaración sobre esto.

$features = $request->get('features');

if (isset($features) && Count($features)>0){
    foreach ($features as $feature_id){
        $feature = Feature::whereId($feature_id)->first();
        $product->updateFeatures($feature);
    }
}

//product.php (extract)
public function updateFeatures($feature) {
        return $this->features()->sync($feature, false);
}

o

public function updateFeatures($feature) {
   if (! $this->features->contains($features))
        return $this->features()->attach($feature);
}
//where my attach() is:
public function addFeatures($feature) {
        return $this->features()->attach($feature);
}

Lo siento chicos, no estoy seguro de si debería eliminar la pregunta porque al haber descubierto la respuesta yo mismo, suena un poco estúpido, bueno, la respuesta a lo anterior es tan simple como trabajar @Barryvdh sync() de la siguiente manera; habiendo leído más y más sobre:

$features = $request->get('features');
if (isset($features) && Count($features)>0){
    $product->features()->sync($features);
}

Ya hay algunas excelentes respuestas publicadas. Sin embargo, también quería lanzar este aquí.

Las respuestas de @AlexandreButynski y @Barryvdh son más legibles que mi sugerencia, lo que agrega esta respuesta es algo de eficiencia.

Recupera solo las entradas para la combinación actual (en realidad solo la identificación) y luego las adjunta si no existe. El método de sincronización (incluso sin desvincularse) recupera todos los ID adjuntos actualmente. Para conjuntos más pequeños con pequeñas iteraciones, esto difícilmente será una diferencia, … entiendes mi punto.

De todos modos, definitivamente no es tan legible, pero funciona.

if (is_null($book->authors()->find($author->getKey(), [$author->getQualifiedKeyName()])))
    $book->authors()->attach($author);

  • Esto es mucho más eficaz cuando se trabaja con relaciones grandes. Nuestro servidor se estaba quedando sin memoria al usar los otros métodos

    –Jeff Davis

    02/09/2021 a las 21:25

  • Esto es mucho más eficaz cuando se trabaja con relaciones grandes. Nuestro servidor se estaba quedando sin memoria al usar los otros métodos

    –Jeff Davis

    02/09/2021 a las 21:25

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad