Estoy intentando crear una instancia y varias instancias relacionadas con una relación de muchos a muchos usando una tabla de unión.
Al crear las múltiples instancias relacionadas, también necesito agregar un valor a una propiedad en la tabla de unión. No sé si es mi falta de conocimiento de secuelas o promesas lo que está causando mi problema.
El código que estoy usando está debajo. Este código agrega los elementos a la base de datos, pero necesito redirigir después de que se haya completado la operación, lo cual no funciona.
Básicamente, necesito crear una Receta. Una vez que se crea, necesito crear Ingredientes y relacionarlos con esa Receta. Los ingredientes se almacenan en una matriz procedente de un formulario en una página HTML. Al relacionar los ingredientes, necesito agregar la cantidad_ingrediente a la tabla Ingredientes de la receta, que es la parte intermedia de la relación (la tabla de unión).
global.db.Recipe.belongsToMany( global.db.Ingredient, { as: 'Ingredients', through: global.db.RecipeIngredients, foreignKey: 'recipe_id' }); global.db.Ingredient.belongsToMany( global.db.Recipe, { as: 'Recipes', through: global.db.RecipeIngredients, foreignKey: 'ingredient_id' }); router.post('/new', ensureLoggedIn, bodyParser.json(), function (req, res) { var recipeName = req.body.recipe_name; var steps = req.body.steps; var ingredients = req.body.ingredients; var ingredientQty = {}; var currentIngredient; var ingredientsToAdd = []; db.Recipe.create({ recipe_name: recipeName, directions: steps, FamilyId: req.user.FamilyId, CreatedBy: req.user._id }) .then(function (recipe) { for (var i = 0; i < ingredients.length; i++) { currentIngredient = ingredients[i]; ingredientQty[currentIngredient.ingredient_name] = currentIngredient.quantity; db.Ingredient.findOrCreate({ where: { ingredient_name: currentIngredient.ingredient_name, FamilyId: req.user.FamilyId } }) .spread(function (ingredient, created) { if (created) { console.log("Added Ingredient to DB: " + currentIngredient.ingredient_name); } ingredient.Recipes = { ingredient_quantity: ingredientQty[ingredient.ingredient_name] }; ingredient.CreatedBy = req.user._id; recipe.addIngredient(ingredient) .then(function () { console.log("Added Ingredient " + ingredient.ingredient_name + " to Recipe " + recipe.recipe_name); }); }) } }) .finally(function(recipe){ res.redirect('/recipes'); }); });Cualquier ayuda sería muy apreciada. Sé que tengo problemas al tratar de usar promesas dentro de un ciclo, pero no sé de qué otra manera puedo lograr esto.
Con Sequelize, puede crear objetos junto con sus objetos asociados en un solo paso, siempre que todos los objetos que esté creando sean nuevos . Esto también se llama creación anidada. Vea este enlace y desplácese hacia abajo hasta la sección titulada "Crear con asociaciones"
En cuanto a su problema, tiene una relación de muchos a muchos entre Recipe e Ingredient , siendo RecipeIngredients la tabla de unión.
Supongamos que tiene un nuevo objeto Receta que desea crear, como:
var myRecipe = { recipe_name: 'MyRecipe', directions: 'Easy peasy', FamilyId: 'someId', CreatedBy: 'someUserId' }Y una matriz de objetos Ingrediente, como:
var myRecipeIngredients = [ { ingredient_name: 'ABC', FamilyId: 'someId'}, { ingredient_name: 'DEF', FamilyId: 'someId'}, { ingredient_name: 'GHI', FamilyId: 'someId'}] // associate the 2 to create in 1 step myRecipe.Ingredients = myRecipeIngredients; Ahora, puede crear myRecipe y sus myRecipeIngredients asociados en un solo paso, como se muestra a continuación:
Recipe.create(myRecipe, {include: {model: Ingredient}}) .then(function(createdObjects){ res.json(createdObjects); }) .catch(function(err){ next(err); }); Y eso es todo !!
Sequelize creará 1 fila en Recipe , 3 filas en Ingredient y 3 filas en RecipeIngredients para asociarlos.
Pude solucionar el problema que estaba teniendo. Las respuestas aquí me ayudaron a encontrar mi solución. Estoy publicando la solución a continuación en caso de que alguien más tenga un problema similar. Creé una variable para almacenar la Promesa de Recipe.create(), usé Promise.map para encontrar o crear todos los ingredientes de los datos del formulario. Debido a que findOrCreate devuelve una matriz que contiene Promise y un valor booleano si se creó el elemento, tuve que obtener los ingredientes reales de los resultados de la función Promise.map. Así que utilicé la función JavaScript array.map() para obtener el primer elemento de las matrices. Y finalmente, use Promise.map nuevamente para agregar cada ingrediente a la receta.
var ingredients = req.body.ingredients, recipeName = req.body.recipeName, ingredientsQty = {}; // Used to map the ingredient and quantity for the // relationship, because of the Junction Table var recipe = models.Recipe.create({recipe_name: recipeName}); // Use Promise.map to findOrCreate all ingredients from the form data Promise.map(ingredients, function(ing){ ingredientsQty[ing.ingredient_name] = ing.ingredient_quantity; return models.Ingredient.findOrCreate({ where: { ingredient_name: ing.ingredient_name}}); }) // Use JavaScript's Array.prototype.map function to return the ingredient // instance from the array returned by findOrCreate .then(function(results){ return results.map(function(result){ return result[0]; }); }) // Return the promises for the new Recipe and Ingredients .then(function(ingredientsInDB){ return Promise.all([recipe, ingredientsInDB]); }) // Now I can use Promise.map again to create the relationship between the / // Recipe and each Ingredient .spread(function(addedRecipe, ingredientsToAdd){ recipe = addedRecipe; return Promise.map(ingredientsToAdd, function(ingredientToAdd){ ingredientToAdd.RecipeIngredients = { ingredient_quantity: ingredientsQty[ingredientToAdd.ingredient_name] }; return recipe.addIngredient(ingredientToAdd); }); }) // And here, everything is finished .then(function(recipeWithIngredients){ res.end });