Imagine I have an app with two Core Data entities: ingredients and cookies.
Ingredients have a title, a type, an image, and a few other properties.
Cookies have a title, a type, an image and a few other properties.
A cookie is comprised of several ingredients. Ingredients can be used in multiple different cookies. The Cookie entity and the Ingredient entity have an inverse relationship which allows me to see which cookies have which ingredients and which ingredients are used in which cookies.
All of this is working fine.
Here is my problem:
In addition to several other ingredients of varying quantities, chocolate chip cookies require one egg but oatmeal raisin cookies require two eggs.
If I was designing a MySQL database to model all this, I'd have a Cookie table (similar to my Cookie Core Data entity), an Ingredient table (similar to my Ingredient Core Data entity), and a cross-reference table CookieIngredient, which would include a cookieID, an ingredientID, and a quantity.
I've tried to do this in Core Data, but I can't seem to get the entity for the cross-reference table created correctly. I create an Entity named CookieIngredient with Integer 16 fields of cookieID, ingredientID, and quantity, but then when I attempt to create an NSManagedObject Subclass, the class which is created -- unlike the other two classes -- displays numerous errors telling me "extensions cannot contain stored properties", which doesn't seem to have anything to do with anything else in the universe.
None of the many "recipe" Swift tutorials I've found online use Core Data, and the ones that do don't use a cross-reference table.
Any suggestions appreciated. (Am I doing this the wrong way?)
do{
let fetchRequest : NSFetchRequest<Ingredient> = Ingredient.createFetchRequest()
fetchRequest.predicate = NSPredicate(format: "title == %#", title)
let fetchedResults = try the_context.fetch(fetchRequest)
if let anIngredient = fetchedResults.first {
let the_recipe = CookieIngredient(context: the_context)
the_recipe.quantity = amount
the_recipe.unit = unit
the_recipe.mutableSetValue(forKey: "recipeIngredient").add(anIngredient)
the_recipe.mutableSetValue(forKey: "recipeCookie").add(Cookie)
}
}
catch{
print( "unable to update ingredients" )
}
Assuming I have a Cookie object, I should be able to do this. But the error I'm getting is:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSManagedObjects of entity 'CookieIngredient' do not support -mutableSetValueForKey: for the property 'recipeIngredient''
Is mutableSetValue not the correct way to create the relationship?
Note: mutableSetValue was not the correct way to do it.
extension CookieIngredient {
#nonobjc public class func createfetchRequest() -> NSFetchRequest<CookieIngredient> {
return NSFetchRequest<CookieIngredient>(entityName: "CookieIngredient")
}
#NSManaged public var quantity: String
#NSManaged public var unit: String
#NSManaged public var recipeCookie: Cookie
#NSManaged public var recipeIngredient: Ingredient
}
Related
I have some items which I fetch from Realm:
let items = realm.objects(ShoppingListItem.self)
print("\(items.count) items") // 10 items
Each item has a subdepartment, and each subdepartment has a department:
let departments = items.flatMap({ $0.product?.subdepartment?.department })
print("\(departments.count) departments") // 10 departments
My goal is to find the unique Department objects from this list. The expected result is 4. My standard approach here is to use Set:
let uniqueDepartments1 = Set(departments)
print("\(uniqueDepartments1.count) unique departments via Set") // 9 unique departments via Set - but changes every time!?
I figure there must be something I'm missing related to the way Realm treats equality. But to check, I also attempt to get the unique departments via enumeration:
var uniqueDepartments2 = [Department]()
for department in departments {
if uniqueDepartments2.contains(department) {
continue
}
uniqueDepartments2.append(department)
}
print("\(uniqueDepartments2.count) unique departments via enumeration") // 4 unique departments via enumeration
This is indeed the expected result.
Why doesn't Set work here as I expected it to? And why is the count changing each time I run it?
Edit 2/27
Here are the models in play:
class ShoppingListItem: Object {
dynamic var product: Product?
convenience init(ingredient: Ingredient) {
self.init()
self.product = ingredient.product
}
}
class Product: Object {
dynamic var productID, subdepartmentID, title: String?
dynamic var subdepartment: Subdepartment?
}
class Department: Object {
dynamic var departmentID, title: String?
}
class Subdepartment: Object {
dynamic var subdepartmentID, departmentID, title: String?
dynamic var department: Department?
}
In short: in order for an Object subclass to be correctly hashable you must currently declare a property as the primary key.
Set is built on top of a hash table. This means it checks for the existence of a specific object by first computing the hash value of the object, mapping that hash value to a bucket within the hash table, and then checking each entry in that bucket for equality with the specified object.
The nature of this implementation means that for Set to work correctly on objects of a given type, both the hashValue property and == operator must obey specific rules. In particular, any two objects for which == returns true must return the same value from their hashValue property (the reverse is not required; it's completely valid for two unequal objects to have the same hashValue). Realm's implementation's of hashValue and == don't currently meet this criteria unless your class declares a property as the primary key. When no primary key is declared, Object's default hashValue calculation defers to -[NSObject hash], which simply returns the object's address in memory. Since Object.== allows two objects with different addresses to compare equal, this violates the relationship between hashValue and == I outlined above, leading to incorrect behavior when used with Set or as the key of a Dictionary. I've filed a bug against Realm requesting that the behavior of == be fixed to be compatible with the value returned by the hashValue property.
I have a structure of objects in Firebase looking like this:
-KBP27k4iOTT2m873xSE
categories
Geography: true
Oceania: true
correctanswer: "Yaren (de facto)"
languages: "English"
question: "Nauru"
questiontype: "Text"
wronganswer1: "Majuro"
wronganswer2: "Mata-Utu"
wronganswer3: "Suva"
I'm trying to find objects by categories, so for instance I want all objects which has the category set to "Oceania".
I'm using Swift and I can't really seem to grasp the concept of how to query the data.
My query right now looks like this:
ref.queryEqualToValue("", childKey: "categories").queryOrderedByChild("Oceania")
Where ref is the reference to Firebase in that specific path.
However whatever I've tried I keep getting ALL data returned instead of the objects with category Oceania only.
My data is structured like this: baseurl/questions/
As you can see in the object example one question can have multiple categories added, so from what I've understood it's best to have a reference to the categories inside your objects.
I could change my structure to baseurl/questions/oceania/uniqueids/, but then I would get multiple entries covering the same data, but with different uniqueid, because the question would be present under both the categories oceania and geography.
By using the structure baseurl/questions/oceania/ and baseurl/questions/geography I could also just add unique ids under oceania and geography that points to a specific unique id inside baseurl/questions/uniqueids instead, but that would mean I'd have to keep track of a lot of references. Making a relations table so to speak.
I wonder if that's the way to go or? Should I restructure my data? The app isn't in production yet, so it's possible to restructure the data completely with no bigger consequences, other than I'd have to rewrite my code, that pushes data to Firebase.
Let me know, if all of this doesn't make sense and sorry for the wall of text :-)
Adding some additional code to Tim's answer for future reference.
Just use a deep query. The parent object key is not what is queried so it's 'ignored'. It doesn't matter whether it's a key generated by autoId or a dinosaur name - the query is on the child objects and the parent (key) is returned in snapshot.key.
Based on your Firebase structure, this will retrieve each child nodes where Oceania is true, one at a time:
let questionsRef = Firebase(url:"https://baseurl/questions")
questionsRef.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)
.observeEventType(.ChildAdded, withBlock: { snapshot in
print(snapshot)
})
Edit: A question came up about loading all of the values at once (.value) instead of one at at time (.childAdded)
let questionsRef = Firebase(url:"https://baseurl/questions")
questionsRef.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)
.observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot)
})
Results in (my Firebase structure is a little different but you get the idea) uid_1 did not have Oceania = true so it was omitted from the query
results.
Snap (users) {
"uid_0" = {
categories = {
Oceania = 1;
};
email = "dude#thing.com";
"first_name" = Bill;
};
"uid_2" = {
categories = {
Oceania = 1;
};
"first_name" = Peter;
};
}
I think this should work:
ref.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)
I can't seem to understand where I am going wrong currently when I attempt to update my User domain model that has a hasOne association to a Profile object.
My domain models are as follows:
class User {
static hasOne = [profile: Profile]
static fetchMode = [profile: 'eager']
ObjectId id
String username
}
class Profile {
static belongsTo = [user: User]
ObjectId id
String familyName
String givenName
}
I am able to persist a User with a profile originally but when attempting to update the User object I get validation errors.
Validation error occurred during call to save():
- Field error in object 'co.suitable.User' on field 'profile.familyName': rejected value [null]
- Field error in object 'co.suitable.User' on field 'profile.givenName': rejected value [null]
I am able to print out the user.profile ID and also the user.profile.familyName before saving the object. Like the following:
println(user.profile.familyName)
println(user.profile.id.toString())
user.save(flush: true, failOnError: true)
But I still get the validation errors before saving, i'd imagine that the println(user.profile.familyName) call is fetching the profile object if it already hasn't been loaded which I thought setting the fetchMode would have handled.
The object is able to successfully persist and save when I do:
user.profile = Profile.findById(user.profile.id)
println(user.profile.id.toString())
user.save(flush: true, failOnError: true)
I could wrap that in a service but I was hoping for a solution that would be handled by Grails if possible. Any advice or thoughts is much appreciated.
You should not apply the logic for the SQL DB to Mongo 1 to 1. Mongo and other document-oriented DBs are not originally intended to store the joins between collections. There are some workarounds, like db-refs, but they are to be used with caution.
For your case - with hasOne - I would suggest using mongo's subdocuments (mirrored as GORM's embedded objects) instead of referencing:
class User {
ObjectId id
String username
Profile profile
static embedded = [ 'profile' ]
}
class Profile {
String familyName
String givenName
}
thus you use the mongo in accordance to it's original puprose. Also querying is simpler and faster.
I'm trying to put together a ViewModel that will have a list of users and each user will have a list of locations.
The User table and Location table are joined together through another table that holds each respective ID and some other information. This table is essentially a many to many join table.
I've tried a few different viewModel approaches and they we're severely lacking... What would be the best approach for displaying this type of information?
I assume that the issue is that you want to be able to access the collection by either User or Location. One approach could be to use ILookup<> classes. You'd start with the many-to-many collection and produce the lookups like this:
var lookupByUser = userLocations.ToLookup(ul => ul.User);
var lookupByLocation = userLocations.ToLookup(ul => ul.Location);
Update:
Per your description, it seems like you don't really need to have a full many-to-many relationship in your ViewModel. Rather, your ViewModel could have a structure like this:
public class YourViewModel
{
public IEnumerable<UserViewModel> Users { get; set; }
}
public class UserViewModel
{
// User-related stuff
public IEnumerable<LocationViewModel> Locations { get; set; }
}
If you wanted to avoid redundant LocationViewModel objects, you could pre-build a mapping between your Model and ViewModel objects:
var locationViewModels = myLocations.ToDictionary(
loc => loc, loc => CreateLocationViewModel(loc));
And then reuse these objects when populating your page's ViewModel.
I have following table structure:
Table: Plant
PlantID: Primary Key
PlantName: String
Table: Party
PartyID: Primary Key
PartyName: String
PlantID: link to Plant table
Table: Customer
PartyID: Primary Key, link to Party
CustomerCode: String
I'd like to have Customer entity object with following fields:
PartyID: Primary Key
CustomerCode: String
PartyName: String
PlantName: String
I am having trouble with PlantName field (which is brought from Plant table
I connected Customer to Party and Party to Plant with associations
However I can not connect Customer to Plant with association ( because it does not have one)
I can not add Plant table to mapping, when I do that - I am getting following error:
Error 3024: Problem in Mapping Fragment starting at line 352: Must specify mapping for all key properties (CustomerSet.PartyID) of the EntitySet CustomerSet
Removing Plant association works.
Any hints or directions very appreciated.
You can get these fields by using the reference path on the Entity Object.
To get the PartyName, use this syntax: Customer.Party.PartyName
To get the PlantName, use this syntax: Customer.Party.Plant.PlantName
You can extend the Customer entity by using the public partial class:
public partial class Customer
{
public string PartyName
{
get { return Party.PartyName; }
set { Party.PartyName = value; }
}
public string PlantName
{
get { return Party.Plant.PlantName; }
set { Party.Plant.PlantName = value; }
}
}
After some research, I came across this thread on MSDN that says you can create a read-only entity, which is enough of a downside to not use it alone, but it gets worse. You will also lose the ability to update all of the models dynamically based on the schema of the database.