1

I hold recipes in a database, each recipe has ingredients field of type Array. to the query i pass an array of ingredients for example: ["banana", "apple", "watermelon"] , and i want to find all recipes in the db containing in the ingredients field each of the ingredients passed to the query. the issue is the ingredients passed to the query are usually substrings of the ingredients in the DB, so for example a document in the DB can have "1 ripe banana" in its ingredients array and in the query i pass "banana" , which mean this document should be returned has a result. the result documents should include each of the ingredients passed.

i did manage to find all recipes including at least one using this code:

app.get('/recipe', (req, res) => {
if (req.query.ingredients) {
    let ingredientMap = req.query.ingredients.map(x =>
    Recipe.find({ingredients: {$regex: new RegExp(JSON.parse(x).name)}}));

    return Promise.all(ingredientMap).then((results) => {
        if (!results) {
        res.status(404).send();
    }
    let filteredResults = results.filter(x => {
        return typeof x !== 'undefined' ? x[0] : null
    });

    let merged = [].concat.apply([], filteredResults);

    res.status(200).send(merged);
    }).catch( e => {
        res.status(400).send();
    })
}

});

but how can i make a query where each result includes all of the ingredients passed to the query, as substrings?

5
  • The .name of an ingredient is a plain string, right? Odd, it looks like you're doing it exactly as you should be Commented Dec 1, 2018 at 21:35
  • yes, like "banana" for example, which means array of strings Commented Dec 1, 2018 at 21:37
  • @CertainPerformance he can't use the $all since the issue is he wants substrings. $all would have worked if his recipes ware composed of "clean" ingredients but he has 1 awesome bag of bananas and he searches for banana. Hence the need for the $regex etc. Commented Dec 1, 2018 at 22:09
  • not sure i understood, if none found the result is empty Commented Dec 1, 2018 at 22:29
  • Makes sense @yarivbar see if the proposed answer would work for you and makes sense. Commented Dec 2, 2018 at 1:26

1 Answer 1

1

You can achieve this in a different way with a change to your regular expression.

Loop through your array from req.query.ingredients (for example ["banana", "apple", "watermelon"]) and compose this regEx:

(?=.*banana)(?=.*apple)(?=.*watermelon).+

Something among these lines:

const ing = ["banana", "apple", "watermelon"]
const result = `${ing.reduce((r,c) => r.concat(`(?=.*${c})`), "")}.+`
console.log(result)

This is using positive lookahead and would match if all of the ingredients are found in your recipe. This should also remove the need to use Promise.all since you would only need one mongoDB find query.

You can test the regEx here

Also make sure you do case-insensitive search with mongo via the $options:

{'$regex' : '<YOUR-REGEX>', '$options' : 'i'}

Otherwise if someone is really BANANAS about the ingredients in one of your recipes ... you wont find it :)

Sign up to request clarification or add additional context in comments.

1 Comment

You might consider leading the regex with a start-of-string anchor (so that it fails faster if there's no match), and you can probably omit the trailing .+

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.