Inverse of a relationship

Hey there,

I have two objects related to each other like so:

    A -- (has many) --> B
Now when I want to create a "new" B-record, I have to put it into A's relation, that's fine. But what when I want to directly select B (like findAll) and know, which A it does belong to?

Let’s take your example: https://backendless.com/javascript-invoice-app-with-baas-data-management-user-registration-and-login/
If I do findAll on “items”, how can I possibly know what Invoice an item belongs to?

Is it possible?

Thanks in advance and have a great day!

Regards,
Fabian

I am working with iOS SDK and I have the same case: i do not know how to retrieve the parent object in a relationship. A response for these would be very helpful.

Suppose you know objectId of a child entity and need to load it’s parent. Let’s say the parent class is PhoneBook which contains a collection of Contact objects (represented by the “contacts” property). Say objectId of a contact is: XXXX-XXXX-XXXX-XXXX, then the “where” query you use in a “find” call sent to PhoneBook is:

contacts.objectId = ‘XXXX-XXXX-XXXX-XXXX’

Hope this helps.

Regards,
Mark

No i dont think so, it cant help. Lets discuss more difficult case to understand:

I have Users with relational table -> Cars. In this table i have list of owner cars for example.
Then we have another table -> Ads containing list of metadata plus relation to Cars table.
And finally third table -> Offers Containing relation to User (client), relation to Ads table and for example offered price.

And I want inverted relation constructed like this:
Offers -> Ads -> Cars -> Users

It mean I have offer related to add, and there is car owned by user and i want to receive his name.

To achieve this you have to get list car.object id in first step.
And then find users with relation Cars and setup where clause to that objectID.

But its just too complicated, specially when you open backendless console and you have such a nice field: child of Users.Cars! Why we just cant use this property?!
I could easily add to backendless options -> load relations -> “Ads.Cars.Users.Cars” (or different syntax…)

Because what I did, is to automatise this process and created custom API call in business logic. This is how first draft looks:

'use strict';

class InverseRelationService {
    /**
     * @param {String} query
     * @returns {Object}
     */
    invert(query) {
        let rp = require('request-promise');
        let jsonQuery = JSON.parse(query.replace(/\'/g, '"'));
        var returnData = [];

        function doWork(text, data) {
            var promiseChain = null;
            for (var i = 0, len = data.length; i < len; i++) {
                let objectId = data;
                let whereClause = "cars.objectId='" + objectId + "'";
                let URL = 'https://api.backendless.com/v1/data/' + jsonQuery.invertedTable + '?loadRelations=' + jsonQuery.invertedRelation + "&where=" + whereClause;
                console.log('InvertedTable URL:' + URL);

                let invertedOptions = {
                    uri: URL,
                    headers: {
                        'application-id': 'D33CD664-1EE3-401F-FF16-E3B50D22B700',
                        'secret-key': '8A2F0C39-33F5-386F-FFA9-B362F41F0200',
                        'application-type': 'REST'
                    },
                    json: true // Automatically parses the JSON string in the response
                };

                if (promiseChain == null) {
                    promiseChain = rp(invertedOptions)
                        .then((inverted) => {
                            //Construct dict
                            var objDict = {[objectId]:inverted.data};
                            returnData.push(objDict);
                        }).catch((invertedErr) => {
                            console.error(invertedErr);
                        });
                }
                else {
                    promiseChain = promiseChain.then(() => {
                        return rp(invertedOptions)
                            .then((inverted) => {
                                //Construct dict
                                var objDict = {[objectId]:inverted.data};
                                returnData.push(objDict);
                            }).catch((invertedErr) => {
                                console.error(invertedErr);
                            })
                    })
                }
            }

            return promiseChain;
        }

        //Get inverted table data
        var URL = 'https://api.backendless.com/v1/data/' + jsonQuery.tableName + "?loadRelations=" + jsonQuery.relation;
        if (jsonQuery.query != undefined) {
            if (jsonQuery.query.condition != undefined) {
                URL += "&where=" + jsonQuery.query.condition;
            }
        }

        console.log('mainTable URL:' + URL);

        let options = {
            uri: URL,
            headers: {
                'application-id': 'D33CD664-1EE3-401F-FF16-E3B50D22B700',
                'secret-key': '8A2F0C39-33F5-386F-FFA9-B362F41F0200',
                'application-type': 'REST'
            },
            json: true // Automatically parses the JSON string in the response
        };

        return rp(options).then((res) => {
                //Construct new array
                var relationIDs = [];
                var relations = jsonQuery.relation.split(".");
                console.log('relations:' + relations);
                for (var i = 0, len = res.data.length; i < len; i++) {
                    //Sort out deepness
                    var root = res.data;
                    for (var r = 0, rlen = relations.length; r < rlen; r++) {
                        root = root[relations[r]];
                    }
                    relationIDs.push(root.objectId);
                }
                console.log(relationIDs);
                return relationIDs;
            }
        ).then(doWork.bind(null, '')).then(() => {
            return returnData;
        })
            .catch((err) => {
                console.log(err);
                err
            });
    }
}
Backendless.enablePromises();
Backendless.ServerCode.addService(InverseRelationService);

API call:
As far as API services does not support anything but string/int/bool in some strange behaviour your call has to look like this:

“{‘tableName’:‘CarsOffers’,‘relation’:‘cars.owner’,‘query’:{‘condition’:‘price>0’},‘invertedTable’:‘Users’, ‘invertedRelation’:‘cars’}”

And then proper " and ’ is replaced inside function.

What it does to to take your initial table -> look for relation and take objectID
And then it will call based on results your Inverted table inside related data and perform FIND

Or for reference, here is my solution to do inverse relation inside swift app:







p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #000000}
p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #4f8187}
p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #000000; min-height: 16.0px}
span.s1 {font-variant-ligatures: no-common-ligatures; color: #ba2da2}
span.s2 {font-variant-ligatures: no-common-ligatures}
span.s3 {font-variant-ligatures: no-common-ligatures; color: #4f8187}
span.s4 {font-variant-ligatures: no-common-ligatures; color: #703daa}
span.s5 {font-variant-ligatures: no-common-ligatures; color: #000000}
span.s6 {font-variant-ligatures: no-common-ligatures; color: #31595d}
span.s7 {font-variant-ligatures: no-common-ligatures; color: #3e1e81}
span.s8 {font-variant-ligatures: no-common-ligatures; color: #d12f1b}



func InvertRelation(fromTable:GenericObject!, relation:String,whereClause:String, invertTable:AnyClass!, invertRelation:String, completionHandler:((_ response:[Any]?, _ error:Fault?) -> Void)!)

    {

        let query:BackendlessDataQuery = BackendlessDataQuery()

        query.whereClause = whereClause

        let queryOptions = QueryOptions()

        queryOptions.related = NSMutableArray(array: [relation]);

        query.queryOptions = queryOptions

        

        backendless.data.of(fromTable.ofClass()).find(query, response: { (col:BackendlessCollection?) in

            if (col != nil)

            {

                var data:[Any]?

                

                for entry in col!.data

                {

                    if let castedEntry = entry as? GenericObject {

                        let objID = castedEntry.value(forKeyPath: "\(relation).objectId") ?? ""

                        let invertedQuery:BackendlessDataQuery = BackendlessDataQuery()

                        invertedQuery.whereClause = "\(invertRelation).objectId = '\(objID)'"

                        let invertedQueryOptions = QueryOptions()

                        invertedQueryOptions.related = NSMutableArray(array: [invertRelation]);

                        invertedQuery.queryOptions = invertedQueryOptions

                        

                        let invertedCol = self.backendless.data.of(invertTable).find(invertedQuery)

                        if invertedCol != nil

                        {

                            if data == nil

                            {

                                data = []

                            }

                            data!.append(contentsOf: invertedCol!.data)

                        }

                    }

                }

                completionHandler(data,nil)

            }

            else

            {

                completionHandler(nil,Fault(message: "Col == nil"))

            }

        }, error: { (fault:Fault?) in

            completionHandler(nil,fault)

        })

    }