Skip to content

Creating Reusable Views

Charlie Jonas edited this page Mar 8, 2019 · 9 revisions

A common scenario when working with SObjects is the need to query the same data from different starting points.

EXAMPLE

PLAYGROUND DEMO

Say we have a function that takes a Contact and validates that some fields are set correctly:

function checkContactInfo(c: Contact){
    if(!c.phone){
        console.warn('MISSING PHONE');
    }
    
    if(!c.email){
        console.warn('MISSING EMAIL');
    }
    
    if(!c.mailingStreet || !c.mailingCity || !c.mailingState){
        console.warn('MISSING ADDRESS FIELDS');
    }

    if(!c.owner.isActive){
        console.warn('RECORD OWNER IS INACTIVE');
    }
 }

Besides just querying the contacts directly, we might also want need to validate contacts originating from Account, Case, Opportunity, etc...

To be able to reuse the SELECT requirements needed in all these places, we can create "view" functions. A "view" is just a function that takes a FieldResolver and returns a string[]:

 const userView = (uFields: FieldResolver<User>) => {
    return uFields.select('isActive', 'department')  
 }

 const contactView = (cFields: FieldResolver<Contact>) => {
    return [
       ...cFields.select('phone', 'email', 'mailingStreet', 'mailingCity', 'mailingState'),
       ...userView(cFields.parent('owner')),
       ...userView(cFields.parent('lastModifiedBy')),
    ]
 }

You can already see how we are benefiting from reusability in the contactView, by ensuring that the "User" object we retrieve for both the Owner and LastModifiedBy references have the same fields.

Now can use our contactView in a query directly against it's table like so:

await Contact.retrieve(cFields => (
    {
        select: contactView(cFields),
        limit: 1
    }
));

Or even in an inner query:

await Account.retrieve(accFields => (
    {
        select: [
            accFields.subQuery('contacts', cFields => (
                {
                    select: contactView(cFields)
                }
            ))
        ],
        limit: 1
    }
));

Considerations:

  1. You need to carefully consider recursion when building views to avoid in a infinite loop:
const contactViewRecursiveBroken = (contactFields: FieldResolver<Contact>): string[] => {
    return [
        ...contactFields.select('email', 'name'),
        ...contactViewRecursiveBroken(contactFields.parent('reportsTo')) //this will break stuff!
    ]
}

This can be solved by checking that the traversed property of the FieldResolver has hit it's limit and exiting:

const contactViewRecursive = (contactFields: FieldResolver<Contact>): string[] => {
    if(contactFields.traversed.length == 5){ //max relationships
        return [];
    }
    return [
        ...contactFields.select('email', 'name'),
        ...contactViewRecursive(contactFields.parent('reportsTo')) //this will break stuff!
    ]
}
  1. Avoid adding Inner Queries to Views as it reduces reusability
  2. Consider performance when creating really large views (especially using views in inner queries)

Clone this wiki locally