Blog

How to Update an Object’s Related Collections Using API

by on September 1, 2019

Data objects stored in Backendless Database may have related objects through one-to-one or one-to-many collections. When objects are retrieved on the client-side, these relations are materialized as collections of data in the object’s fields or properties.

Consider the following data object:

The Person table has the addresses column which is a one-to-many collection with the Address table. The Person table schema is:

The related Address objects are shown below:


    At the code level, there is Person class defined as:

    import java.util.Date;
    import java.util.List;
    
    public class Person {
       public int age;
       public String name;
       public Date birthdate;
       public List
    addresses; public String objectId; }

    And the related Address class is defined as:

    public class Address {
       public String street;
       public String city;
       public String objectId;
    }

    The code below loads the Person object with the related Address objects. It prints out the number of Address objects, deletes one from the collection and saves the Person object back. Once it is saved and the updated instance is returned, the code prints out the number of related addresses to confirm one was indeed deleted:

    final List<String> relations = new ArrayList<>();
    relations.add("addresses");
    
    // load object
    Backendless.Data.of(Person.class).findFirst(relations, new AsyncCallback<Person>() {
       @Override
       public void handleResponse(Person person) {
           Log.i(TAG, "Loaded object. Name - " + person.name + ", relations - " + person.addresses.size());
           List
    addressesToDelete = Collections.singletonList(person.addresses.get(0)); // delete relation Backendless.Data.of(Person.class).deleteRelation(person, “addresses”, addressesToDelete, new AsyncCallback<Integer>() { @Override public void handleResponse(Integer response) { // receive updated object Backendless.Data.of(Person.class).findFirst(relations, new AsyncCallback<Person>() { @Override public void handleResponse(Person updatedPerson) { Log.i(TAG, “Received updated object. Name – ” + updatedPerson.name + “, relations – ” + updatedPerson.addresses.size()); } @Override public void handleFault(BackendlessFault fault) { Log.e(TAG, fault.getMessage()); } }); } @Override public void handleFault(BackendlessFault fault) { Log.e(TAG, fault.getMessage()); } }); } @Override public void handleFault(BackendlessFault fault) { Log.e(TAG, fault.getMessage()); } });

    import java.util.Date
    
    data class Person(
       var objectId: String? = null, var age: Int = 0, var name: String? = null,
       var birthdate: Date? = null, var addresses: List
    ? = null)

    And the related Address class is defined as:

    data class Address(var street: String? = null, var city: String? = null, var objectId: String? = null)

    The code below loads the Person object with the related Address objects. It prints out the number of Address objects, deletes one from the collection and saves the Person object back. Once it is saved and the updated instance is returned, the code prints out the number of related addresses to confirm one was indeed deleted:

    val relations = listOf("addresses")
    
    // load object
    Backendless.Data.of(Person::class.java).findFirst(relations, object : AsyncCallback<Person> {
      override fun handleResponse(person: Person) {
         Log.i(TAG, "Loaded object. Name - ${person.name}, relations - ${person.addresses.size}")
         val addressesToDelete = listOf(person.addresses[0])
    
         // delete relation
         Backendless.Data.of(Person::class.java).deleteRelation(person, "addresses", addressesToDelete, object : AsyncCallback {
            override fun handleResponse(response: Int?) {
               // receive updated object
               Backendless.Data.of(Person::class.java).findFirst(relations, object : AsyncCallback<Person> {
                  override fun handleResponse(updatedPerson: Person) {
                     Log.i(TAG, "Received updated object. Name - ${updatedPerson.name}, relations - ${updatedPerson.addresses.size}")
                  }
    
                  override fun handleFault(fault: BackendlessFault) {
                     Log.e(TAG, fault.message)
                  }
               })
            }
    
            override fun handleFault(fault: BackendlessFault) {
               Log.e(TAG, fault.message)
            }
         })
      }
    
      override fun handleFault(fault: BackendlessFault) {
         Log.e(TAG, fault.message)
      }
    })


    At the code level, there is Person class defined as:

    @interface Person : NSObject
    
    @property (strong, nonatomic) NSString *objectId;
    @property (strong, nonatomic) NSString *name;
    @property (strong, nonatomic) NSDate *birthdate;
    @property (nonatomic) NSInteger age;
    @property (strong, nonatomic) NSArray<Address *> *addresses;
    
    @end

    And the related Address class is defined as:

    @interface Address : NSObject
    
    @property (strong, nonatomic) NSString *objectId;
    @property (strong, nonatomic) NSString *city;
    @property (strong, nonatomic) NSString *street;
    
    @end

    The code below loads the Person object with the related Address objects. It prints out the number of Address objects, deletes one from the collection and saves the Person object back. Once it is saved and the updated instance is returned, the code prints out the number of related addresses to confirm one was indeed deleted:

    DataStoreFactory *dataStore = [Backendless.shared.data of:[Person class]];
        
    DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
    [queryBuilder setRelatedWithRelated:@[@"addresses"]];
        
    [dataStore findFirstWithQueryBuilder:queryBuilder responseHandler:^(Person *person) {
        NSLog(@"Loaded object. Name - %@, relations - %lu", person.name, (unsigned long)person.addresses.count);
            
        [dataStore deleteRelationWithColumnName:@"addresses" parentObjectId:person.objectId childrenObjectIds:@[person.addresses.firstObject.objectId] responseHandler:^(NSInteger removedRelation) {
                
            [dataStore findFirstWithQueryBuilder:queryBuilder responseHandler:^(Person *updatedPerson) {
                NSLog(@"Received updated object. Name - %@, relations - %lu", updatedPerson.name, (unsigned long)updatedPerson.addresses.count);
            } errorHandler:^(Fault *fault) {
                NSLog(@"Error: %@", fault.message);
            }];
                
        } errorHandler:^(Fault *fault) {
            NSLog(@"Error: %@", fault.message);
        }];
            
    } errorHandler:^(Fault *fault) {
        NSLog(@"Error: %@", fault.message);
    }];


    At the code level, there is Person class defined as:

    @objcMembers class Person: NSObject {
        var objectId: String?
        var name: String?
        var birthdate: Date?
        var age: Int = 0
        var addresses: [Address]?
    }

    And the related Address class is defined as:

    @objcMembers class Address: NSObject {
        var objectId: String?
        var city: String?
        var street: String?
    }

    The code below loads the Person object with the related Address objects. It prints out the number of Address objects, deletes one from the collection and saves the Person object back. Once it is saved and the updated instance is returned, the code prints out the number of related addresses to confirm one was indeed deleted:

    let dataStore = Backendless.shared.data.of(Person.self)
            
    let queryBuilder = DataQueryBuilder()
    queryBuilder.setRelated(related: ["addresses"])
            
    dataStore.findFirst(queryBuilder: queryBuilder, responseHandler: { person in
        if let person = person as? Person {
            print("Loaded object. Name - \(person.name ?? ""), relations - \(person.addresses?.count ?? 0)")
                    
            if let personId = person.objectId,
                let firstAddressId = person.addresses?.first?.objectId {
                dataStore.deleteRelation(columnName: "addresses", parentObjectId: personId, childrenObjectIds: [firstAddressId], responseHandler: { removedRelation in
                            
                    dataStore.findFirst(queryBuilder: queryBuilder, responseHandler: { updatedPerson in
                        if let updatedPerson = updatedPerson as? Person {
                            print("Received updated object. Name - \(updatedPerson.name ?? ""), relations - \(updatedPerson.addresses?.count ?? 0)")
                        }
                    }, errorHandler: { fault in
                        print("Error: \(fault.message ?? "")")
                    })
                            
                }, errorHandler: { fault in
                    print("Received updated object. Name - \(person.name ?? ""), relations - \(person.addresses?.count ?? 0)")
                })
            }
                    
        }
    }, errorHandler: { fault in
        print("Error: \(fault.message ?? "")")
    })

    const Backendless = require('backendless')
    /*
     Or use `import Backendless from 'backendless'` for client side.
     If you don't use npm or yarn to install modules, you can add the following line
     <script src="//api.backendless.com/sdk/js/latest/backendless.min.js"></script>
     to your index.html file and use the global Backendless variable.
    */
    
    Backendless.initApp('YOUR_APP_ID', 'YOUR_JS_API_KEY')
    
    const PersonStorage = Backendless.Data.of('Person')
    
    const findPerson = () => {
      const dataQuery = Backendless.DataQueryBuilder.create()
      dataQuery.setRelated('addresses')
    
      return PersonStorage.findFirst(dataQuery)
    }
    
    const removeRelation = async person => {
      console.log(`Loaded object. Name - ${ person.name }, relations - ${ person.addresses.length }`)
    
      const addressToDelete = person.addresses[0]
    
      const relationsDeleted = await PersonStorage.deleteRelation(person, 'addresses', [addressToDelete])
    
      console.log(`Relation has been deleted - ${ relationsDeleted }`)
    }
    
    const onError = error => {
      console.error('Server reported an error: ', error.message)
      console.error('error code: ', error.code)
      console.error('http status: ', error.status)
    }
    
    Promise.resolve()
      .then(findPerson)
      .then(removeRelation)
      .catch(onError)
    

    List<String> relations = ["addresses"];
    
    Backendless.Data.of("Person").findFirst(relations: relations).then((person) {
      print("Loaded object. Name - ${person['name']}, relations - ${person['addresses'].length}");
    
      Backendless.Data.of("Person").deleteRelation(person, "addresses",
          children: [person['addresses'][0]]).then((response) {
        Backendless.Data.of("Person").findFirst(relations: relations).then((updatedPerson) {
          print("Received updated object. Name - ${updatedPerson['name']}, relations - ${updatedPerson['addresses'].length}");
        });
      });
    });

    The code produces the following output:

    Loaded object. Name - Frankie, relations - 2
    Received updated object. Name - Frankie, relations - 1

    It is important to note that Backendless supports any kind of relation update. Your code could add new related objects, remove some (as shown above), change data inside of the related objects, etc. All of these changes will be persisted when the parent (Person) object is saved.

    Enjoy!

    Leave a Reply