[SalesForce] REST API – Retrieving records linked to a contact object

I'm attempting to use the REST API to pull out the contact record for a Client object and then retrieve all associated data from objects that belong to that Client.

There are 28 pertinent objects (a majority of them custom) that we use to store data… an ugly diagram of which would look something like…

# Details of our client
¬ Contact object (ID: 001C00000xxxxxx) 

# Each Instance of contact with the client
-¬ Contact Encounters object
--- Record (ID: 001C00000xxxxx1) 
--- Record (ID: 001C00000xxxxx2) 
--- Record (ID: 001C00000xxxxx3) 

To be clear, these request are being triggered from a Ruby-on-Rails app running Restforce. Within the app I've built a user interface that can grab all the object labels and API-names from a global describe and then allow the user to select which ones are pertinent for the report they are running. From there all they have to do is put in our internal UID which then gets looked up and transferred into Salesforce IDs for any further API requests.

Now I'm trying to work out how best to gather all records from all associated pertinent objects, so in the case of above, gathering each individual record of a client encounter where I know the client's salesforce ID.

I figure there's two ways:

Boring and Brittle?

I hard code the names of the objects into my queries, go to each custom object and discover how our developer has set up the field that links back to the client record in each case (i'm assuming that could vary even if only as much as: Client_id, ClientID, ClientID__c (depending on what they've called it) and then use find again to return collections of data based on the record Id… but this seems ugly and brittle to me. If we added a new object that I wanted to get data on I'd have to go back into the code, discover the fields check and recheck… plus I'm always a little concerned about the find method… what edge cases might cause it to turn up incorrect data/miss some.

In an ideal world

Once or twice in the hash of data returned from an Sobject describe API request i noticed there was information about 'relationships'. I'm not sure what these would relate to, but in an ideal world I'd be able to request something akin to a Ruby-on-Rail ActiveRecord request where you can grab all Child data by stating a parent object (assuming in this case our contact record would be the parent of all other pertinent objects… though I'm not sure Salesforce is set up in this way).

What i'm wondering is if there's any way I could have a process that:

  • Accepts user input in the form of a UID, converts that to a salesforce
    ID
  • Performs a query on the contact record using the salesforce ID to return all fields that i've whitelisted as pertinent.
  • Gets a list of all other objects with a relation back
    to that record (too naive to hope there's one API call for this?)

That way, so long as global sobject describe is working and by user can select which objects are pertinent in the app, I could rely on some more consistent background salesforce fields (relations?) instead of developer defined fields to perform look ups for associated records?

Does anyone have any experience / neat tricks I could try to nail this one?

Best Answer

Yes, you can drive your process from metadata. The key is, as you guessed, in the describe data, specifically, in childRelationships. This will tell you all the objects that have a relationship back to contact. I used Workbench to create this example; I'm pasting in some of the raw JSON that the API returns, but you can map it to what restforce gives you.

/services/data/v32.0/sobjects/Contact/describe

{
  "actionOverrides" : [ ],
  "activateable" : false,
  "childRelationships" : [ {
    "cascadeDelete" : false,
    "childSObject" : "AcceptedEventRelation",
    "deprecatedAndHidden" : false,
    "field" : "RelationId",
    "relationshipName" : "AcceptedEventRelations",
    "restrictedDelete" : false
  }, ...lots more..., {
    "cascadeDelete" : false,
    "childSObject" : "Tweet__c",
    "deprecatedAndHidden" : false,
    "field" : "Contact__c",
    "relationshipName" : "Tweets__r",
    "restrictedDelete" : true
  }, ...lots more..., {
    "cascadeDelete" : false,
    "childSObject" : "Visit_Preparation__c",
    "deprecatedAndHidden" : false,
    "field" : "Contact__c",
    "relationshipName" : "Visit_Preparation__r",
    "restrictedDelete" : false
  } ],
  "compactLayoutable" : true,
  ...
}

You get a whole lot of info back in childRelationships, but you probably want to focus on elements where childSObject has the __c suffix - your custom objects. In my case, these are Tweet__c and Visit_Preparation__c. Now you can build a SOQL query to pull back all the different related records in a single request. For the above example, it is

SELECT Id, Name, 
       (SELECT Id, Name FROM Tweets__r), 
       (SELECT Id, Name FROM Visit_Preparation__r) 
FROM Contact 
WHERE UID__c = 'ID101'

The response looks like

{
  "totalSize" : 1,
  "done" : true,
  "records" : [ {
    "attributes" : {
      "type" : "Contact",
      "url" : "/services/data/v32.0/sobjects/Contact/003E000001CbzBiIAJ"
    },
    "Id" : "003E000001CbzBiIAJ",
    "Name" : "Pat Patterson",
    "Tweets__r" : {
      "totalSize" : 2,
      "done" : true,
      "records" : [ {
        "attributes" : {
          "type" : "Tweet__c",
          "url" : "/services/data/v32.0/sobjects/Tweet__c/a0KE000000CmllPMAR"
        },
        "Id" : "a0KE000000CmllPMAR",
        "Name" : "570319233919422464"
      }, {
        "attributes" : {
          "type" : "Tweet__c",
          "url" : "/services/data/v32.0/sobjects/Tweet__c/a0KE000000Cmm6SMAR"
        },
        "Id" : "a0KE000000Cmm6SMAR",
        "Name" : "570394001620103168"
      } ]
    },
    "Visit_Preparation__r" : {
      "totalSize" : 1,
      "done" : true,
      "records" : [ {
        "attributes" : {
          "type" : "Visit_Preparation__c",
          "url" : "/services/data/v32.0/sobjects/Visit_Preparation__c/a0BE000000SEPpEMAX"
        },
        "Id" : "a0BE000000SEPpEMAX",
        "Name" : "Boondoggle"
      } ]
    }
  } ]
}

A call to the global sobject describe will give you the API name -> Label mapping, so it looks like you can do everything you need with 3 calls, the results of 2 of which (the contact and global describe) are relatively static, so you might be able to cache them.

Related Topic