Trying to create an instance and multiple related

2020-07-30 10:28发布

问题:

I am attempting to create an instance and multiple related instances with a many to many relation using a junction table.

While creating the multiple related instances, I need to add a value to a property on the junction table as well. I don't know if it is my lack of knowledge of sequelize or promises that is causing my problem.

The code I am using is below. This code does add the items to the database, but I need to redirect after the operation has completed, which is not working.

Basically, I need to create a Recipe. Once that is created, I need to create Ingredients and relate them to that Recipe. The ingredients are stored in an array coming from a form on an HTML page. While relating the Ingredients, I need to add the ingredient_quantity to the RecipeIngredients table, which is the through part of the relationship (the junction table).

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');
});
});

Any help would be greatly appreciated. I know that I am running into issues because of trying to use promises inside of a loop, I just don't know how else I can accomplish this.

回答1:

Using sequelize, you can create objects along with its associated objects in one step, provided all objects that you're creating are new. This is also called nested creation. See this link and scroll down to section titled "Creating with associations"

Coming to your issue, you've a many-to-many relationship between Recipe and Ingredient, with RecipeIngredients being the join table.

Suppose you've a new Recipe object which you want to create, like:

var myRecipe = {
  recipe_name: 'MyRecipe',
  directions: 'Easy peasy',
  FamilyId: 'someId',
  CreatedBy: 'someUserId'
}

And an array of Ingredient objects, like:

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;

Now, you can create myRecipe and its associated myRecipeIngredients in one step as shown below:

Recipe.create(myRecipe, {include: {model: Ingredient}})
.then(function(createdObjects){
   res.json(createdObjects);
})
.catch(function(err){
   next(err);
});

And that is all !!
Sequelize will create 1 row in Recipe, 3 rows in Ingredient and 3 rows in RecipeIngredients to associate them.



回答2:

I was able to fix the problem that I was having. The answers here helped me come up with my solution. I am posting the solution below in case anyone else runs into a similar issue. I created a variable to store the Promise from Recipe.create(), I used Promise.map to findOrCreate all of the ingredients from the form data. Because findOrCreate returns an array containing Promise and a boolean for if the item was created, I then had to get the actual ingredients out of the results of the Promise.map function. So I used the JavaScript array.map() function to get the first item from the arrays. And finally, use Promise.map again to add each Ingredient to the Recipe

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
});