I have a strange occurrence happening with the ownerId.
FDMeal has an array of FDPortion. When I add a portion, or several, and save the FDMeal, they all get ownerId. But if I save the meal again, the ownerId disappears.
func updateMealOnServer() -> FDMeal? {
MMProgressHUD.showWithStatus("Saving")
let backendless = Backendless.sharedInstance()
var error: Fault?
let result = backendless.data.update(self, error: &error) as? FDMeal
if error == nil {
debugPrint("Meal is updated: \(result!.objectId)")
debugPrint("portions saved: \(result!.portions)")
for portion in (result?.portions)! {
debugPrint("saved portion: \(portion.objectId) name: \(portion.portion)")
for devicePortion in self.portions {
if devicePortion.objectId == nil && devicePortion.portion == portion.portion {
devicePortion.objectId = portion.objectId
debugPrint("selfId: \(devicePortion.objectId) server: \(portion.objectId)")
}
}
}
MMProgressHUD.dismiss()
return result!
} else {
debugPrint("Server reported an error: \(error)")
MMProgressHUD.dismissWithError("\(error!.message)", title: "Server Error!", afterDelay: 4)
return nil
}
}
Is there anyone that can shed some light on this?
Hi!
Could you show FDMeal and FDPortion classes?
//
// FDPortion.swift
// BeFitForLife
//
// Created by Jorgen Andreasson on 07/03/2016.
// Copyright © 2016 Clockwork Concepts. All rights reserved.
//
import Foundation
/// Portion class for FDMeal
class FDPortion: BackendlessEntity {
var portion: String
var portionSize: Double
var portionType: String // F,P,C,V
override init() {
portion = ""
portionSize = 0.0
portionType = ""
}
}
//
// FDMeal.swift
// BeFitForLife
//
// Created by Jorgen Andreasson on 06/03/2016.
// Copyright © 2016 Clockwork Concepts. All rights reserved.
//
import Foundation
import MMProgressHUD
/// Food Diary Meal class
class FDMeal: BackendlessEntity {
// var objectId: String?
var time: NSDate
var planned: Bool
var achieved: Bool
var targetProtein: Double
var targetCarb: Double
var targetFat: Double
var targetVeg: Double
var note: String
var photoPath: String
var portions: [FDPortion] = []
override init() {
time = Utils.dateFromString("00:00", format: "HH:mm")
planned = false
achieved = false
targetProtein = 0.0
targetCarb = 0.0
targetFat = 0.0
targetVeg = 0.0
note = ""
photoPath = ""
portions = [FDPortion]()
}
// MARK: - Calculations
func getPtotal() -> Double {
var retVal = 0.0
for portion in portions {
if portion.portionType == "P" {
retVal = retVal + portion.portionSize
}
}
return retVal
}
func getCtotal() -> Double {
var retVal = 0.0
for portion in portions {
if portion.portionType == "C" {
retVal = retVal + portion.portionSize
}
}
return retVal
}
func getFtotal() -> Double {
var retVal = 0.0
for portion in portions {
if portion.portionType == "F" {
retVal = retVal + portion.portionSize
}
}
return retVal
}
func getVtotal() -> Double {
var retVal = 0.0
for portion in portions {
if portion.portionType == "V" {
retVal = retVal + portion.portionSize
}
}
return retVal
}
// MARK: - Backendless
func updateMealOnServer() -> FDMeal? {
MMProgressHUD.showWithStatus("Saving")
let backendless = Backendless.sharedInstance()
var error: Fault?
let result = backendless.data.update(self, error: &error) as? FDMeal
if error == nil {
debugPrint("Meal is updated: \(result!.objectId)")
NSNotificationCenter.defaultCenter().postNotificationName("mealUpdated", object: nil)
debugPrint("portions saved: \(result!.portions)")
for portion in (result?.portions)! {
debugPrint("saved portion: \(portion.objectId) name: \(portion.portion)")
for devicePortion in self.portions {
if devicePortion.objectId == nil && devicePortion.portion == portion.portion {
devicePortion.objectId = portion.objectId
debugPrint("selfId: \(devicePortion.objectId) server: \(portion.objectId)")
}
}
}
MMProgressHUD.dismiss()
return result!
} else {
debugPrint("Server reported an error: \(error)")
MMProgressHUD.dismissWithError("\(error!.message)", title: "Server Error!", afterDelay: 4)
return nil
}
}
}
Ok, we will try it. And also I found similar topic on support forum http://support.backendless.com/t/lost-of-ownerid-value-after-updating-and-saving-an-object
maybe it will help you.
I had a look at that earlier, thanks. However, I’m not updating any Ids, except the ones in the internal array so I don’t end up with doubled up portions.
Hi Jorgen,
When you get a result, you should set
self.objectId = result!.objectId
Regards,
Slava
I don’t follow you.
Why would devicePortion.portion == portion.portion always be false?
.portion is the string value which is the name e.g. “potato” just to check that the portion I am setting the objectId on is the right one. It is only the local array that get the objectIds. Just so they don’t double up, which they do if they don’t have an objectId.
I see, Jorgen - the property name “portion” i associated with FDPortion (formal logic).
I fixed my comment, did you follow it?
I know. The name portion is not very well named.
When do I set the self.objectId? It’s the portions that looses the ownerId, so I need to iterate through them and set them.
Hi Jorgen,
I’ve created a sample project with your data model classes (with some refactoring - see the my comments). It works fine for me. I shared this project for you, please investigate it and let me know how in goes for you.
Regards,
Slava
Hi Slava,
Thank you for this. I will update my code and test.
What is the reason for calling meal.updateMealOnServer() twice?
This is a feature of your implementation.
After you saved FDMeal object with updateMealOnServer() method, the only ‘result’ object and its ‘portions’ have ‘objectId’ - not the original meal, so you need to set its objectId and objectIds of its relations.
You could implement another method, for example, in your ViewController class:
func saveFDMeal() {
let portion1 = FDPortion()
portion1.portion = "First"
let portion2 = FDPortion()
portion2.portion = "Second"
let portion3 = FDPortion()
portion3.portion = "Third"
var meal = FDMeal()
meal.portions = [portion1, portion2, portion3]
Backendless.sharedInstance().data.save(meal, response: {(response:AnyObject!) -> Void in
meal = response as! FDMeal
print("Meal is updated: \(meal.objectId)")
print("portions saved: \(meal.portions)")
},
error: {(fault:Fault!) -> Void in
print ("Fault! \(fault)")
})
}
In this case you set ‘meal’ object with result of the save operation, and you don’t need to set its objectIds.
Checked if objectIds of the portions disappear, as you described above.
It doesn’t seem to help. It still disappears when I save the meal the second time.
Jorgen, what disappear? objectIds are on both screenshots!
Jorgen is talking about disappearing ownerId, not objetId.
Jorgen, it would be very helpful if you didn’t crop out the “updated” column from the screenshots, which would make it super clear to show the time sequence of your operations.
Jorgen,
The root cause of the problem is in the overall approach you took with the design. Your “data model” classes (FDMeal in particular) contain both data structure, which is the collection of properties AND behavior (methods operating on the instances of FDMeal). It would be okay if the additional method were only saving/updating/deleting instances, however, they do something rather drastic. When you save an object, you get what the server returns and copy the assigned values to the local representation of the same data structure. As a result, you end up with a separate instance which essentially is a copy of what the server returned back to you.
The objects returned by the server carry more information than the properties you declared. This includes fields like “created”, “updated”, “__meta” and “ownerId”. When you make a copy of objectId, you miss all other properties which remain very important for the consistency of the data model. In the end you end up with the behavior you observed.
I recommend changing the approach where any operations you perform on your data model objects resides out of the data model classes. The traditional MVC (model-view-controller) approach works the best here. Your FDMeal, FDPortion classes become your model. They should really be dumb data structures. A controller is where you put your business logic, it knows when/how to save/update/delete apps’ objects. The view is straight-forward, it is responsible for rendering the UI.
Hope this helps.
Mark
Hi Mark,
Thanks for your excellent information.
Normally I would always have the object methods in the class declaration, are you suggesting these should be in another place such as a dedicated controller (datalayer)?
All the updateMealOnServer() does is saving the meal. So it’s just its own instance that saves. I don’t actually use the FDMeal that is returned. The call is, on the save button, self.updateMealOnServer(), the return isn’t used. I copy the objectId of the portions to the local object to stop them doubling up.
Would it be better to use a Viper style approach to separate the actions from the object class?
Would you be so kind to explain the .save versus .update please? Am I right in thinking that .save is used when you create the object and .update when you, well, update one that is already created?
The main problem is exactly how you described it:
I copy the objectId of the portions to the local object to stop them doubling up
Rather than copying anything, you should use the objects which come back to your program from the server. Those objects are already of the specific types (classes) defined in your app.
I apologize, I am not familiar with the Viper approach.
“save” is a universal operation. It checks if there is objectId assigned to the object and if so, updates the existing object, otherwise creates a new one.
Regards,
Mark