Apex Trailhead
Apex Programing Language
Prepared for [Client company]
Created by [Consultant company]
[Company]
Apex is a strongly typed,object oriented program language that allows developers to execute flow and transaction control statements on the salesforce platform.
1.Unlike other object-oriented programming languages, Apex supports:
Cloud development as Apex is stored, compiled, and executed in the cloud.
Triggers, which are similar to triggers in database systems.
Database statements that allow you to make direct database calls and query languages to query and search data.
Transactions and rollbacks.
The global access modifier, which is more permissive than the public modifier and allows access across namespaces and applications.
Versioning of custom code.
Data type in Apex -
A primitive, such as an Integer, Double, Long, Date, Datetime, String, ID, Boolean
An sObject, either as a generic sObject or as a specific sObject, such as an Account, Contact, or MyCustomObject__c
A list (or array) of primitives, sObjects, user defined objects, objects created from Apex classes, or collections
A set of primitives, sObjects, user defined objects, objects created from Apex classes, or collections
A map from a primitive to a primitive, sObject, or collection
A typed list of values, also known as an enum
User-defined Apex classes
System-supplied Apex classes
Apex Collection : List
Lists hold an ordered collection of objects. Lists in Apex are synonymous with arrays and the two can be used interchangeably.
The following two declaration are equivalent
List<String> colors = new List<String>();
String[] colors = new List<String>();
Each Salesforce record is represented as an sObject before it is inserted into Salesforce. Likewise, when persisted records are retrieved from Salesforce, they’re stored in an sObject variable.
Account acct = new Account(Name='Acme');
For custom objects and custom fields, the API name always ends with the __c suffix. For custom relationship fields, the API name ends with the __r suffix. For example:
Before you can insert a Salesforce record, you must create it in memory first as an sObject. Like with any other object, sObjects are created with the new operator:
The account referenced by the acct variable is empty because we haven’t populated any of its fields yet. There are two ways to add fields: through the constructor or by using dot notation.
Account acct = new Account();
Account acct = new Account(Name='Acme', Phone='(415)555-1212', NumberOfEmployees=100);
Account acct = new Account();
acct.Name = 'Acme';
acct.Phone = '(415)555-1212';
acct.NumberOfEmployees = 100;
This example shows how any Salesforce object, such as an account or a custom object called Book__c, can be assigned to a generic sObject variable.
sObject sobj1 = new Account(Name='Trailhead');
sObject sobj2 = new Book__c(Name='Workbook 1');
// Doubt cleared
// Cast a generic sObject to an Account
Account acct = (Account)myGenericSObject;
// Now, you can use the dot notation to access fields on Account
String name = acct.Name;
String phone = acct.Phone;
//
DML Statements
The following DML statements are available.
insert
update
upsert
delete
undelete
merge
The upsert DML operation creates new records and updates sObject records within a single statement, using a specified field to determine the presence of existing objects, or the ID field if no field is specified.
The merge statement merges up to three records of the same sObject type into one of the records, deleting the others, and re-parenting any related records.
Upsert uses the sObject record's primary key (the ID), an idLookup field, or an external ID field to determine whether it should create a new record or update an existing one:
If insert was used in this example instead of upsert, a duplicate Jane Smith contact would have been inserted.
Execute this snippet in the Execute Anonymous window of the Developer Console.
Contact jane = new Contact(FirstName='Jane', LastName='Smith', Email='jane.smith@example.com', Description='Contact of the day'); insert jane; // 1. Upsert using an idLookup field // Create a second sObject variable. // This variable doesn’t have any ID set. Contact jane2 = new Contact(FirstName='Jane', LastName='Smith', Email='jane.smith@example.com', Description='Prefers to be contacted by email.'); // Upsert the contact by using the idLookup field for matching. upsert jane2 Contact.fields.Email; // Verify that the contact has been updated System.assertEquals('Prefers to be contacted by email.', [SELECT Description FROM Contact WHERE Id=:jane.Id].Description);
Copy
Database Methods
Apex contains the built-in Database class, which provides methods that perform DML operations and mirror the DML statement counterparts.
These Database methods are static and are called on the class name.
Database.insert()
Database.update()
Database.upsert()
Database.delete()
Database.undelete()
Database.merge()
When this parameter is set to false, if errors occur on a partial set of records, the successful records will be committed and errors will be returned for the failed records. Also, no exceptions are thrown with the partial success option.
Database.insert(recordList, false); //
By default, the allOrNone parameter is true, which means that the Database method behaves like its DML statement counterpart and will throw an exception if a failure is encountered
// Create a list of contacts
List<Contact> conList = new List<Contact> {
new Contact(FirstName='Joe',LastName='Smith',Department='Finance'),
new Contact(FirstName='Kathy',LastName='Smith',Department='Technology'),
new Contact(FirstName='Caroline',LastName='Roth',Department='Finance'),
new Contact()};
// Bulk insert all contacts with one DML call
Database.SaveResult[] srList = Database.insert(conList, false);
// Iterate through each returned result
for (Database.SaveResult sr : srList) {
if (sr.isSuccess()) {
// Operation was successful, so get the ID of the record that was processed
System.debug('Successfully inserted contact. Contact ID: ' + sr.getId());
} else {
// Operation failed, so get all errors
for(Database.Error err : sr.getErrors()) {
System.debug('The following error has occurred.');
System.debug(err.getStatusCode() + ': ' + err.getMessage());
System.debug('Contact fields that affected this error: ' + err.getFields());
}
}
}
DML operations execute within a transaction. All DML operations in a transaction either complete successfully, or if an error occurs in one operation, the entire transaction is rolled back and no data is committed to the database.
// SOQL
Account[] accts = [SELECT Name,Phone FROM Account];
Bind variable
SOQL statements in Apex can reference Apex code variables and expressions if they are preceded by a colon (:). The use of a local variable within a SOQL statement is called a bind.
This example shows how to use the targetDepartment variable in the WHERE clause.
String targetDepartment = 'Wingo'; Contact[] techContacts = [SELECT FirstName,LastName FROM Contact WHERE Department=:targetDepartment];
Query Related Records
Child records from the SOQL result by using the Contacts relationship name on the sObject.
Account[] acctsWithContacts = [SELECT Name, (SELECT FirstName,LastName FROM Contacts) FROM Account WHERE Name = 'SFDC Computing']; // Get child records Contact[] cts = acctsWithContacts[0].Contacts; System.debug('Name of first associated contact: ' + cts[0].FirstName + ', ' + cts[0].LastName);
Parent record from the Child component-
Contact[] cts = [SELECT Account.Name FROM Contact
WHERE FirstName = 'Carol' AND LastName='Ruiz'];
Contact carol = cts[0];
String acctName = carol.Account.Name;
System.debug('Carol\'s account name is ' + acctName);
// Salesforce Object Search Language (SOSL) is a Salesforce search language that is used to perform text searches in records
List<List<SObject>> searchList = [FIND 'SFDC' IN ALL FIELDS
RETURNING Account(Name), Contact(FirstName,LastName)];
FIND {SearchQuery} [IN SearchGroup] [RETURNING ObjectsAndFields]
String soslFindClause = 'Wingo OR SFDC';
List<List<sObject>> searchList = [FIND :soslFindClause IN ALL FIELDS
RETURNING
ALL FIELDS
NAME FIELDS
EMAIL FIELDS
PHONE FIELDS
SIDEBAR FIELDS
Account(Name),Contact(FirstName,LastName,Department)];
Account[] searchAccounts = (Account[])searchList[0];
Contact[] searchContacts = (Contact[])searchList[1];
System.debug('Found the following accounts.');
for (Account a : searchAccounts) {
System.debug(a.Name);
}
System.debug('Found the following contacts.');
for (Contact c : searchContacts) {
Sy
Trigger syntax
trigger TriggerName on ObjectName (trigger_events) {
code_block
}
ore or after insert, update, delete, and undelete operations, specify multiple trigger events in a comma-separated list. The events you can specify are:
before insert
before update
before delete
after insert
after update
after delete
after undelete
before or after insert, update, delete, and undelete operations, specify multiple trigger events in a comma-separated list. The events you can specify are:
before insert
before update
before delete
after insert
after update
after delete
after undelete
Before triggers are used to update or validate record values before they’re saved to the database.
After triggers are used to access field values that are set by the system (such as a record's Id or LastModifiedDate field), and to affect changes in other records. The records that fire the after trigger are read-only.
Trigger.new contains all the records that were inserted in insert or update triggers. Trigger.old provides the old version of sObjects before they were updated in update triggers, or a list of deleted sObjects in delete triggers.
trigger HelloWorldTrigger on Account (before insert) {
for(Account a : Trigger.new) {
a.Description = 'New description';
}
}
To prevent saving records in a trigger, call the addError() method on the sObject in question. The addError() method throws a fatal error inside a trigger. The error message is displayed in the user interface and is logged.
Apex calls to external Web services are referred to as callouts.
To make a callout from a trigger, call a class method that executes asynchronously. Such a method is called a future method and is annotated with @future(callout=true).
// Asynchronous
In a nutshell, asynchronous Apex is used to run processes in a separate thread, at a later time.
Advantage -
User efficiency,Scalability,Higher Limits
Future Method Syntax
Future methods must be static methods, and can only return a void type. The specified parameters must be primitive data types, arrays of primitive data types, or collections of primitive data types.
public class SomeClass {
@future
public static void someFutureMethod(List<Id> recordIds) {
List<Account> accounts = [Select Id, Name from Account Where Id IN :recordIds];
// process account records to do awesome stuff
}
}
Note - The reason why objects can’t be passed as arguments to future methods is because the object can change between the time you call the method and the time that it actually executes.
Test code cannot actually send callouts to external systems, so you’ll have to ‘mock’ the callout for test coverage.
// doubt in mock test class
https://trailhead.salesforce.com/content/learn/modules/asynchronous_apex/async_apex_future_methods
You can’t call a future method from a future method. Nor can you invoke a trigger that calls a future method while running a future method. See the link in the Resources for preventing recursive future method calls.
@isTest
public class SMSCalloutMock implements HttpCalloutMock {
public HttpResponse respond(HttpRequest req) {
// Create a fake response
HttpResponse res = new HttpResponse();
res.setHeader('Content-Type', 'application/json');
res.setBody('{"status":"success"}');
res.setStatusCode(200);
return res;
}
}
Batch -
Every transaction starts with a new set of governor limits, making it easier to ensure that your code stays within the governor execution limits.
If one batch fails to process successfully, all other successful batch transactions aren’t rolled back.
Database.Batchable
start ->
returns either a Database.QueryLocator object or an Iterable that contains the records or objects passed to the job.
With the QueryLocator object, the governor limit for the total number of records retrieved by SOQL queries is bypassed and you can query up to 50 million records. However, with an Iterable, the governor limit for the total number of records retrieved by SOQL queries is still enforced.
execute
This method takes the following:
A reference to the Database.BatchableContext object.
A list of sObjects, such as List<sObject>, or a list of parameterized types. If you are using a Database.QueryLocator, use the returned list.
finish
—---------------------------------------------------------------
public class MyBatchClass implements Database.Batchable<sObject> {
public (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
// collect the batches of records or objects to be passed to execute
}
public void execute(Database.BatchableContext bc, List<P> records){
// process each batch of records
}
public void finish(Database.BatchableContext bc){
// execute any post-processing operations
}
}
Invoking a Batch Class
MyBatchClass myBatchObject = new MyBatchClass();
Id batchId = Database.executeBatch(myBatchObject);
// for limiti ng batch size
Id batchId = Database.executeBatch(myBatchObject, 100);
Each batch Apex invocation creates an AsyncApexJob record so that you can track the job’s progress. You can view the progress via SOQL or manage your job in the Apex Job Queue. We’ll talk about the Job Queue shortly.
AsyncApexJob job = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors FROM AsyncApexJob WHERE ID = :batchId ];
Using State in Batch Apex -
If you specify Database.Stateful in the class definition, you can maintain state across all transactions. When using Database.Stateful, only instance member variables retain their values between transactions.
public class UpdateContactAddresses implements
Database.Batchable<sObject>, Database.Stateful {
// instance member to retain state across transactions
public Integer recordsProcessed = 0;
public Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
'SELECT ID, BillingStreet, BillingCity, BillingState, ' +
'BillingPostalCode, (SELECT ID, MailingStreet, MailingCity, ' +
'MailingState, MailingPostalCode FROM Contacts) FROM Account ' +
'Where BillingCountry = \'USA\''
);
}
public void execute(Database.BatchableContext bc, List<Account> scope){
// process each batch of records
List<Contact> contacts = new List<Contact>();
for (Account account : scope) {
for (Contact contact : account.contacts) {
contact.MailingStreet = account.BillingStreet;
contact.MailingCity = account.BillingCity;
contact.MailingState = account.BillingState;
contact.MailingPostalCode = account.BillingPostalCode;
// add contact to list to be updated
contacts.add(contact);
// increment the instance member counter
recordsProcessed = recordsProcessed + 1;
}
}
update contacts;
}
public void finish(Database.BatchableContext bc){
System.debug(recordsProcessed + ' records processed. Shazam!');
AsyncApexJob job = [SELECT Id, Status, NumberOfErrors,
JobItemsProcessed,
TotalJobItems, CreatedBy.Email
FROM AsyncApexJob
WHERE Id = :bc.getJobId()];
// call some utility to send email
EmailUtils.sendMessage(job, recordsProcessed);
}
}
Queueable Apex
Non-primitive types: Your Queueable class can contain member variables of non-primitive data types, such as sObjects or custom Apex types. Those objects can be accessed when the job executes.
Monitoring: When you submit your job by invoking the System.enqueueJob() method, the method returns the ID of the AsyncApexJob record. You can use this ID to identify your job and monitor its progress, either through the Salesforce user interface in the Apex Jobs page, or programmatically by querying your record from AsyncApexJob.
Chaining jobs: You can chain one job to another job by starting a second job from a running job. Chaining jobs is useful if you need to do some sequential processing.
public class SomeClass implements Queueable {
public void execute(QueueableContext context) {
// awesome code here
}
}
///
public class UpdateParentAccount implements Queueable {
private List<Account> accounts;
private ID parent;
public UpdateParentAccount(List<Account> records, ID id) {
this.accounts = records;
this.parent = id;
}
public void execute(QueueableContext context) {
for (Account account : accounts) {
account.parentId = parent;
// perform other processing or callout
}
update accounts;
}
}
// To add this class as a job on the queue, execute the following code:
// find all accounts in ‘NY’
List<Account> accounts = [select id from account where billingstate = ‘NY’];
// find a specific parent account for all records
Id parentId = [select id from account where name = 'ACME Corp'][0].Id;
// instantiate a new instance of the Queueable class
UpdateParentAccount updateJob = new UpdateParentAccount(accounts, parentId);
// enqueue the job for processing
ID jobID = System.enqueueJob(updateJob);
Chaining Jobs
public class FirstJob implements Queueable {
public void execute(QueueableContext context) {
// Awesome processing logic here
// Chain this job to next job by submitting the next job
System.enqueueJob(new SecondJob());
}
}
//
You can add up to 50 jobs to the queue with System.enqueueJob in a single transaction.
When chaining jobs, you can add only one job from an executing job with System.enqueueJob, which means that only one child job can exist for each parent queueable job. Starting multiple child jobs from the same queueable job is a no-no.
//
clone(preserveId, isDeepClone, preserveReadonlyTimestamps, preserveAutonumber)
Schedule Jobs Using the Apex Scheduler -
To invoke Apex classes to run at specific times, first implement the Schedulable interface for the class. Then, schedule an instance of the class to run at a specific time using the System.schedule() method.
public class SomeClass implements Schedulable {
public void execute(SchedulableContext ctx) {
// awesome code here
}
}
The parameter of this method is a SchedulableContext object. After a class has been scheduled, a CronTrigger object is created that represents the scheduled job. It provides a getTriggerId() method that returns the ID of a CronTrigger API object.
public class RemindOpptyOwners implements Schedulable {
public void execute(SchedulableContext ctx) {
List<Opportunity> opptys = [SELECT Id, Name, OwnerId, CloseDate
FROM Opportunity
WHERE IsClosed = False AND
CloseDate < TODAY];
// Create a task for each opportunity in the list
TaskUtils.remindOwners(opptys);
}
}
You can only have 100 scheduled Apex jobs at one time and there are maximum number of scheduled Apex executions per a 24-hour period. See Execution Governors and Limits in the Resources section for details.
RemindOpptyOwners reminder = new RemindOpptyOwners();
// Seconds Minutes Hours Day_of_month Month Day_of_week optional_year
String sch = '20 30 8 10 2 ?';
String jobID = System.schedule('Remind Opp Owners', sch, reminder);
Apex Integration Services
// Adding Comments to your code -
Single line comments - //
Multiple line comments - /* */
Collection -
List - Collection of elements of Same Data Type (Dynamic Data Type )-
A new keyword is used to create the instance of the class .
Store the data in indices - 0 -> n -1
List<Integer> rollNumbers = new List<Integer>{11008890, 11008100, 11007231};
System.debug(rollNumbers);
rollNumbers.add(89897767);
rollNumbers.add(89897764);
rollNumbers.add(89897765);
System.debug(rollNumbers);
// get item on index 1
Integer rollNum = rollNumbers.get(1);
System.debug(rollNum);
System.debug(rollNumbers.get(1));
// add item on index 4
rollNumbers.add(4, 99990000);
System.debug(rollNumbers);
// get the list size
System.debug(rollNumbers.size());
// remove the item on index 3
rollNumbers.remove(3);
System.debug(rollNumbers);
System.debug(rollNumbers.size());
// update item on index 1
rollNumbers.set(1, 44444444);
System.debug(rollNumbers);
// clear the list
rollNumbers.clear();
System.debug(rollNumbers);
System.debug(rollNumbers.size());
// below line will throw an error
rollNumbers.set(1, 44444444);
System.debug(rollNumbers);
// Set -
An unordered collection of elements of the same Data Type .(Unique)
Set<Integer> rollNumbers = new Set<Integer>{11008890, 11008100, 11007231};
System.debug(rollNumbers);
rollNumbers.add(89897767);
rollNumbers.add(89897764);
rollNumbers.add(89897765);
System.debug(rollNumbers);
// adding duplicate values - NOT ALLOWED
rollNumbers.add(89897767);
System.debug(rollNumbers);
// check if set has an item
System.debug(rollNumbers.contains(89897764));
System.debug(rollNumbers.contains(345345));
// delete an item
rollNumbers.remove(89897765);
System.debug(rollNumbers);
// get set size
System.debug(rollNumbers.size());
// check if set is empty
System.debug(rollNumbers.isEmpty());
// remove all items
rollNumbers.clear();
System.debug(rollNumbers.isEmpty());
// Map
A Key value pair based collection
Key - Can’t have duplicates
But Value can have duplicates.
Comments
Post a Comment