7

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?

  • n.b. describes are done on the schema - i.e. on the Contact SObject, not on individual records (your second bullet) – cropredy Mar 2 '15 at 17:29
  • Thx will amend for clarity – Huw Mar 2 '15 at 20:38
8

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.

  • 2
    This is an amazing answer, thanks so much @metadaddy. I'll give it a shot and come back for the answered tick once it works. Really appreciate the help! – Huw Mar 3 '15 at 11:37
  • Confirmed - it works... now I just need to find out what my limits are... I fear I'm requesting too many fields! – Huw Mar 9 '15 at 11:24
  • 1
    You can always split your fields between multiple queries, but that, obviously will complicate your code and require more API calls. – metadaddy Mar 9 '15 at 16:35
  • I think I may have to. My query is ~29 objects, most with around 10-25 fields to query (if not nil, which I'm screening after the api call in Rails) but a couple have 200+ fields to query... Guess I'll write a digest method that breaks down and reassembles large calls. – Huw Mar 9 '15 at 16:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.