Evitar que Laravel agregue varios registros a una tabla dinámica

98

Tengo una relación de muchos a muchos configurada y en funcionamiento, 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 hace clic en el enlace nuevamente para agregar un elemento que ya ha agregado, crea una entrada duplicada en la tabla dinámica.

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

Si no es así, ¿cómo puedo verificar la tabla dinámica para encontrar si ya existe un registro coincidente?

Alabama_
fuente

Respuestas:

76

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 sencilla de Barryvdh justo debajo.

Alexandre Butynski
fuente
1
El parámetro $ id para el método attach()es mixto, puede ser un int o una instancia del modelo;) - ver github.com/laravel/framework/blob/master/src/Illuminate/…
Rob Gordijn
Gracias @RobGordijn. ¡He aprendido algo hoy! Respuesta editada.
Alexandre Butynski
1
containsDeclaración de @bagusflyer comprueba si la clave está presente en un objeto de la colección. Debería haber un error en su código.
Alexandre Butynski
4
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 buscar y hidratar toda la colección a menos que realmente la necesite.
Silencio
1
Así es, su solución está un poco más optimizada. Incluso puede utilizar la exists()función en lugar de count()para la mejor optimización.
Alexandre Butynski
265

También puede usar el $model->sync(array $ids, $detaching = true)método y deshabilitar 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 :)

Barryvdh
fuente
2
El segundo parámetro booleano para evitar separar ID no listados no se encuentra en la documentación principal de L5. Es muy bueno saberlo: parece ser la única forma de "adjuntar si no está ya adjunto" en una sola declaración.
Jason
11
Esto debe aceptarse como la respuesta, es una manera mucho mejor de hacerlo que la respuesta aceptada
Daniel
4
Para los novatos como yo: $cart->items()->sync([1, 2, 3])construirán-muchos-a-muchos relación a la identificación de la matriz dada, 1, 2, y 3, y eliminar (o "de desconexión") todos los demás ID no está en la matriz. De esta manera, solo los identificadores dados en la matriz existirán en la tabla. 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, sino que solo se adjuntarán las identificaciones únicas. Consulte el documento "Sincronización para mayor comodidad". (Laravel 5.2)
identicon
3
Para su información, actualicé los documentos, por lo que 5.2 y superior: laravel.com/docs/5.2/… Y Taylor agregó inmediatamente agregar syncWithoutDetaching(), que llama a sync () con falso como segundo parámetro.
Barryvdh
Laravel 5.5 laravel.com/docs/5.5/… syncWithoutDetaching() ¡funcionó!
Josh LaMar
2

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

Uno para comprobar 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) {}
Octavio Ruda
fuente
Esto es lo que hice en mi proyecto. Pero el problema es que no sabe si esta excepción se debe a la entrada duplicada o cualquier otra cosa.
Bagusflyer
2
¿Por qué lanzaría alguna excepción? sería si hubiera una clave única
Seiji Manoan
1

Por muy buenas que sean todas estas respuestas porque las probé todas, una cosa aún queda sin respuesta o no se ha solucionado: el problema de actualizar un valor previamente marcado (desmarcó las casillas marcadas). 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)

Apreciaré cualquier esclarecimiento en 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 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 con @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);
}
adeguk Loggcity
fuente
0

Ya se han publicado algunas respuestas excelentes. Sin embargo, también quería tirar este aquí.

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

Recupera solo las entradas para la combinación actual (en realidad solo el id) y luego lo adjunta si no existe. El método de sincronización (incluso sin desconectar) recupera todos los identificadores adjuntos actualmente. Para conjuntos más pequeños con pequeñas iteraciones, esto apenas 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);
Peter de Kok
fuente