Friday, December 28, 2012

Using standard controller's "getRecord" method

There seems to be a common problem using the getRecord function, so I am going to write it down here.  This problem has haunted me a few times now.

Whenever you use a controller extension, extending a standard object, you can use the getRecord() to get the reference to the record.  For example, if you have an extension to the contact object, this can be what your Apex class looks like:

public class ContactExtension
{
    private Contact contact;

    public ContactExtension(ApexPages.StandardController sController)
    {
        this.contact = (Contact)sController.getRecord();
    }
}

Besides the obvious constructor, you will most likely have some other methods inside your class to manipulate the contact.  Let's say you want to provide a form so the user can change email address and phone number of the contact.  Your VisualForce class looks very simple

<apex:page standardController="Contact" extensions="ContactExtension">
    {!greeting}
    <apex:form>
        <apex:inputField value="{!contact.Email" />
        <apex:inputField value="{!contact.MobilePhone}" />
        <apex:commandButton action="{!save}" value="Save" />
    </apex:form>
</apex:page>

The {!greeting} is going to display some greeting messages.  That means we will need to write a getGreeting() method in the class.

If this is the definition of your getGreeting() inside the class,

public String getGreeting()
{
    return 'Hi ' + contact.FirstName + ' how have you been?  Please update the following.';
}

you will then get an error message that looks like this:

System.SObjectException: SObject row was retrieved via SOQL without querying the requested field: Contact.FirstName

Most of us think that as soon as we call the getRecord() method, then the entire record is loaded into the contact variable.  However if you have an object with 5000 fields, and you only need to use 2 or 3 in your Visualforce pages, do you think it really makes sense to load all 5000 fields for that record into the contact variable?  Probably not.

Then what can you do?  The easiest way is to do add this into your Visualforce page:
<apex:outputText value="{!contact.FirstName}"/>

If you say, but I don't want to display the contact name twice!  Then you can simply change this to:
<apex:outputText value="{!contact.FirstName}" rendered="false" />

By adding rendered="false", that will hide this variable.  Or perhaps use <apex:variable> instead, such as:
<apex:variable value="{!contact.FirstName}" var="contactFName" />

When you load this Visualforce page, the standard controller will scan through your Visualforce page and knows that you need this field, and when making the getRecord() method call, it automatically and implicitly adds this FirstName field into the SOQL.

Remember, if you want to use any field in your extension via the getRecord() method, you need to let it be known by making sure that the field exists in your Visualforce page!

(Another alternative is to write your own SOQL in your Apex class, but why make an extra SOQL call when you can avoid it?)

Wednesday, December 12, 2012

Caution with the ID field


The subject says it all.  It is important to know that Salesforce's Id field is a field that needs to be handled with care.  It is a read-only field.  

Here is a use case that I have encountered.  I'm going to keep using my Rig example from my last blog entry.  Suffice to know is that I have an object that stores information about rigs, which are big machineries you deploy at well sites in order to drill oil.  One of the things we capture in the Rig object is the Id of the rig owner (Rig_Owner__c), which is a lookup to the standard Account object.

Let's say all account information and rig information has been imported into Salesforce from an external system, and all data has been verified to be imported successfully.   Now one of the rigs has been sold from company A to company B.  We need to update the rig information.  When the user clicks the lookup icon to the Account, too many records are returned because the system has over 50000 account records.

It's decided we need to add a custom checkbox (or boolean) field into the Account object, called Rig_Owner_Indicator__c, and is set to true only for accounts that are rig owners.  This should help eliminate the number of records returned when we use the Lookup Filter associated with the lookup field.

After adding the column and defaulting the field values for all records to be false, now it's time we set those account records that are actually rig owners.  All rig owners are already identified in the Rig_Owner__c of the Rig object.  So one way we can run our code is:

List<Rig__c> rigs = [SELECT Rig_Owner__c FROM Rig__c];
List<Account> accounts = new List<Account>();
for (Rig__c rig : rigs)
{
    account RigAccount = new Account();
    RigAccount.Id = rig.Rig_Owner__c;
    RigAccount.Rig_Owner_Indicator__c = true;
    accounts.add(RigAccount);
}
update accounts;

However, you will get an error message that says the Id is not writeable, as this screen shot from Eclipse shows.


The offending line is
    RigAccount.Id = rig.Rig_Owner__c;

In order to fix the problem, you will need to instantiate your Id in the line where you create a new instance of the Accout SObject.  That is, if you rewrite the above as below (note the bolded line), the Apex code will execute successfully, and your Rig Owner Indicator will be populated as desired.

List<Rig__c> rigs = [SELECT Rig_Owner__c FROM Rig__c];
List<Account> accounts = new List<Account>();
for (Rig__c rig : rigs)
{
    account RigAccount = new Account(Id = rig.Rig_Owner__c);
    RigAccount.Rig_Owner_Indicator__c = true;
    accounts.add(RigAccount);
}
update accounts;

The code above will now compile.  That goes to show that when you are trying to manipulate the Id field, you need to set it when you instantiate the SObject.  Make sure you do NOT attempt to set the Id field.  Remember, it is a read-only field.

Lastly, the revised Apex code is still faulty because it will fail if more than one rig belongs to the same rig owner.  This is related to adding SObjects to List collection objects.  I will leave that to my next blog.

Tuesday, December 4, 2012

How to write SOQL to traverse across two objects and their junction object


Last month I built a prototype for a Salesforce organized event in Calgary to show how to build applications in the cloud for the energy industry.

It was a fairly simple system:  it's about deploying rigs to wells so that drilling can happen, and so that oil can be extracted.

This is a prototype to show what Salesforce can do so the use case is very simplified.  To model this use case, I have a Rig object, a Well object and a junction object called the Rig Deployment that records which rig is deployed to which well, start date and the scheduled end date, etc.  This Rig Deployment object contains all current and past deployments so this object has potential to contain a lot of data.

Using the schema builder, the entity relationship diagram looks like this:




The first screen of the prototype is to display a google map of deployed rigs in Canada (there are already lots of examples online to show how to display google maps with Visualforce and Apex so I will not cover that here, however if you want me to go through that then please leave me a note).

This first screen requires a query into the Rig Deployment object because the date related fields in the object contain information on which rigs are currently deployed.  Remember that this object contains current and past data, and we only want the current data.  All I need to display is a map with wells that have some rigs deployed at the location, the names of the rigs deployed and the scheduled completion date.

If you have read my last blog, you already know that when I started learning SOQL I had a few challenges because I had to change my mindset when it comes to writing the query language.  In traditional SQL you can write the query with a simple SELECT statement and a few INNER JOINs and you're done.  Once again, writing a SOQL query to traverse across two objects and the junction object was a daunting task at first.  You really need to divide and conquer.  Here is how.

First, you need to know that there are two ways in which you can traverse objects in SOQL.  One is to go from child to parent, and one is to go from parent to children.

Going from child to parent is pretty straightforward because all you need is the relationship name (which ends   in __r for custom objects).  The Rig Deployment object is the child object of both rig object and well object.  So let's say you want to retrieve the name of the well, then the SOQL is simply:

SELECT 
    Scheduled_Completion_Date__c,
    Well__r.Name 
FROM 
    Rig_Deployment__c 

which will return all the records in Rig Deployment object with the name of the well.  Note that because we are traversing the information from child to parent, when we access the name of the well, we need to indicate the relationship by using Well__r instead of Well__c.  Remember we only want the information of currently deployed wells, so we need to include the date filter.  The above will be rewritten as:


SELECT 
    Scheduled_Completion_Date__c,
    Well__r.Name 
FROM 
    Rig_Deployment__c 
WHERE
    Completion_Date__c = NULL

Going from parent to child looks a little more complicated but only because you need to write a sub-query.   Now this time let's say you want to go from Rig to Rig Deployment object to display a list of rigs and the scheduled completion date for the current deployment.  The SOQL is:

SELECT
    Name, 
    (
    SELECT 
        Scheduled_Completion_Date__c
    FROM
        Rig_Deployments__r
    WHERE
        Completion_Date__c = NULL
    )
FROM
    Rig__c

Remember that inside the sub-query you need to pluralize the object (because it's the child object and is supposed to have more than one record) and then append the pluralized object name with __r instead of __c because you are traversing from the Rig object to its related Rig Deployment object.

Now the last piece of the puzzle.  You need to combine the two queries together.  But look!  Did you see how almost identical the two queries already are?  The only differences between the two are:
1) The second query contains the column indicating the name of the well, and
2) The second query uses the pluralized object name that ends with __r in the FROM clause. 

That means in this case all you really need to do is to add the well name column into the second query and you're almost done.

SELECT
    Name, 
    (
    SELECT 
        Scheduled_Completion_Date__c,
        Well__r.Name
    FROM
        Rig_Deployments__r
    WHERE
        Completion_Date__c = NULL
    )
FROM
    Rig__c

Now, I said almost done because it seems to me SOQL joins by outer join by default.  So the above query will return all rigs, even though they are not currently deployed anywhere.

If you ONLY want rigs that are currently deployed, excluding rigs that are sitting idle, then you'll need to add a WHERE clause.  But worry not.  You already wrote it, sort of, when you wrote the first query above.  The WHERE clause will indicate that the rig must be in the Rig Deployment object that has a blank completion date.  So the final query is merely an extension to the last query.

This is the final SOQL (the Rig__c in the Rig Deployment object is the lookup field to the Rig object - that is, it stores the Salesforce record id).

SELECT
    Name, 
    (
    SELECT 
        Scheduled_Completion_Date__c,
        Well__r.Name
    FROM
        Rig_Deployments__r
    WHERE
        Completion_Date__c = NULL
    )
FROM
    Rig__c

WHERE
    Id IN
    (
    SELECT 
        Rig__c
    FROM
        Rig_Deployment__c
    WHERE
        Completion_Date__c = NULL
    )

You see that my main query retrieves data from the Rig object.  However it doesn't have to be this way.  You can also use the Well object as your main object in the query.  It all depends on what fields you are trying to retrieve.  The lesson I learned from this is that the junction object ends up sitting in the sub-query.  It is after all, the child object so it should be used in the sub-query.

Now, does anyone know is there an even simpler way to write an inner join without having to write the last WHERE clause I added above?