Akaino
Akaino

Reputation: 1025

Core Data many to many relation with intermediate table (Swift 2)

TL;DR EDIT with answer

As Wain perfectly answered this is how I get my information now:

let ingredientsToRecipe = recipe.valueForKey("ingredientsToRecipe")! as! NSSet
for i in ingredientsToRecipe {
  print(i.valueForKey("amount")!)
  print(i.valueForKeyPath("ingredient.name")!)
}

Original question

I have a huge problem understanding the usage of intermediate tables in CoreData. I've searched SO for answers and found a few threads about intermediate tables and many-to-many relations but those where either Objective-C or didn't help me.

I have the following setup (simplified): image

Now I want to add a new recipe with a bunch of ingredients. Let's say a Burger. The burger consists of

This is what I tried so far:

// Core Data
let appDelegate =
  UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let entity = NSEntityDescription.entityForName("Recipe",
                                                inManagedObjectContext:managedContext)
// creating a new recipe with name and id
let recipe = NSManagedObject(entity: entity!,
                                 insertIntoManagedObjectContext: managedContext)
recipe.setValue("Burger", forKey: "name")
recipe.setValue("B_001", forKey: "id")

Now I got an Array:[NSManagedObject] of of ingredients (created just like the Burger) and a Dictionary of amounts to the ingredient_IDs. This is how I'm trying to marry my Recipe with the ingredients (over the intermediate table).

for i in selectedIngredients { // the ingredient array
  let ingredientsToRecipe =     NSEntityDescription.insertNewObjectForEntityForName("RecipeIngredient", inManagedObjectContext: managedContext)
  
  ingredientsToRecipe.setValue(i, forKey: "ingredient")
  ingredientsToRecipe.setValue(recipe, forKey: "recipe")

  let quantity = Double(quantityDictionary[(i.valueForKey("id") as! String)]!) // the amount-to-ID dictionary
  ingredientsToRecipe.setValue("\(quantity)", forKey: "quantity")
}

In the end I simply save everything:

do {
  try managedContext.save()
  print("Saved successfully")
  self.dismissViewControllerAnimated(true, completion: nil)
} catch let error as NSError  {
  print("Could not save \(error), \(error.userInfo)")
}

All this above somehow works. But now I'm struggling to fetch information about my recipes. How am I supposed to fetch the amount of tomatoes of this specific burger?

Things like recipe.valueForKey("RecipeIngredient").valueForKey("amount") work but I don't know which amount is from which ingredient. Am I doing anything wrong? What can/should I do better?

The goal is to create a recipe with ingredients and later populate a Table with information about the recipe and the amounts of it's ingredients (and the ingredients themselves).

I appreciate any help!

Upvotes: 2

Views: 759

Answers (3)

Duncan Babbage
Duncan Babbage

Reputation: 20187

You don't need to use valueForKey and valueForKeyPath to access these kinds of properties... Rather, you should let Core Data do the work of traversing the relationships for you, and just ask for what you need using dot syntax:

for item in recipe.ingredientsToRecipe {
    print(item.amount)
    print(item.ingredient.name)
}

I suggest that you rename your intermediate entity from IngredientsToRecipe (which is thinking like database design) to Items or ReceipeItems to better capture what it is—the item that actually appears in a recipe, rather than the underlying food type itself.

But whether you do that or not, you could certainly name the relationship on Receipe to be items, resulting in the much more readable:

for item in recipe.items {
    print(item.amount)
    print(item.ingredient.name)
}

You could also go further and create a computed property on the intermediate entity called name that simply returned ingredient.name, which would then let you use:

for item in recipe.items {
    print(item.amount)
    print(item.name)
}

:)

Upvotes: 0

stefos
stefos

Reputation: 1235

For you to get the amount of an ingredient for a specific recipe you can create a fetch request at RecipeIngredient using predicates like this :

var request = NSFetchRequest(entityName: "RecipeIngredient")
let predicate = NSPredicate(format: "recipe.name = %@ AND ingredient.name = %@", "burger","tomato")
request.predicate = predicate

Then you simply get tha amount value from the returned RecipeIngredient entity.

Upvotes: 1

Wain
Wain

Reputation: 119031

The power of the intermediate object is that it takes your many-to-many relationship and breaks it into multiple one-to-many relationships. The to-one relationships are easy to navigate.

So, from your Recipe you can get an array of RecipeIngredients, and for each one you can get valueForKey("amount") and valueForKeyPath("ingredient.name").

Upvotes: 3

Related Questions