- Collections
- OOPS Concept
- Static Keyword
- Access Specifier
- Trigger
- How to avoid Recursion in Trigger
- Order of execution
- Apex Test Class
- DML (Data Manipulation Language)
- SOQL Cheat Sheet
- SOSL
- Asynchronous Processing Basics
- Integration in Salesforce
- Deployment
- Exception Handling
- Alternate of SELECT * in Salesforce
- Javascript Remoting or Remoting or APEX Remoting or Remote Action
- Static Resources in Salesforce
- Generate PDF in Salesforce
- Security in Apex class
- Send Email in Salesforce with Apex Method
- Server Side Validation
- Benefits of Test isRunningTest
- Bulk API
- Invocable Method
-
Field History Tracking You can now find a Field History Tracking page in Setup where you can control both the tracked objects as well as the fields. Additionally, you can easily see the number of tracked fields right from the get-go before starting to make any changes. Goto -> setup -> field histrory tracking-- objects-- No. of fields
-
Use Blank Spaces to Align Fields on Dynamic Forms-Enabled Pages for example - a form has two section in left we have address field which will take 3/4 rows so to align the same in right section we can add blank field which will be part of 200 fields in a object
-
- Get a Summary of a User’s Permissions and Access Troubleshooting user permissions and access just got a little bit easier with the User Access Summary on a user record. The summary view will display various permissions and memberships in an easy-to-use view. goto setup -> user -> choose shikha bansal ->> user access summary
-
Einstein for Flow
-
LWC enhancements
lightning-datatable component now includes a new attribute called wrap-table-header, allowing the header text to wrap within column widths.
<lightning-datatable
key-field="id"
data={data}
columns={columns}
wrap-table-header={true}>
</lightning-datatable>
The lightning-input component introduces a new attribute called role, enabling the creation of accessible combo boxes. Accessibility behaviors have been added or modified, including passing down ARIA attributes for various input types and removing the role=alert attribute for specific input types.
Example for lightning-input changes:
<!-- New attribute: role -->
<lightning-input
type="text"
role="combobox">
</lightning-input>
- There are 3 types of collections.
(https://intellipaat.com/blog/collection-in-salesforce/#no3)
A list can hold any type of data and is one of the most important types of collection. A list is an ordered collection of any data type such as primitive types, collections, sObjects, user-defined types, and built-in Apex types.
The following are the key features of a list collection in Salesforce:
- Duplicates and nulls are allowed in a list collection.
- "List" is the keyword to declare a list collection.
- In a list collection, the index position of the first entry is always zero (0).
- An Apex list collection has the ability to grow dynamically over time.
- In a list collection, the data type can be both primitive and non-primitive.
The keyword followed by the data type has to be used within <> characters to define a list collection.
The syntax for a list is as follows:
// Create an empty list of String
List<String> my_list = new List<String>();
List<Integer> myList = new List<Integer>(); // Define a new list
myList.add(47); // Adds a second element of value 47 to the end of the list
Integer i = myList.get(0); // Retrieves the element at index 0
myList.set(0, 1); // Adds the integer 1 to the list at index 0
myList.remove(0) // Remove element of 0 index
myList.clear(); // Removes all elements from the list
integer myListLength = myList.size(); // How many items in the list
List<String> myList = new List<String>();
myList.add('Harry');
myList.add('Ram');
myList.add('Shyam');
myList.add('Rishabh');
myList.add('Madhav');
myList.add('Shikha');
system.debug('Collection list : '+myList);
myList.remove(2);
myList.set(3,'Hari');
integer totalListLength = myList.size();
system.debug('Total List length : ' + totalListLength);
// check if any item exits inside list
if(myList.contains('Rishabh')){
system.debug('Yes, Rishabh is exist');
}
if(myList.contains('Pankaj')){
system.debug('Yes, Pankaj is exist');
} else {
system.debug('Pankaj is not exist inside the list');
}
// for each loop
for(string name: myList){
system.debug('Person Name : ' + name);
}
// check if myList empty
if(myList.isEmpty()){
system.debug('myList is empty');
} else {
system.debug('myList is not empty and it contain '+myList.size()+' items');
}
// clear list
myList.clear();
if(myList.isEmpty()){
system.debug('myList is empty');
}
List of all methods available in List : https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_list.htm#apex_System_List_methods
- What is the difference between Add Vs Set function in List collection ? A. Set is used for replace any value while Add is used for add new value at the end of the list we don't need to pass index number in Add.
Set is an unordered collection. It is a unique set of values that do not have any duplicates. If you want no duplicates in your collection, then you should opt for this collection.
The following are the key features of a set collection in Salesforce:
- Any data types, such as primitive data types and sObjects, are allowed in a set collection.
- A set collection does not have an index.
- A set collection does not contain any duplicates or null values.
- Set collections are not widely used in Salesforce Apex.
- Sets can hold collections that are nested inside of one another.
- You have to use the set keyword followed by the primitive data type name within <> characters to declare a set collection.
The syntax for a set is as follows:
Set<datatype> setName = new Set<datatype>();
Map is a key-value pair that contains each value’s unique key. It is used when something needs to be located quickly.
The following are the key features of a map collection in Salesforce:
- Any data type can be used for both the key and the value in a map collection.
- In a map collection, the null value can be stored in a map key.
- The keys of type string are case-sensitive in a map collection.
- You have to use the map keyword followed by the key and value data types enclosed in <> to declare a map collection.
The syntax for a map is as follows:
Map<String, String> country_currencies = new Map<String, String>();
Q. What is a Class? \
- A class is a user-defined layout or blueprint of an object that describes what a specific kind of object will look like. A class description consists of two things: 1) Attributes or member variables 2) Implementations of behavior or member functions.
- So in object-oriented terminology: A class is a blueprint that defines the variables and the methods common to all objects of a certain kind. It helps us to bind data and methods together, making the code reusable, unlike procedural language.
Q. What is an Object? \
- An object is a single instance of a class, which contains data and methods working on that data. So an object consists of three things:
Special Notes
- When a class is defined, no memory is allocated, but memory is allocated when an object is created.
- We can create many objects from the same class type.
- Functions where they will have return value = non void functions
- Functions where they will NOT have return value = void functions
- Functions where they will expect some parameters/data from us = functions with parameters
- Functions where they will NOT expect any parameters from us = functions WITHOUT parameters
Q. Why we need class in salesforce programming ?
Q. Why we need object when we already have class ?
Q. Can we use standard and custom function in programming ?
Q. Why we need void keyword in function ?
Q. What else I can write if I don't want to use void keyword ?
Q. Can I keep the blank option instead of void I don't want to raise anything so is that possible ?
Q. What is function parameter and why we need that function parameter why we need that ?
Q. I don't want to use function parameter can I keep it blank ?
Current Moral:
Class = Variables + Functions
Variables are for Data storage.
Functions are for performing some Activity | Action.
Problem: Class wont have any memory, so we cant do anything!
Solution : Object
Very Important Current Moral: \
- Class = Variables + Functions (No Memory)
- Object = Variables + Functions + MEMORY
- Integer, Double, String, Boolean = primitive data type AKA system defined data type
- collection or apex class or object = user defined data type
- Whenever is user defined datatype then new keyword is required.
See this example, how to bypass Object with the help of static keyword:
public class AppleMobileClass (
//variable declaration
private static integer DurationOfCall;
public static void CallAPerson(integer NumToCall) {
// code to video call a person
}
}
// Syntax to execute
ClassName.FunctionName(Parameter);
// To execute the function
AppleMobileClass.CallAPerson(9970629222);
SUCCESS !!! NO NEED OF OBJECT !!!
Moral 1:
By using static, we get special/global storage.
Moral 2:
So now by using word "static",
we can give memory to the class as well, this is the famous style of writing program in Salesforce industry. \
(which style is famous? of not using Object / instance style)
- Private
- Public
- Protect
- Global
public class APEXClassForCalculator {
private static integer VarSum;
private static integer VarMultiplication;
// function for addition
public static integer add(integer a, integer b){
VarSum = a + b;
return VarSum;
}
// function for multiplication
public static void multiply(integer a, integer b){
system.debug(a * b);
}
}
APEXClassForCalculator.multiply(3,6);
- Triggers are initiated when a record is inserted, updated, deleted and undeleted.
- Use triggers to do operations that cannot be done by point and click tools provided in Salesforce.
- We can do things using triggers that we can do through Apex, including execution SOQL and DML or calling custom methods.
- Triggers can be created for both Standard and Custom objects.
- We can perform custom operations before or after events to records.
- By default triggers are active as you create them.
- Trigger class will execute automatically based on trigger event what you have definded in Trigger class.
- Trigger file extension is apxt while Apex class file extension is apxc
- One trigger per object so you don’t have to think about the execution order as there is no control over which trigger would be executed first.
- Logic-less Triggers – use Helper classes to handle logic.
- Code coverage 100%
- Handle recursion – To avoid the recursion on a trigger, make sure your trigger is getting executed only one time. You may encounter the error : ‘Maximum trigger depth exceeded’, if recursion is not handled well.
- Before Triggers
- It is used to update or validate record values before saved to database.
- After Triggers
- It is used to access field values that are set by the system such as Ids, and to make changes in the related/other records. The records that fire the after trigger are read-only.
- before insert
- before update
- before delete
- after insert
- after update
- after delete
- after undelete
All triggers define implicit variables that allow developer to access run-time context. These variables are contained in the System. Trigger class.
- isExecuting: Returns true if the current context of Apex code is a trigger, not a VF page, a web service, or an executeanonymous) API call.
- isinsert: Returns true if trigger was fired due to an insert operation, from the Salesforce UI, Apex or API.
- isUpdate: Returns true if trigger was fired due to an update operation, from the Salesforce Ul, Apex or API.
- isDelete: Returns true if trigger was fired due to a delete operation, from the Salesforce Ul, Apex or API.
- isBefore: Returns true if the trigger was fired before any record was saved.
- isAfter: Returns true if the trigger was fired after all records were saved.
- isUndelete: Returns true if the trigger was fired after a record is recovered from Recycle Bin.
- size: The total number of records in a trigger invocation, both old and new
- new:
- Returns a list of new versions of sObject records.
- This sObject list is available in Insert, Update and Undelete triggers, and the records can only be modified in before trigger.
- newMap:
- A Map of ids to the new versions of sObject records.
- Available in after insert, before update, after update, after undelete triggers.
- old:
- Returns a list of old versions of sObject records.
- Available in before update, after update, before delete, after delete triggers.
- oldMap:
- A map of ids to the old versions of sObject records.
- Available in before update, after update, before delete, after delete triggers.
The context variable Trigger.operationType is used to get the current System.TriggerOperation enum value.
The following are the values of the System.TriggerOperation enum:
AFTER_DELETE
AFTER_INSERT
AFTER_UNDELETE
AFTER_UPDATE
BEFORE_DELETE
BEFORE_INSERT
BEFORE_UPDATE
Example:
trigger TestSwitch on Account (before insert, after insert) {
switch on Trigger.operationType {
when BEFORE_INSERT {
System.debug('Before Insert');
}
when AFTER_INSERT {
System.debug('After Insert');
}
when else {
System.debug('Something went wrong');
}
}
}
trigger TriggerName on ObjectName (trigger_events) {
//code_block
}
trigger AccountTrigger on Account (before insert) {
System.debug('Trigger before insert event');
}
trigger AccountTrigger on Account (before insert) {
if(Trigger.isInsert){
if(Trigger.isBefore){
// Trigger.new is a sObject List
for(Account acc: Trigger.new){
acc.description = 'Account has been created';
}
}
}
}
// For the best practice we need to write business logic in Apex class as Trigger Handler separately.
// Trigger
trigger AccountTrigger on Account (before insert) {
if(Trigger.isInsert){
if(Trigger.isBefore){
AccountTriggerHandler.updateDescription(Trigger.new);
}
}
}
// Trigger Handler (Apex Class)
public class AccountTriggerHandler {
public static void updateDescription(List<Account> accList){
for(Account acc: accList){
acc.Description = 'Account has been created. !!Trigger!!';
}
}
}
/*
Question :
Before Insert
- If Account Industry is not null and having value as 'Media' then populate Rating as Hot.
After Insert
- Create related Opportunity when Account is created.
*/
// File Name : AccountTrigger.apxt
trigger AccountTrigger on Account (before insert, after insert) {
if(Trigger.isInsert){
if(Trigger.isBefore){
AccountTriggerHandler.populateRating(Trigger.new);
} else if(Trigger.isAfter){
// Trigger.new will be read only in After insert.
AccountTriggerHandler.createOpportunity(Trigger.new);
}
}
}
// File Name : AccountTriggerHandler.apxc
public class AccountTriggerHandler {
public static void updateDescription(List<Account> accList){
for(Account acc: accList){
acc.Description = 'Account has been created. !!Trigger!!';
}
}
public static void populateRating(List<Account> accList){
for(Account acc: accList){
if(acc.Industry == 'Media'){
acc.Rating = 'Hot';
}
}
}
public static void createOpportunity(List<Account> accList){
List <Opportunity> oppList = new List<Opportunity>();
for(Account acc: accList){
if(acc.Active__c == 'Yes'){
Opportunity opp = new Opportunity();
opp.AccountId = acc.Id;
opp.Name = acc.Name + ' Opportunity';
opp.CloseDate = System.today();
opp.StageName = 'Qualification';
opp.Description = 'Opportunity has been created Through !!Account Trigger!!';
oppList.add(opp);
}
}
if(!oppList.isEmpty()){
insert oppList;
}
}
}
/*
Question :
Before Update
- If account phone is updated then put update message in description.
*/
// File Name : AccountTrigger.apxt
trigger AccountTrigger on Account (before update) {
if(Trigger.isUpdate){
if(Trigger.isBefore){
AccountTriggerHandler.updatePhoneDesc(Trigger.new, Trigger.oldMap);
}
}
}
// File Name : AccountTriggerHandler.apxc
public class AccountTriggerHandler {
public static void updatePhoneDesc(List<Account> accList, Map<ID, Account> accOldMap){
//execute a loop on update account list
for(Account acc: accList){
if(acc.Phone != accOldMap.get(acc.Id).Phone){
acc.Description = 'Phone number has been change. Old Value is : '+ accOldMap.get(acc.Id).Phone + ' New Value is : ' + acc.Phone;
}
}
}
}
/*
Question:
Before Insert & Update
- Upon Account Creation or Updation if Account Industry is not null and having value as 'Media' then populate Rating as Hot.
*/
// File Name : AccountTrigger.apxt
trigger AccountTrigger on Account (before insert, before update) {
if(Trigger.isInsert){
if(Trigger.isBefore){
// we'll pass null as oldMap here because for insert field we don't have oldMap value.
AccountTriggerHandler.populateRating(Trigger.new, null);
}
}
if(Trigger.isUpdate){
if(Trigger.isBefore){
AccountTriggerHandler.populateRating(Trigger.new, Trigger.oldMap);
}
}
}
// File Name : AccountTriggerHandler.apxc
public class AccountTriggerHandler {
public static void populateRating(List<Account> accList, Map<Id, Account> accOldMap){
for(Account acc: accList){
// here i am checking if this is insert operation with accOldMap == null
// and further check for Industry value should not be null and it should be Media then execute
if((accOldMap == null && acc.Industry != null && acc.Industry == 'Media')){
acc.Rating = 'Hot';
acc.Description = 'Rating updated with Hot on Insert !!AccountTrigger!!';
}
// here i am checking if user touched or changed Industry field and select Media
// with code `accOldMap.get(acc.Id).Industry` we we get old value of Industry field.
else if(accOldMap.get(acc.Id).Industry != acc.Industry && acc.Industry == 'Media'){
acc.Rating = 'Hot';
acc.Description = acc.Description || '' + ' \n Rating updated with Hot on Update !!AccountTrigger!!';
}
}
}
}
/*
Question:
Before Delete
- Account record cannot be deleted if active is Yes.
- Use Custom Label to show Error Message.
- Apply a check that only System Administrator profile user can delete an account.
*/
// File Name : AccountTrigger.apxt
trigger AccountTrigger on Account (before delete) {
if(Trigger.isBefore){
if(Trigger.isDelete){
AccountTriggerHandler.preventDelete(Trigger.old);
}
}
}
// File Name : AccountTriggerHandler.apxc
public class AccountTriggerHandler {
public static void preventDelete(List<Account> accList){
profile p = [SELECT Id FROM Profile WHERE Name = 'System Administrator'];
for(Account acc: accList){
// here userInfo.getProfileId() we will get logged-in user Id
if(userInfo.getProfileId() != p.Id || acc.Active__c == 'Yes'){
acc.addError(Label.Prevent_Account_Deletion );
}
}
}
}
/*
Question:
After Delete
- When a contact record is deleted then update 'Total Contact Count' field on related Account.
After Undelete
- When a contact record is undeleted then update 'Total Contact Count' field on related Account.
*/
// File Name : ContactTrigger.apxt
trigger ContactTrigger on Contact (after insert, after delete, after undelete) {
if(Trigger.isAfter){
if(Trigger.isInsert){
ContactTriggerHandler.updateContactCount(Trigger.new);
}
if(Trigger.isDelete){
ContactTriggerHandler.updateContactCount(Trigger.old);
}
if(Trigger.isUndelete){
ContactTriggerHandler.updateContactCount(Trigger.new);
}
}
}
// File Name :
public class ContactTriggerHandler {
public static void updateContactCount(List<Contact> conList){
List<Account> accList = new List<Account>();
Set<Id> accIdSet = new Set<Id>();
for(Contact con: conList){
if(con.AccountId != null){
accIdSet.add(con.AccountId);
}
}
for(Account acc: [SELECT Id, (SELECT Id FROM Contacts) FROM Account WHERE Id IN: accIdSet]){
acc.Contact_Count__c = acc.Contacts.size();
accList.add(acc);
}
if(!accList.isEmpty()){
update accList;
}
}
}
Question : Write a trigger to check if account email id already exist when user want to insert any entry into account object.
trigger CheckDuplicateEmail on Account (before insert) {
// Set to store all email IDs of the new records being inserted
Set<String> newEmailIds = new Set<String>();
// Loop through the records being inserted to collect email IDs
for (Account acc : Trigger.new) {
if (acc.Email__c != null) {
newEmailIds.add(acc.Email__c);
}
}
// Query for existing accounts with the same email IDs
List<Account> existingAccounts = [SELECT Id, Email__c FROM Account WHERE Email__c IN :newEmailIds];
// Set to store the existing email IDs
Set<String> existingEmailIds = new Set<String>();
for (Account acc : existingAccounts) {
existingEmailIds.add(acc.Email__c);
}
// Check if any new records have an email ID that already exists
for (Account acc : Trigger.new) {
if (acc.Email__c != null && existingEmailIds.contains(acc.Email__c)) {
acc.addError('An account with this email ID already exists: ' + acc.Email__c);
}
}
}
// Question : when contact is inserted or deleted then you want the count of contacts in account object.
trigger contactTrigger on Contact (after insert, after delete, after undelete){
if(Trigger.isAfter){
if(Trigger.isInsert){
ContactTriggerHandler.updateAccountCount(Trigger.new);
}
if(Trigger.isDelete){
ContactTriggerHandler.updateAccountCount(Trigger.old);
}
if(Trigger.isUndelete){
ContactTriggerHandler.updateAccountCount(Trigger.new);
}
}
}
public class ContactTriggerHandler{
public static void updateAccountCount(List<Contact> conList){
Set<Id> accountIds = new Set<Id>();
List<Account> updateAccList = new List<Account>();
for(Contact con : conList){
if(con.AccountId != null){
accountIds.add(con.AccountId);
}
}
List<Account> accList = [SELECT Id, (SELECT Id FROM Contacts) FROM Account WHERE Id IN: accountIds];
for(Account acc: accList){
acc.contact_count__c = acc.Contacts.size();
updateAccList.add(acc);
}
if(updateAccList.size() > 0) {
update updateAccList;
}
}
}
// Question : Trigger to count number of sibling in contacts associated with an account
trigger ContactTrigger on Contact (after insert) {
if(Trigger.isAfter){
if(Trigger.isInsert){
ContactTriggerHandler.updateContactSibling(Trigger.new);
}
}
}
public class ContactTriggerHandler{
public static void updateContactSibling(List<Contact> conList){
// To get list of all accounts (companies) involved in this process
Set<Id> AccountIds = new Set<Id>();
for (contact c : conList) {
if(c.AccountId != null) {
AccountIds.add(c.AccountId);
}
}
// To maintain which company has how many employees
List<Account> accList = [SELECT id, name, (SELECT id FROM Contacts) FROM Account WHERE id IN :AccountIds];
Map<Id, integer> AccountContactMap = new Map<Id, integer>();
for(Account acc: accList) {
integer contactCount = acc.contacts.size();
AccountContactMap.put(acc.id, contactCount);
}
// To call all employees one by one and setting the sibling count on that
list<Contact> GetContacts = [SELECT id, SiblingCount_C, AccountId FROM contact WHERE AccountId in : AccountContactMap.keySet()];
list<Contact> TpUpdate = new list<Contact>();
for (contact c: GetContacts ){
c.SiblingCount__c = AccountContactMap.get.(c.AccountId) > 0 ? AccountContactMap.get.(c.AccountId) - 1 : 0;
TpUpdate.add(c);
}
update (TpUpdate);
}
}
>> In the above scenario we can not handle after update trigger because in trigger handler we are updating contacts itself, so if we'll handle after update trigger then it'll go to in a infinite loop (recusion situation will come)
// Question : Write a trigger to prevert update status of Account object, if user will update satus of account object to Closed then first we need to check if all opportunity is closed or not, if all opportunity has been closed then only user can change status of account to closed else throw an error.
trigger AccountTrigger on Account(before update){
if(Trigger.isBefore()){
if(Trigger.isUpdate()){
AccountTriggerHandler.updateStatus(Trigger.new, Trigger.oldMap)
}
}
}
public class AccountTriggerHandler{
public static void updateStatus(List<Account> accList, Map<id, Account> oldAccountMap){
Set<Id> accIds = new Set<Id>();
List<Opportunity> oppList = new List<Opportunity>();
for(Account acc: accList){
if(acc.status__c == 'Closed' && oldAccountMap.get(acc.Id).status__c != 'Closed'){
accIds.add(acc.Id);
}
}
if(accIds.size() > 0){
oppList = [SELECT Id, Status FROM Opportunity WHERE AccountId IN :accIds AND status__c != 'Closed'];
Map<Id, Boolean> accOpenOpp = new Map<Id, Boolean>();
for(Opportunity opp : oppList){
accOpenOpp.put(opp.AccountId, true);
}
for(Account acc: accList){
if(accOpenOpp.containsKey(acc.Id)){
acc.addError('You can not close account because all opportunity should be closed first.');
}
}
}
}
}
// Question : Write a trigger to update contact when accounts phone changed.
trigger UpdateContactOnAccountPhoneChange on Account (after update) {
// Set to hold account IDs with changed phone numbers
Set<Id> changedAccountIds = new Set<Id>();
// Map to hold account IDs and their new phone numbers
Map<Id, String> accountIdToPhoneMap = new Map<Id, String>();
// Iterate through the trigger's new list of updated accounts
for (Account updatedAccount : Trigger.new) {
// Check if the phone number has been changed
if (Trigger.oldMap.get(updatedAccount.Id).Phone != updatedAccount.Phone) {
// Add account ID to the set of changed IDs
changedAccountIds.add(updatedAccount.Id);
// Add account ID and new phone number to the map
accountIdToPhoneMap.put(updatedAccount.Id, updatedAccount.Phone);
}
}
// Query for related contacts in bulk
List<Contact> relatedContacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :changedAccountIds];
// Iterate through related contacts and update phone numbers
for (Contact relatedContact : relatedContacts) {
relatedContact.Phone = accountIdToPhoneMap.get(relatedContact.AccountId);
}
// Update contacts
if (!relatedContacts.isEmpty()) {
update relatedContacts;
}
}
Ans : When trigger goes to recursive loop, that means trigger called itself back to back. we can solve this by static boolean variable (see resolve recursion)
// Question : we have a picklist of contact type in contact object where we will store type of contact (Platinum, Gold, Silver) and we have one field in account object that is Premium_contact_status. so ask is if any premium contact will update then status value (premium_contact_status__c) should be update.
trigger ContactMasterTrigger on Contact (after update) {
Set<Id> setAccountIds = new Set<Id>();
List<Account> accountListToBeUpdate = new List<Account>();
for(Contact con: Trigger.new) {
if(con.AccountId != null && con.contact_type__c == 'Platinum') {
setAccountIds.add(con.AccountId);
}
}
if(setAccountIds.size() > 0){
List<Account> accList = [SELECT Id, premium_contact_status__c FROM Account WHERE id IN : setAccountIds];
for(Account acc : accList){
acc.premium_contact_status__c = 'Platinum contact modified!';
accountListToBeUpdate.add(acc);
}
}
if(accountListToBeUpdate.size() > 0){
update accountListToBeUpdate;
}
}
// Question: prevent deletion of account if its have more than 2 contacts
trigger AccountTrigger on Account(before delete){
if(Trigger.isBefore){
if(Trigger.isDelete){
AccountTriggerHandler.preventDelete(Trigger.Old)
}
}
}
public class AccountTriggerHandler{
public static void preventDelete(List<Account> accList){
Set<Id> accountIdsSet = new Set<Id>();
for(Account acc: accList){
accountIdsSet.add(acc.Id);
}
List<Account> accWitCon = [SELECT Id, (SELECT Id FROM Contacts) FROM Account WHERE Id IN :accountIdsSet HAVING Count(Contacts) > 2];
Set<Id> accountWithConIdsSet = new Set<Id>();
for(Account acc: accWitCon){
accountWithConIdsSet.add(acc.Id);
}
for(Account acc: accList){
if(accountWithConIdsSet.contains(acc.Id)){
acc.addError('You can not delete this account, because it has more than 2 contants')
}
}
}
}
Recursion is the process of executing the same Trigger multiple times to update the record again and again due to automation. There may be chances we might hit the Salesforce Governor Limit due to Recursive Trigger.
You can create a class with a static Boolean variable with a default value of true. In the trigger, before executing your code keep a check that the variable is true or not. Once you check make the variable false.
Apex Class with Static Variable
public class DealTriggerHandler{
public static Boolean isFirstTime = true;
}
Public = so i can use this variable outside static = so that data will not vanish when control is out of class.
Trigger Code
Trigger ContactTriggers on Contact (after update){
Set<String> accIdSet = new Set<String>();
if(DealTriggerHandler.isFirstTime){
DealTriggerHandler.isFirstTime = false;
System.debug('---- Trigger run ---->'+Trigger.New.size() );
for(Contact conObj : Trigger.New){
if(conObj.name != 'Test') {
accIdSet.add(conObj.accountId);
}
}
// any code here
}
}
This is good for less than 200 records. But what about if we have a large set of records it won’t work. It will update the first 200+ records then it will skip the others. Let’s execute the above code with more than 200+ records.
So It is better to use a Static set or map to store all executed record ids. So when next time it will execute again we can check record is already executed or not. Let modify the above code with the help of Set.
public class ContactTriggerHandler{
public static Set<Id> setExecutedRecord = new Set<Id>();
}
Let us the same set variable in Trigger.
trigger ContactTriggers on Contact (after update){
System.debug('---- Trigger run ---->'+Trigger.New.size() );
for(Contact conObj : Trigger.New){
if(ContactTriggerHandler.setExecutedRecord.contains(conObj.id)){
ContactTriggerHandler.setExecutedRecord.add(conObj.id);
if(conObj.name != 'Test') {
// logic
}
}
}
// any code here
}
- Standard validation rule
- Before trigger
- Custom validation rule
- After trigger
- WFR (Work Flow Rule)
- Process builder *
- If WER/PB has any action called as "field update" and this changes any record, and on that record's object we have trigger, then one more time the before and after trigger code will work. BUT ONLY ONE TIME !!!
- Data will be committed in database
Requirement: If Age < 18 = throw error as per Validation Rule.
Assume, user created a record with exp 20, so VR did not come, then trigger came, then WFR came, and at that time by "WFR field update", value of Age became 15, what will happen? Error will come or not?
Answer : Error will not come because after WFR trigger will render.
Counter IQ:
Requirement : If Age < 18 = throw error as per "Trigger", using function addError.
Assume, user created a record with exp 20, then WFR came, and at that time by "WFR field update", value of Age became 15, what will happen? Error will come or not?
Visualforce page:
<apex:page controller="SampleClass" action="{!FunctionA}">
<apex:outputText>{!VariableABC}</apex:outputText>
</арех:page>
public with sharing class SampleClass {
public String VariableABC {
get {
integer a = btc;
return a;
}
set;
}
Or
//VariableABC {set;get;}
public SampleClass (ApexPages. StandardController StdController1) {
// any code
}
public void FunctionA() {
// any code
}
}
//any code
Order of execution in visualforce page is 1: constructor function 2: action function 3: getter function
- The Apex testing framework enables you to write and execute tests for your Apex classes and triggers on the Lightning Platform.
- Apex unit tests ensure high quality for your Apex code and let you meet requirements for deploying Apex.
- Apex code can only be written in a sandbox environment (copy of production) or a Developer org, not in production.
- Apex code can be deployed to a production org from a sandbox. Also, app developers can distribute Apex code to customers from their Developer orgs by uploading packages to the Lightning Platform AppExchange (AppExchange is the leading enterprise cloud marketplace).
- In addition to being critical for quality assurance, Apex unit tests are also requirements for deploying and distributing Apex.
- Ensuring that your Apex classes and triggers work as expected.
- Having a suite of regression tests that can be rerun every time classes and triggers are updated to ensure that future updates you make to your app don't break existing functionality.
- Meeting the code coverage requirements for deploying Apex to production or distributing Apex to customers via packages.
- High-quality apps delivered to the production org, which makes production users more productive.
- High-quality apps delivered to package subscribers, which increase your customers trust.
- Test class must start with @isTest annotation.
- Focus 90+ : To deploy to production at least 75% code coverage is required. But always try to focus on 90%+. We should not focus on the percentage of code coverage, We should make sure that every use case should covered including positive, negative,bulk and single record.
- One Assert Statement per method : Always put assert statements for negative and positive tests.
- System.assert(condition, msg)
- System.assertEquals(expected, actual, msg)
- System.assertNotEquals(expected, actual, msg)
- @testSetup to create test records once in a method and use in every test method in the test class.
- Create TestFactory class with @isTest annotation to exclude from organization code size limit.
- No SeeAllData=true : Always use ( seeAllData = false ) at class or method level. User, profile, organization, AsyncApexjob, Corntrigger, RecordType, ApexClass, ApexComponent, ApexPage we can access without (seeAllData=true) .
- As apex runs in system mode so the permission and record sharing are not taken into account . So we need to use System.runAs to enforce record sharing.
- Avoid Using Hard Coding Ids anywhere in test Class or any apex class
- Governor limits : Using Test.startTest()gives developers a new set of governor limits for the act stage of their test. Test.stopTest() then allows them to return to their previous governor limits. Test classes must test for Governor Limits using Limits Class
- Testing Exception : Any exceptions that are caught in the production methods should be tested by feeding the test data that throws exception. Exception Type and error message should be asserted
- Exercise bulk trigger functionality – use at least 200 records in your tests.
- Before you can deploy your code or package it for the Lightning Platform AppExchange, at least 75% of Apex code must be covered by tests, and all those tests must pass.
- In addition, each trigger must have some coverage. Even though code coverage is a requirement for deployment, don't write tests only to meet this requirement.
- Make sure to test the common use cases in your app, including positive and negative test cases, and bulk and single-record processing.
- Calls to System.debug are not counted as part of Apex code coverage.
- Test methods and test classes are not counted as part of Apex code limit (The default Apex code limit in Salesforce is 6 MB of code per organization, excluding comments and @isTest classes). So, no worries about writing long test class with more methods just to make sure that all your code branches are covered.
- Class can be deployed on 0% coverage as well, but that overall coverage of your production org after getting your code deployed should be 75%, otherwise Salesforce won't let you deploy your code.
@isTest
static void testName() {
// code_block
}
OR
static testMethod void testName() {
// code_block
}
@isTest
private class MyTestClass {
@isTest
static void myTestMethod() {
// code_block
}
}
FileName : TemperatureConverter.apxc
public class TemperatureConverter {
// Takes a Fahrenheit temperature and returns the Celsius equivalent.
public static Decimal FahrenheitToCelsius(Decimal fh) {
Decimal cs = (fh - 32) * 5/9;
return cs.setScale(2);
}
}
FileName : TemperatureConverterTest.apxc
@isTest
private class TemperatureConverterTest {
@isTest
static void testWarmTemp() {
Decimal celsius = TemperatureConverter.FahrenheitToCelsius(70);
System.assertEquals(21.11, celsius);
}
@isTest
static void testFreezingPoint() {
Decimal celsius = TemperatureConverter FahrenheitToCelsius(32) ;
System. assertEquals (0,celsius);
}
@isTest
static void testBoilingPoint() {
Decimal celsius = TemperatureConverter. FahrenheitToCelsius(212) ;
System. assertEquals (10,celsius, 'Boiling point temperature is not expected.');
}
@isTest
static void testNegativeTemp() {
Decimal celsius = TemperatureConverter. FahrenheitToCelsius(-10) ;
System. assertEquals (-23.33,celsius);
}
}
Example for Trigger : after insert in account Object create opportunity (above trigger example : AccountTriggerHandler.apxc -> CreateOpportunity())
@isTest
private class AccountTriggerHandlerTest {
@isTest
static void createOppTest (){
List<Account> accList = new List<Account>();
for(Integer i=1; i<=10; i++){
Account acc = new Account ();
acc.Name = 'Test ' + i;
acc.Active__c = 'Yes';
accList.add (acc) ;
}
Test.startTest();
insert accList;
Test. stopTest() ;
List<Opportunity> oppList = [Select Id FROM Opportunity WHERE AccountId =: accList[0].Id];
System.assert(oppList != null, 'Opportunity is not created');
}
@isTest
static void populateRatingTest() {
List<Account> accList = new List<Account>();
for(Integer i=1; i<=10; i++){
Account acc = new Account ();
acc.Name = 'Test ' + i;
acc.Active__c = 'Yes';
acc.Industry = 'Media';
accList.add (acc) ;
}
Test.startTest();
insert accList;
Test. stopTest() ;
List<Account> updatedAccountInfo = [Select Rating FROM Account WHERE Id =: accList[0].Id];
System.assertEquals('Hot', updatedAccountInfo[0].Rating);
}
}
- DML is used to insert, update, delete & undelete records.
- Use upsert to either insert or update a record.
- Always perform DML in Bulk.
- Handle Exceptions.
Insert Records
- Insert only one record.
- Fetch Id just after insertion of record.
- Insert records in bulk using List.
- Governor limits check :
- Inserting two records separately
- Insert two records through a List.
- Insert related records. First insert Account then Contact.
Note: Check newly created records in Org.
public class DMLDemo {
public static void insertDemo(){
List<Account> accList = new List<Account>();
for(integer i = 1; i <= 5; i++){
Account acc = new Account();
acc.Name = 'Test DML 2000' + i;
accList.add(acc);
}
insert accList;
system.debug('Account Id : '+ accList);
}
}
Update Records
- Query existing records and display their values.
- Then update the record and do Update DML.
- Check updated records in Org.
- Helpful in processing in large records
- When we want to see which record were successful
- When we want to see which records failed
- What are IDs of success records
- What are error reasons
- What are error fields
- This will help for partial DML
- We can process large records by this
- Documentation can be done using debig logs to know the reasons of errors
- Everything is dynamic, whether records are few or large
DML statement with database function:
Suppose, we have some records (say, 20), in a list. If suppose, I did insert for that list, and 1 record is having bad data so error took place. Yet, all rest 19 records will be cancelled. And nothing will be inserted.
This is the regular pattern of DML.
When we make any transaction using database.insert() or database.update() then the return value is database.saveresult
Account acc1 = new Account();
acc1.Name = 'Account 22001';
Account acc2 = new Account();
acc2.Name = 'Account 22001';
Account acc3 = new Account();
acc3.Industry = 'Chemical';
list<Account> accList = new list<Account>();
accList.add(acc1);
accList.add(acc2);
accList.add(acc3);
List<database.saveresult> resultList = database.insert(accList, false);
system.debug(resultList);
for(database.saveresult result : resultList){
system.debug('--------------------------');
system.debug(result.isSuccess());
system.debug(result.getErrors());
system.debug(result.getID());
system.debug('--------------------------');
}
database.saveresult database contains 3 methods
- getErrors()
- getId()
- isSuccess()
https://www.apexhours.com/soql-cheat-sheet/
Earlier Query all fields functionality was not available in Salesforce. Now we can get records of all fields available in Salesforce using the new FIELDS() function of Salesforce Object Query Language (SOQL).
1.FIELDS(ALL) – All Fields including custom and standard fields. This is like * in SQL.
2.FIELDS(Custom) – This will fetch only custom fields.
3.FIELDS(Standard) – This will fetch only standard fields.
Example Query: SELECT FIELDS(ALL) FROM ACCOUNT LIMIT 200
Note: The SOQL FIELDS() function must have a LIMIT of 200. We can also use the above query in workbench but we can not export data in bulk CSV.
Salesforce Object Search Language (SOSL) is a Salesforce search language that is used to perform text searches in records. Use SOSL to search fields across multiple standard and custom object records in Salesforce. SOSL is a search language in salesforce and the important feature is that Unlike SOQL, we can search in multiple objects at same time using SOSL. In SOQL, we can query only one object at a time but in SOSL, We can search for some specified string like ‘testString’ in multiple objects at the same time.
Like SOQL, SOSL allows you to search your organization’s records for specific information. Unlike SOQL, which can only query one standard or custom object at a time, a single SOSL query can search all objects.
Another difference is that SOSL matches fields based on a word match while SOQL performs an exact match by default (when not using wildcards). For example, searching for ‘Digital’ in SOSL returns records whose field values are ‘Digital’ or ‘The Digital Company’, but SOQL returns only records with field values of ‘Digital’.
// Search 'Salesforce' keyword in Account object under all fields
List<List<SObject>> accList = [FIND 'Salesforce' IN ALL FIELDS RETURNING Account];
// Search 'Salesforce' keyword in Account object under all fields and get it's ID, Name, Phone field info
List<List<SObject>> accList = [FIND 'Salesforce' IN ALL FIELDS RETURNING Account(Id, Name, Phone)];
// Search 'Salesforce' keyword in Account object under all fields and get it's ID, Name, Phone field info and we need to add where condition for account object
List<List<SObject>> accList = [FIND 'Salesforce' IN ALL FIELDS RETURNING Account(Id, Name, Phone WHERE Name = 'Manchester Utd')];
// if we want to search from two objects
List<List<SObject>> accList = [FIND 'Salesforce' IN ALL FIELDS RETURNING Account(Id, Name, Phone), Contact(Id, Name)];
// if you want you search multiple text in single query then we need to pass both keyword in single quote by OR seperator
List<List<SObject>> accList = [FIND '"Salesforce" OR "SF"' IN ALL FIELDS RETURNING Account(Id, Name, Phone), Contact(Id, Name)];
// wildcards:
FIND {Salesforce N*}
FIND {Salesforce N?ob}
List<List<SObject>> accList = [FIND 'Sa*' IN ALL FIELDS RETURNING Account(Id, Name, Phone), Contact(Id, Name)];
- Asynchronous Apex is used to run processes in a separate thread, at a later time. An asynchronous process is a process or function that executes a task "in the background" without the user having to wait for the task to finish.
- Synchronous Apex means entire Apex code is executed in one single go. Asynchronous term means not existing or occurring at the same time.
- We use asynchronous apex mostly when callouts to external systems is required, code needs to be run at some particular time, higher Governor Limits are required for some operation.
- An asynchronous process executes a task in the background.
- User doesn't have to wait for the task to finish.
- Use Asynchronous Apex for:
- Callouts to external systems
- Operations that require higher limits
- Code that needs to run at a certain time.
Benefits of Asynchronous Processing
- User Efficiency
- Scalability
- Higher Governor Limits
- Can process more number of records
Types of Asynchronous Processing
Туре | Overview | Common Scenarios |
---|---|---|
Future Methods | Run in their own thread, and do not start until resources are available
|
Web service callout. |
Batch Apex | Run large jobs that would exceed normal processing limits
|
Data cleansing or archiving of records |
Queueable Apex | Similar to future methods, but provide additional job chaining and allow more complex data types to be used.
|
Performing sequential processing operations with external Web Services. |
Scheduled Apex | Schedule Apex to run at a specified time. | Daily or weekly tasks. |
Governor & Execution Limits
- Asynchronous apex provides higher governor and execution limits.
- Number of SOQL is doubled from 100 to 200.
- Total heap size and maximum CPU time are similarly larger for asynchronous calls.
- As you get higher limits with async, also those governor limits are independent of the limits in the synchronous request that queued the async request initially.
How Async Processing Works?
- Enqueue
- Persistence
- Dequeue
- Future Apex runs process in a separate thread, at a later time when system resources become available.
- Use @future annotation to create future methods.
- In Synchronous processing, all method calls are made from the same thread and no additional processing can occur until the process is complete.
- Whereas in future method, methods runs asynchronously in its own thread.
- This unblocks users from performing other operations.
- Provides higher governor & execution limits for processing.
- future method never returns anything so we have to use static void keyword when declaring future method.
- You can perform mixed DML use future method.
Mixed DML There are two types of objects in Salesforce.
-
Set Up Objects: Setup objects are the sObjects that affect the user's access to records in the organization. Eg - User, Profile, Permission Set etc.
-
Non Setup Objects : All (Standard and Custom Objects).
- A mixed DML Operation Error comes when you try to perform DML operations on setup and non-setup objects in a single transaction.
- This restriction exists because some sObjects affect the user's record access in the org. You must insert or update these types of sObjects in a different transaction to prevent operations from happening with incorrect access-level permissions.
- For example, you can't update an account and a user role in a single transaction.
Disadvantages of Future Method :
- Chaining is not possible so you cannot call a future from another future or batch apex like it is not guaranteed that method will execute in same order as they called.If you want that go for Queueable.
- In a single request. You can make only 50 future calls.
- Cannot Monitor: Method always be used static & void so you cannot return job ID then how can you monitor.
- You cannot call future method using batch but you can call batch using future method.
Syntax
global class FutureClass
{
@future
public static void myFutureMethod()
{
// Perform some operations
}
}
Example
public class CreateAccountAndUser {
public static void createData(){
Account ac = new Account ();
ac.name = 'Salesforce Shiksha';
insert ac;
}
@future
public static void createuser (){
List‹Profile> upro = [Select id,name from Profile where name = 'Standard User'];
List<UserRole> uRole = [Select id, name from UserRole where name = 'CEO'];
User u = new User();
u.FirstName = 'Salesforce';
u.LastName = 'Shiksha';
u.ProfileId = upro[0].id;
u.UserRoleId = uRole[0].id;
u.Alias = 'sshik';
u.Email = '[email protected]';
u.Username = '[email protected]';
u.emailencodingkey= 'UTF-8';
u.languagelocalekey= 'en_US';
u.localesidkey='en_US';
u.timezonesidkey='America/Los_Angeles';
insert u;
}
}
Things to Remember
- It can happen that future methods are running in different order as they are called.
- You cannot call a future method from another.
- There is a limit of 50 future calls per Apex invocation. There is an additional limit on the number of calls in a 24-hour period.
Connect salesforce org with external system like SAP by using future method
trigger SyncAccountToSAP on Account (after insert) {
// Define the endpoint URL of SAP's web service
String sapEndpoint = 'https://sap.example.com/api/accounts';
List<Account> accountsToSend = new List<Account>();
for (Account acc : Trigger.new) {
// Create a JSON payload to send to SAP
String payload = '{' +
'"accountId": "' + acc.Id + '",' +
'"accountName": "' + acc.Name + '",' +
// Add other fields you want to sync
'}';
// Send the data to SAP using outbound messaging (HTTP request)
HttpRequest request = new HttpRequest();
request.setEndpoint(sapEndpoint);
request.setMethod('POST');
request.setHeader('Content-Type', 'application/json');
request.setBody(payload);
// Execute the HTTP request
HttpResponse response = new Http().send(request);
// Check the response (optional: handle errors or log responses)
if (response.getStatusCode() != 200) {
// Handle error scenario if needed
System.debug('Error syncing account to SAP: ' + response.getBody());
} else {
// Add logic if needed for successful sync
System.debug('Account synced to SAP successfully: ' + response.getBody());
}
}
}
call third party like sap with future method
public class SyncAccountWithSAP {
@future(callout=true)
public static void syncAccount(String accountId) {
// Retrieve the Account record
Account acc = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry
FROM Account WHERE Id = :accountId];
// Prepare the data to be sent to SAP
String sapEndpoint = 'https://your-sap-endpoint-url.com'; // Replace with your SAP endpoint URL
HttpRequest req = new HttpRequest();
req.setEndpoint(sapEndpoint);
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
// Create a JSON payload to send to SAP
Map<String, Object> sapPayload = new Map<String, Object>();
sapPayload.put('accountId', acc.Id);
sapPayload.put('accountName', acc.Name);
sapPayload.put('billingStreet', acc.BillingStreet);
sapPayload.put('billingCity', acc.BillingCity);
sapPayload.put('billingState', acc.BillingState);
sapPayload.put('billingPostalCode', acc.BillingPostalCode);
sapPayload.put('billingCountry', acc.BillingCountry);
String jsonBody = JSON.serialize(sapPayload);
req.setBody(jsonBody);
// Send the HTTP request to SAP
Http http = new Http();
HttpResponse res = http.send(req);
// Handle response from SAP if needed
if (res.getStatusCode() != 200) {
System.debug('Error syncing Account with SAP: ' + res.getBody());
// Handle error scenario, log or notify admins
} else {
System.debug('Account synced successfully with SAP');
// Optionally, handle success scenario
}
}
}
- When you are dealing with big data(millions of records) that can exceed normal processing limits then you use Batch there.
- Batch is most advanced in Asynchronous Apex.
- In Batch Apex, you can process records asynchronously in batches.
- Default batch size is 200.
- Maximum Batch size is 2000.
- Implements Database.Batchable interface.
- If you are using Database.QueryLocator it can retrieves 50 million records from Database while Database. Query retrieves 50 k records.
Database.Batchable interface gives three methods -
1. start (one time run) - This method collects all the records or objects to be passed to execute method for further processing.
2. execute (running depends on number of jobs) -
- This method executes the functionality or run the process.
- execute method takes Database.BatchableContext(collects the records or object).
- A list of object (List‹sobject>).
3. finish(one time run) - Used to execute post processing operations. (eg : sending an email).
Advantages/Features of Batch Apex:
- In finish method you can invoke another batch.
- When 1 record in a batch fails, that whole batch will be rolled back, but other batches will not be affected.
- Batch apex can make callouts to external systems by using Database.AllowsCallouts interface.
- You can track the state of batch execution by using Database.stateful interface.
- You can schedule batch apex class by implementing Schedulable interface.
Disadvantages of Batch Apex:
-
You cannot call future method from Batch class.
-
Only one batch Apex job's start method can run at a time in an org.
-
For Reference : https://trailhead.salesforce.com/trailblazer-community/feed/0D54V00007T4IGZSA3 (You cannot call future method from Batch class)
Syntax
public class ContactBatch implements Database.Batchable<sobject> {
public Database.QueryLocator start(Database.BatchableContext BC){
return Database.getQueryLocator(query);
}
public void execute(Database.BatchableContext BC, List<sobject> scope){
for (sobject s : scopel{
s.put(Field,Value);
}
update scope;
}
public void finish(Database.Batchablecontext BC){
system.debug('operation performed successfully.')
}
}
Example
public class ContactBatch implements Database.Batchable<sobject> {
public Database.QueryLocator start(Database.BatchableContext BC){
return Database.getQueryLocator('SELECT Id FROM Contact');
}
public void execute(Database.BatchableContext BC, List<Contact> contactList){
for(Contact con: contactList){
con.Status__c = 'Active';
}
update contactList;
}
public void finish(Database.Batchablecontext BC){
system.debug('operation performed successfully.');
}
}
Execute Window
ContactBatch con = new ContactBatch();
Database.executeBatch(con, 10);
Q. : How to check failed records in batchable apex?
A.
first of all you need to make your batch class stateful using Database.Stateful so replace your first line with
public class SimpleBatch implements Database.Batchable<sObject>,Database.Stateful{
A global variable required which will maintain failed record.
global List<String> exception_List;
Use Database.update method instead of update with allOrNothing = false parameter which will return how many records has passed and failed in update call.
Database.SaveResult[] SaveResultList = Database.update(objsToUpdate,false);
You will iterate saveResultList and you will add failed records in exception_list
for(integer i = 0; i < objsToUpdate.size(); i++) {
String msg='';
If(!SaveResultList[i].isSuccess()) {
msg += userList.get(i).id + '\n'+'Error: "';
for(Database.Error err: SaveResultList[i].getErrors()){
msg += err.getmessage()+'"\n\n';
}
}
if(msg!='')
exception_List.add(msg);
}
You can use this exception_list in execute method to send in your final email.
Ans : Yes
How ?
In finish method
Ans. Yes, but not advised because there is a limit on calling batch classes per Salesforce org. If Trigger has large records, it will call many batch classes, then limit can hit.
Upto 5 active batch jobs are allowed in APEX (in queue).
Ans. Global
Ans. Database.Batchable
Ans. The way we have data type like integer, string; same way we have "Database.QueryLocator" which contains large number of records, like a container/pot.
And this is return type of start() function.
Ans. No!
We will use "database.getQueryLocator()" function.
This IS first line Of Batch class | normal process:
global class AccountlaxUpdate implements Database.Batchable<sObject> {
Make it like this:
global class AccountTaxUpdate implements Database.Batchable<sObject>, Database.Stateful{
Ans. Iterator (we dont use this in max cases) (just memorize for interview)
Ans. 5 (that's why we should not call any batch apex from Trigger)
- It is also a type of asynchronous apex.
- It is similar to future method but provides additional functionality which future method was unable to provide -
a) We can use non-primitive data type in Queueable apex.
b) Chaining is possible.
c) Can Monitor the job because when you submit job by invoking system.enqueueJob method it returns the ID.You can use that ID to monitor.
Example
public class ContactQueueable implements Queueable {
public void execute(QueueableContext context) {
List<Contact> conList = [Select id, status__c from Contact];
for (Contact con : conList){
con.status__c = 'Inactive';
}
update conList;
}
}
Execute Window
ContactQueueable con = new ContactQueueable();
ID JobId = System.enqueueJob(con);
system.debug('Job Id : ' + JobId);
public class ContactCreationQueueable implements Queueable {
private List<Account> accListToCreateContacts;
public ContactCreationQueueable(List<Account> expectingAccountsFromTrigger) {
this.acclistToCreateContacts = expectingAccountsFromTrigger;
}
public void execute(QueueableContext qCont) {
List<Contact> conListToInsert = new List<Contact>();
//Loop on all accounts that are inserted
for(Account acc: accListToCreateContacts){
Contact con = new Contact();
con.lastName = acc.Name;
con.AccountId = acc.Id;
conListToInsert.add(con); // Add each contact to list
}
if(conListToInsert.size() > 0)
INSERT conListToInsert;
}
}
// Trigger
trigger AccountTriggerForContacts on Account (after insert) {
if(Trigger.isAfter && Trigger.isInsert){
System.enqueueJob(new ContactCreationQueueable(Trigger.New)); //Trigger.new has list of accounts that are inserted
}
}
- You can run Apex classes at a specified time.
- Run Maintenance tasks on Daily or Weekly basis.
- Implements Schedulable interface in Apex class.
- It is also a type of asynchronous apex in which you can schedule your jobs daily & weekly.
- The Apex scheduler lets you delay execution so that you can run apex classes at a specified time.
- It schedules a job in future.
- It's a global class that implements Schedulable Interface.
- You can schedule it using "CRON Expression" or using "Schedule apex button" in Salesforce Ul.
- It has only execute method.
- You can have only 100 active or scheduled jobs concurrently.
- You can call future method using schedule apex ;@future(callout=true)
Syntax
global class SomeClass implements Schedulable {
global void execute(SchedulableContext ctx) {
// write some code
}
}
Select Id, CronJobDetail.Name, State, NextFireTime From CronTrigger limit 10
Ans: "CronTrigger"
Integration Methods
Salesforce can be integrated with the other systems by using various methods depending upon the overall business requirement, the commonly used ones are:
- Via Apex Method
- Mulesoft, Informatica and other third party ETL Tools
- Various AppExchange Products
Points to remember before setting up the integration between Salesforce and the other system using any of the above-mentioned methods:
- Document what and how often information needs to move between systems.
- Review API limits
- Identify the type of integration involved: Real-time or batch
In Salesforce, web services are a set of APIs that allow users to interact with the Salesforce platform. These APIs define a set of features that are accessible over the web. Web services can be used for a variety of functionalities, including:
- Data transfers
- Business process management
- Creating, retrieving, updating, or deleting records
- Real-time integration with third-party devices and apps
Apex web services can be used to expose Apex class methods as REST or SOAP web service operations. This allows external applications to integrate with Salesforce to perform operations such as creating, updating, and deleting records.
The SOAP API and REST API are both considered webservices and APIs in Salesforce. SOAP is a structured protocol, while REST is more flexible and less defined. Both of the API formats use data that both humans and machines can read, and both often use HTTP protocols.
API stands for Application Programming Interface and it's simply another way of sending commands to Salesforce, only this time there's no graphical UI to work through, instead developers use the APIs to send commands like View or Save to Salesforce programmatically by writing platform code.
A connected app is a framework that enables an external application to integrate with Salesforce using APIs and standard protocols, such as SAML, OAuth, and OpenID Connect. Connected apps use these protocols to authenticate, authorize, and provide single sign-on (SSO) for external apps.
Connected Apps help user delegate their access without sharing sensitive credentials or giving full control of their applications to third parties.
Note : The maximum number of OAuth 2.0 connected apps that can be registered through the dynamic client registration endpoint is 100
- To begin, navigate to the setup page in Salesforce and locate the "App Manager" within the setup menu. From there, click on the New Connected App button to proceed.
-
Enter all required information, then check the "Enable OAuth Settings" checkbox. Next, enter the callback URL. Now, proceed to select OAuth Scopes from the list. For now, you can select "Full access" and save your settings. Activation may take up to 10 minutes.
-
After creating your connected app, you'll be directed to your app page. Here, click on the Manage Consumer Details button. This action will first validate your account by sending a verification code to your email. After verification, you will receive your Consumer Key and Consumer Secret.
- To generate a Security Token, begin by clicking on your name in the header (classic mode) and navigate to the Settings section in the left-hand sidebar. From there, click on the "Reset My Security Token" link to create your token. Once generated, the token will be sent to your email address.
- Now Jump to our Postman and in postman first we need to create a request for access salesforce and get access_token.
- Enter URL in postman as base url and then add /services/oauth2/token
- create a post request.
- add following fields in body as form-data
- username : username of salesforce account
- password : password of salesforce account + Security Token which was sent to your email address.
- grant_type : 'password'
- client_id : Consumer Key
- client_secret : Consumer Secret
Note : There is one more setting is important to configure the integration between two Salesforce orgs that should be enabled 'Allow OAuth Username-Password Flows' when getting invalid grant error.
- After hit that above api you'll get access_token in response and from now you'll send many api by using that access_token in header.
public class SalesforceInregrationController {
public string instance_url;
public static void getAccessToken() {
String endpoint = 'https://dyninno-dev-ed.develop.my.salesforce.com/services/oauth2/token';
String methodType = 'POST';
String username = '[email protected]';
String password = 'Q!w2e3uaY9GY5VdmeBdnbkXeRHRnMc';
String grantType = 'password';
String clientId = '3MVG9pRzvMkjMb6k97lCSQehB7oWFZw5i.lrmuHTJASFIDNnnadire83dJD_Jjkasdfie393.Qh3jVxE756Ol';
String clientSecretKey = '6EBB20442F8A7C503AEA73FE259319192AB1FD40DA8F4153A5JASDLKFEIR2348';
// create a new http request
HttpRequest httpReq = new HttpRequest();
//set all fields into httpReq..
httpReq.setEndpoint(endpoint);
httpReq.setMethod(methodType);
httpReq.setBody(
'username=' + username +
'&password=' + password +
'&grant_type=' + grantType +
'&client_id=' + clientId +
'&client_secret=' + clientSecretKey
);
Http http = new Http();
HttpResponse httpResp = http.send(httpReq);
String jsonBody = httpResp.getBody();
// deserilization
SalesforceWrapper wrapData = (SalesforceWrapper)System.JSON.deserialize(jsonBody, SalesforceWrapper.class);
system.debug('====> Accesss Token ====> '+ wrapData.access_token);
system.debug('====> instance_url ====> '+ wrapData.instance_url);
system.debug('===> response ===> ' + httpResp.getBody());
getAccountData(wrapData.access_token);
// createAccount(wrapData.access_token);
}
public Class SalesforceWrapper{
public String access_token;
public String instance_url;
public SalesforceWrapper(String access_token, String instance_url){
this.access_token = access_token;
this.instance_url = instance_url;
}
}
public static void getAccountData(String accessToken) {
String url = 'https://dyninno-dev-ed.develop.my.salesforce.com/services/data/v60.0/query/?q=SELECT Id, Name FROM Account ORDER BY CreatedDate DESC LIMIT 5';
String methodType = 'GET';
// create a new http request
HttpRequest httpReq = new HttpRequest();
httpReq.setEndpoint(url);
httpReq.setMethod(methodType);
httpReq.setHeader('Authorization', 'Bearer ' + accessToken);
httpReq.setHeader('Content-Type', 'application/json');
Http http = new Http();
HttpResponse response = http.send(httpReq);
system.debug('get Account Data ====> '+response.getBody());
}
public static void createAccount(String accessToken) {
String endpoint = 'https://dyninno-dev-ed.develop.my.salesforce.com/services/data/v60.0/sobjects/Account';
String methodType = 'POST';
String body = '{"Name" : "Account Shikha Salesforce"}';
// create a new http request
HttpRequest httpReq = new HttpRequest();
httpReq.setEndpoint(endpoint);
httpReq.setMethod(methodType);
httpReq.setHeader('Authorization', 'Bearer ' + accessToken);
httpReq.setHeader('Content-Type', 'application/json');
httpReq.setBody(body);
Http http = new Http();
HttpResponse response = http.send(httpReq);
system.debug('new Account Data ====> '+response.getBody());
}
}
Apex Web Services:
- We can expose our Apex class methods as a REST or SOAP web service operations.
- By making your methods callable through the web, external applications can integrate with Salesforce to perform all type of operations like Add, Update, Delete etc to and from salesforce.
- What that means is that you can create custom web services to make your APEX methods available to complete APEX code.
Syntax To make your Apex class available as a REST web service is straightforward.
- Define your class as global,
- and define methods as global static.
- Add annotations to the class and methods.
For example, this sample Apex REST class uses one method. The getRecord method is a custom REST API call. It's annotated with @HttpGet and is invoked for a GET Request.
@RestResource(urlMapping='/Account/*')
global with sharing class MyRestResource{
@HttpGet
global static Account getRecord(){
// Add your code...
}
}
The class is annotated with @RestResource(urlMapping='/Account/*')
- Contains the RestRequest and RestResponse objects. RestContext Properties - The following are properties for RestContext.
- request: Returns the RestRequest for your Apex REST method. - response : Returns the RestResponse for your Apex REST method.
Base URL of Apex Rest
- The base endpoint for Apex REST is
https://yourInstance.my.salesforce.com/services/apexrest/
- The URL mapping is appended to the base endpoint to form the endpoint for your REST service.
For example, in the class example, the REST endpoint is
https://yourinstance.my.salesforce.com/services/apexrest/
- For your org, it could look something like,
https://yourInstance.my.salesforce.com/services/apexrest/Account/*
- The URL mapping is case-sensitive and can contain a wildcard character (*)
- Note: You can use each annotation only once in each Apex class.
- Different Types of Annotations available
Different Type of Annotations Available
Apex provides mainly three built in classes to work with HTT services and create HTTP requests:
- Http Class: It is used when initiating an HTTP request and response.
- HttpRequest Class: It is used when initiating HTTP requests such as GET, DELETE, POST, PUT, and PATCH.
- HttpResponse Class: It is used when handling the HTTP response returned by HTTP
Example
For single record
@RestResource(urlMapping='/createAccountRecord')
global class MyRestWebService {
@HttpPost
global static void sendAccountRecords(){
RestRequest request = RestContext.request;
RestResponse response = RestContext.response;
AccountWrapper requestBody = (AccountWrapper)JSON.deserialize(request.requestBody.toString(), AccountWrapper.class );
if(requestBody != null){
Account acc = new Account();
acc.Name = requestBody.name;
acc.Rating = requestBody.rating;
acc.Type = requestBody.type;
if(acc != null){
insert acc;
}
}
}
global Class AccountWrapper{
global String name;
global String rating;
global String type;
}
}
For Multiple Records
@RestResource(urlMapping='/createAccountRecord')
global class MyRestWebService {
@HttpPost
global static void sendAccountRecords(){
RestRequest request = RestContext.request;
RestResponse response = RestContext.response;
String responseJson = '[';
List<Account> accountList = new List<Account>();
List<AccountWrapper> requestBody = (List<AccountWrapper>)JSON.deserialize(request.requestBody.toString(), List<AccountWrapper>.class );
if(!requestBody.isEmpty() && requestBody.size() > 0){
for(AccountWrapper accObj : requestBody){
Account acc = new Account();
acc.Name = accObj.name;
acc.Rating = accObj.rating;
acc.Type = accObj.type;
accountList.add(acc);
}
if(!accountList.isEmpty() && accountList.size() > 0){
insert accountList;
for(Account ac : accountList){
responseJson += '{ "record id" : "'+ac.Id+'", "status" : "success"},';
}
responseJson = responseJson.removeEnd(',');
}
}
responseJson += ']';
response.responseBody = Blob.valueOf(responseJson);
response.statusCode = 200;
}
global Class AccountWrapper{
global String name;
global String rating;
global String type;
}
}
QA org => SIT org => UAT org => Production
- In Salesforce, "deployment" typically refers to the process of moving changes made in one environment (such as development or sandbox) to another sandbox or production environment.
- The deployment process ensures that any new configurations, customizations, or code changes are safely and correctly applied to the production instance of Salesforce without causing any disruption to the existing functionality.
- Metadata is the data which describes other data.
- Example : Field APIs, Object APIs
- Data is actual information.
- Example: Values in particular field of a record
When we make payment to SF to get the environment to work, we get Production. We must not do our work directly on Production as it hampers the work of client.
So we need to create Sandbox first.
It's nothing but the replica of Production environment with some limitations depending on type of Sandbox, and in Sandbox we actually need to make implementation of any Project work we do.
1 Jan = Got project
2 Jan = Client bought SF edition / PROD
3 Jan = Sandbox created | We started work
1 Feb = Project done | Deployed to PROD
2 Feb = Hereonwards client started using project & asked us to stop the service
.
.
.
.
.
.
.
.
1 Aug = Client told some changes in present code
2 Aug = Started working on PROD ? = Wrong = Why?
2 Aug = Started working on Sandbox = Wrong = Why?
PROD to SB = Refresh
When we need fresh copy of LIVE env.
SB to PROD = Deploy
When our work is done, and we need to provide to client.
There are 4 Sandboxes
- Developer
- Developer Pro
- Partial
- Full Copy (More or less a replica of Production Environment)
- Refresh Interval - 1 Day
- Data Storage - File Storage - 200MB | Data Storage - 200MB
- What's Copied? - Metadata
- Refresh Interval - 1 Day
- Data Storage - File Storage - 1GB | Data Storage - 1GB
- What's Copied? - Metadata
- Refresh Interval - 5 Days
- Data Storage - File Storage - 5GB | Data Storage - 5GB
- What's Copied? - Metadata + Sample Data
- Refresh Interval - 29 Days
- Data Storage - File Storage - 5GB(+more) | Data Storage - 5GB(+more)
- What's Copied? - Metadata + All Data
Change set is like a suitcase.
Change set helps us to transfer the items from SB to PROD where all the entities will be added to CS by person at SB. And this CS will be received by person at Prod.
Types:
- Inbound CS
- Outbound CS
Production:
URL = http://login.salesforce.com
UID = [email protected]
pw = abc
==
Sandbox:
(Assume, sandbox name is 'sandbox1')
URL = http://test.salesforce.com
UID = [email protected]
pw = abc
Before creating outbound set First of all we need to create a connection between our sandbox with production, so for that we need to click on deployment settings.
This is production screen (Deployment Screen)
https://www.youtube.com/watch?v=EAudzGsZoq8&ab_channel=ShekharSalesforce
- Change Set
- Workbench
- Salesforce CLI (Command Line Interface) - ANT
- Salesforce Extension for VS Code
- Salesforce DX
- Metadata API
- Managed Packages
- Continuous Integration/Continuous Deployment (CI/CD)
- 3rd Party Tools - Copado, Flosum, Gearset
- Change Sets are a simple and point-and-click way to transfer customizations between Salesforce organizations.
- They are typically used for moving changes from sandbox to production or between sandboxes.
- Change Sets can include various components such as custom objects, fields, workflows, reports, and more.
- Change set is provided by Salesforce itself, so we don't need to install anything it will be available inside our salesforce.
- Workbench is a powerful web-based tool and a popular choice among Salesforce developers and administrators for various tasks, including deployment.
- It provides a user-friendly interface to interact with Salesforce metadata and data, making it easier to perform operations such as data manipulation, metadata deployment, querying, and more.
- Salesforce CLI allows developers to use command-line commands to retrieve and deploy metadata between different environments.
- This method is more flexible and can be used in combination with version control systems, such as Git, for better development practices.
- This is an extension in Visual Studio Code that provides powerful development and deployment tools for Salesforce developers.
- It enables source code management, deployment, and other development tasks within the integrated development environment (IDE).
- Salesforce DX is a set of tools and practices aimed at improving the development lifecycle.
- It allows for source-driven development, meaning that the source code is the single source of truth for the application.
- It uses scratch orgs for development and facilitates easy deployment to production or other environments.
- The Metadata API is a web service provided by Salesforce, which allows you to retrieve and deploy metadata components programmatically.
- This API is suitable for automating deployments and integrating with external tools or systems.
- If you want to distribute your custom applications to other Salesforce organizations, you can package your components into Managed Packages.
- Managed Packages can be installed in other orgs, and they help you ensure that your customizations are secure and upgradeable.
- For a more automated and continuous deployment process, you can set up Cl/CD pipelines using tools like Jenkins, CircleCI, or Azure DevOps.
- These pipelines automate the deployment process, running tests and deploying changes to production whenever new code is pushed to a specific branch.
- Copado is a popular native Salesforce Continuous Integration and Continuous Deployment (Cl/CD) platform that aims to streamline and automate the development and deployment processes for Salesforce applications.
- It is designed to work natively within the Salesforce ecosystem and provides a set of tools and features that enable efficient application development, testing, and deployment on the Salesforce platform.
Youtube Link regarding change set, Outbonund and Inbound. : Deployment Using change set, Outbound and Inbound changeset https://www.youtube.com/watch?v=GIVbdVwjdag
A change set is a means by which one organization can send customizations to another organization. Change sets in Salesforce are groupings of components that can be deployed from one Salesforce org to another Salesforce org. This is out of a box deployment user interface tool provided by Salesforce. Change sets contain information about the org. They don’t contain data, such as records.
Outbound change set: An outbound change set is a change set created in the Source organization that you want to deploy to the target organization. Sending an outbound change set to the target org doesn’t mean that the changes will be deployed in that org. The change set must be accepted and deployed.
Inbound change set: An inbound change set is a change set that is sent from the source Salesforce org to the target Salesforce org.
Whenever any code fails or breaks, then SF throws an error on the screen of user.
This message is mostly raw and gives no clarity to user. Also user gets unclear information, which confuses him more.
The ideal scenarios is, whenever any error occurs, then we need to handle it and present a user friendly message for the same Or inform concern technical person like Developer about this issue.
To handle this situation, first you need to know, what kind of errors are possible?
Means types of Exceptions :
Type of exception:
- DML exception
- List exception
- Null pointer exception
- Query exception
- Generic type
How to write code here after to handle the exception:
try {
//code from which u think err will come
}
catch {
// instead of throwing err on screen, write catch block here, so SF will not throw err on screen and SF will assume that u will handle this situation and SF will realize it has no responsibility wont do anything.
// What we can go ?
// Show some simple message on screen / send email to developer etc anything
}
finally{
//write a code which u want to perform at extreme end, after try and catch scenario, if u want to do something at end, then write code here. This is optional to write any code here.
}
Handle Generic type of exception.
Try {
string name;
// name = 'Raj'; //we are expecting some data from end user but we didi not get and field is empty
integer LengthOfText;
LengthOfText = name. length () ;
system.debug ('Length = ' + Length0fText );
}
catch (exception ex) {
system.debug ('The info about error = ' + ex.getmessage()) ;
}
finally{
// signature work
}
// NullPointerException Example :
Try {
string name;
// name = 'Raj'; //we are expecting some data from end user but we didi not get and field is empty
integer LengthOfText;
LengthOfText = name. length () ;
system.debug ('Length = ' + Length0fText );
}
catch (NullPointerException ex) {
system.debug ('Seems you did not enter the needed data into salesforce') ;
}
finally{
// signature work
}
// DMLException Example :
try {
lead l = new lead();
insert l;
}
catch(DMLException ex) {
system.debug('Seems some DML error, we will take needed action!');
//show end user a clear message: "seems record did not create/update properly.
}
finally {
// signature work
}
// ListException Example :
try {
List<Integer> MyList = new List<integer>();
MyList.add(100);
integer temp = MyList[1];
}
catch(ListException ex) {
system.debug('Seems some List error, we will take needed action!');
}
finally {
// signature work
}
// QueryException Example :
try {
lead l;
l = [SELECT id FROM lead];
}
catch(QueryException ex) {
system.debug('Seems some Query error, we will take needed action!');
}
finally {
// signature work
}
========== Ideal Code ==========
Try{
// whatever logic or code you have..
lead 1 = [SELECT id FROM lead 1;
}
catch (ListException ex) {
system.debug ('Seems some List error, we will take needed action!');
}
catch (DMLException ex) {
system.debug ('Seems some DMI error, we will take needed action!');
}
catch (NullPointerException ex) {
system.debug ('Seems some Null value processed!');
}
catch (QueryException ex) {
system.debug ('Seems some Query error, we will t ake needed action!');
}
catch (Exception ex){
//show generic error message to end user
}
finally{
// signature work
System.debug ('Control came to Finally block');
}
//Efficient way
//Declare object name
string VarTableName = 'Shekhar_Pen_c ';
//Create a string variable where u will have to store entire query
string VarQuery = 'SELECT ';
//Declare a list which can hold such multiple names of table
List<string> VarListOfTables = new List<string>();
//Add above table entry into Varlist0fTables collection
VarListOfTables.add(VarTableName);
//There is a very imp class "Schema" in SF which tells info about objects, fields etc
set<String> VarSetOfAllFields = schema.describeSObjects(VarListOfTables)[0].fields.getMap().keyset();
//above line will give u all the fields in that object
system.debug('The output =' + VarSetOfAllFields);
//means, list of all fields is ready
//use for-each loop to process 1 by 1 all fields present in VarSetOfAllFields
for(string VarOneFieldName : VarSetOfAllFields) {
VarQuery = VarQuery + VarOneFieldName +', '
}
//current status of VarQuery = SELECT name,age_c,
VarQuery = VarQuery.removeEnd(', ');
//current status of VarQuery = SELECT name, age_c
//Add FROM and add table pame we decided above in the query
VarQuery = VarQuery + ' FROM ' + VarTableName;
//current status of VarQuery = SELECT name,age_c FROM Shekhar_Pen_c
Static resources in Salesforce are files that can be used in various parts of the platform, such as Visualforce pages, Apex classes, and Lightning components. These resources can include images, stylesheets, JavaScript files, and other types of documents. By using static resources, you can easily manage and reuse files across your organization, making it easier to maintain and update your Salesforce applications.
To create and manage static resources in Salesforce, you can use the Static Resource section of the Setup menu. Here, you can upload new resources, edit existing ones, and delete resources that are no longer needed.
To upload a new resource, click the "New" button, give your resource a name, select the file to upload and set the cache control. You can also upload multiple files at once by compressing them into a .zip file.
-
Step 1: Navigate to Setup, search for "Static Resource" and click on "New" to create a new resource.
-
Step 2:- Enter a name, select a file to upload and set the cache control, then click "Save"
-
Step 3:- Here you can see full detail about created static Resource and also see Where did it use, by clicking on where is this used?
-
Step 4 :- Now Referencing that Static Resource in Visualforce page ⇒ Open developer console ⇒ Create new Visualforce page
-
Step 5:- To render Visualforce page code ⇒ save the code and click on Preview button at the left corner on the top
In Apex classes, you can use the getContent() method to retrieve the contents of a static resource. The method returns the resource as a Blob, so you can then use it in various ways, such as creating a PDF document or sending it as an email attachment.
To use Static Resource in Lightning Web Component, First we need to import it from the import Salesforce_Images from '@salesforce/resourceUrl/SalesforceImages
Then, we can use Salesforce_Images to refer to the static resource.
https://arrify.com/generate-pdf-in-salesforce/
## Security in Apex class When you are writing code in Apex, the security of your code is of utmost importance. You should ensure that your code meets the latest Security guidelines.
User Mode is when apex code is running by taking the user’s permissions and sharing into consideration. Profile permissions, Field level permissions, and sharing rules are respected when code is running in user mode.
System Mode is when apex code is running without taking the user’s permissions and sharing into consideration. In system mode apex code has access to all objects, fields, and records and user’s sharing, FLS, and Profile permissions are not respected.
Note : Visualforce page always works in User Mode but APEX class by default works in God / System Mode
public ******* class MyClass {
//some code
}
***** = we can use "with sharing" or "without sharing"
- without sharing => GOD / System Mode
- with sharing => User Mode
If we dont write anything before "class" keyword, then also by default "without sharing" format will start If we dont want to allow APEX class behave like above scenario and we want to make that class more secure, then we need to use "with sharing" keyword.
Question : If by default keyword will be "without sharing" then why SF gave us that keyword? Answer : We dont need "without sharing" keyword when we have standalone APEX class (one APEX class), but definitely we need "without sharing" keyword when we make class to class calls.
// Suppose, we have 2 below classes:
public with sharing class ClassA{
// some code
FunctionA () {
// some code
// ClassB.FunctionB() ;
}
}
public class ClassB {
// some code
FunctionB() {
// This function contains a very general code which needs to be executed for all users, without checking any access
// That's why developer of class B did not write any keyword for class B
}
}
public class EmailManager {
// Public method
public void sendMail(String address, String subject, String body) {
// Create an email message object
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
String[] toAddresses = new String[] {address};
mail.setToAddresses(toAddresses);
mail.setSubject(subject);
mail.setPlainTextBody(body);
// Pass this email message to the built-in sendEmail method
// of the Messaging class
Messaging.SendEmailResult[] results = Messaging.sendEmail(
new Messaging.SingleEmailMessage[] { mail });
// Call a helper method to inspect the returned results
inspectResults(results);
}
// Helper method
private static Boolean inspectResults(Messaging.SendEmailResult[] results) {
Boolean sendResult = true;
// sendEmail returns an array of result objects.
// Iterate through the list to inspect results.
// In this class, the methods send only one email,
// so we should have only one result.
for (Messaging.SendEmailResult res : results) {
if (res.isSuccess()) {
System.debug('Email sent successfully');
}
else {
sendResult = false;
System.debug('The following errors occurred: ' + res.getErrors());
}
}
return sendResult;
}
}
The inspectResults() helper method, which is called by sendMail(), writes messages to the log by using the System.debug() method to indicate whether the email send operation was successful or had errors.
EmailManager em = new EmailManager();
em.sendMail('Your email address', 'Email Subject', 'Email Body');
Types :
- Client side validation (by Validation Rule)
- Server side validation (by Apex Class) (we can do in Trigger as well, but preference is to do this in Apex)
what is server side validation ?
Checking the data in Apex and then throw error.
public class PenClassDemonstration{
public static void applyDiscount(list<Pen__c> PensListNew) {
for (Pen_c p :PensListNew){
if(p.Price_c >= 100){
p.Price_c = p.Price_c - 20 ;
}
else if(p.Price_c >= 70 && p.Price_c < 100 ){
p.Price_c = p.Price_c - 10 ;
}
else if (p.Price_c >= 50 && p.Price_c < 70 ){
p.Price_c = p.Price_c - 5 ;
}
else if (p.Price_c >= 20 && p.Price._c < 50 ){
p.Price_c = p.Price_c-1 ;
}
else if (p.Price_c <=) {
//p.addError('Please enter a valid price!'); -- at top
p.Price_c.addError('Please enter a valid price!!');
}
}
}
}
There are many cases where we want to avoid some execution to happen on Test class level, which is fine if that works on APEX level (during non testing situation)
Test.isRunningTest() method is used to identify, if the piece of code being executed is invoked from a Test class execution or from other artefacts such as a Trigger, Batch Job etc
A. Bulk API is like a modified way to load large number of data in Salesforce
A. The reason why it is able to load large number of Records because it actually works in asynchronous mode and these are the key reason why bulk API is able to manage large number of data.
A. Data Loader by default is taking SOAP Based API.
A. YES (Then inteview again asked immediate question that have you seen any kind of difference in the behavious in your project while working with Buld API and when bulk api is not enabled) A. Yes we have seen some difference between the data order with Bulk APi and non bulk API the major difference is by default you are not allowed to work on hard delete but after enable bulk api that Hard Delete option will be enabled. and normal way you have an option in setting to insert null data or not, but after enable bulk api that null data insert option will be disabled that means you can not insert null data.
Use Bulk API enabled means Bulk API + Parallel mode (Fast, but there may be chance to fail connection)
Use Enable serial mode for Bulk api means Bulk API + Serial Mode (slow, but connection will not fail because it will insert one by one record)
The invocable actions mechanism allows to create custom code and package it into components / actions available in declarative tools, such as Flow. You provide input values that the apex class does something with, and it gives you output values.
The InvocableMethod annotation tells Salesforce that this class should be exposed to users of invocable actions in the Flow Builder. It can be used to invoke a single Apex method.
Sample Code
public class AccountAction {
@InvocableMethod(label='Get Account Names' description='Returns the list of account' category='Account')
public static List<Account> getAccount(List<ID> ids) {
// Do Something
}
}
- The invocable method must be static, public, or global, and its class must be an outer class.
- Only one method in a class can have the InvocableMethod annotation.
- Triggers can’t reference Invocable methods.
- No other annotations can be used alongside the InvocableMethod annotation.
- There can be at most one input parameter and its data type must be a list.
Scenario:
Create a Custom Picklist field (status) on Account with Active, In Active & Hold. If the Account status is changed to In Active or Hold. Delete all the Closed Won Opportunity related to that Account.
- In Flow Screen firstly we need to select Record Triggered Flow
- now we need to call the apex class method, so for that we need to click on plus button in flow and then select Action option from list.
- now New Action screen will be open and in this screen we need to search label of out invocable Method.
now everything is aligned but here is one problem, that is we are not checking if user change that status field only then after we need to run this flow
//Org limit checking code :
//Creating map for getting current limit of SF Org
Map<String, System.OrgLimit> VarOrgLimitMap = OrgLimits.getMap() ;
//Raw format output
System.debug ('VarOrgLimitMap-' + VarOrgLimitMap) ;
//Use below code to generate meaning / easy to comprehend data
for (String OrgLimitName :VarOrgLimitMap.keySet()) {
//This provides data of used as well as remaining limits
System.OrgLimit VarOrgLimit = VarOrgLimitMap.get(OrgLimitName) ;
//This line will print information about names of all limits provided by this code
System.debug ('Org Limit Name: ' + OrgLimitName) ;
//This line gives the used limits in Org
System.debug ('Org Limit Used:' + VarOrglimit.getValue()) ;
//This line gives the user limits in Org
System.debug('Org Limit Available:' + VarOrgLimit.getLimit());
}
Platform Event is based on Event-Driven Architecture which enable apps to communicate inside and outside of Salesforce. Platform events are based on the publish/subscribe model and work directly with a message bus which handles the queue of incoming events and processes listening for them.
Platform Events are used to deliver notification within Salesforce or external app. It is based on Event-Driven Architecture. Platform event are helpful to overcome with point to point Salesforce Integration challenges.
- Event-Driven Architecture: Platform Events support an event-driven architecture, allowing different parts of a system to communicate with each other through events.
- Publish-Subscribe Model: Events are published by publishers and consumed by subscribers, following a publish-subscribe model.
- Real-Time Processing: Events can be processed in real-time, enabling immediate response to changes or actions within the system.
public class OrderService {
public void placeOrder(String orderId) {
// Create instance of the event
Order_Event__e orderEvent = new Order_Event__e();
orderEvent.OrderID__c = orderId;
// Publish the event
Database.SaveResult sr = EventBus.publish(orderEvent);
if (sr.isSuccess()) {
System.debug('Event published successfully.');
} else {
System.debug('Event publish failed: ' + sr.getErrors()[0].getStatusCode() + ' - ' + sr.getErrors()[0].getMessage());
}
}
}
trigger OrderEventTrigger on Order_Event__e (after insert) {
for (Order_Event__e event : Trigger.New) {
// Process the event
System.debug('Received Order Event: ' + event.OrderID__c);
// Yahan pe hum custom logic add kar sakte hain jaise ki order processing
}
}
// Example usage
OrderService orderService = new OrderService();
orderService.placeOrder('123456789012345678');
Named Credentials in Salesforce provide a secure and simplified way to configure and manage authentication details for external services. By using Named Credentials, you can avoid hard-coding sensitive information like usernames, passwords, and endpoint URLs in your Apex code.
1. Simplified Authentication: Store authentication details in one place, reducing the need to manage credentials in your code. 2. Security: Keep sensitive information secure by storing it in Salesforce and avoiding exposure in code. 3. Maintainability: Easily update authentication details and endpoints without changing your code.
Use the callout: prefix followed by the Named Credential name to construct the endpoint URL.
Example : string url = 'callout:IMDB/imdb/imdbSearchByName?query='+searchText;
Named Credentials in Salesforce provide a robust, secure, and easy-to-maintain way to manage authentication details for external service integrations. By centralizing the storage of credentials and endpoint URLs, they enhance security and simplify the process of making API calls from Apex.
Suppose we have 2 users, and user_1 had created a record R1 and it is not visible to user_2. Now user_1 want to share that R1 record to user_2 so there are multiple way we have learned in admin to share that record like :
- Role based sharing
- Sharing Rules
- Manual Sharing
but apart from these 3 way there is an another way to share that record that is with the help of code, that is called Apex Sharing.
//======= Sharing record by code / in APEX class =============
//API name of object = Shekhar_Pen_c
//For this APEX sharing scenario = Shekhar_Pen_Share
//Create variable of the object, of which u want to share record
Shekhar_Pen_Share VarPenRecordShare = New Shekhar_Pen_Share();
//Decide which record u want to share
//Give record Id which u want to use for this sharing process
Shekhar_Pen_c VarP = [SELECT id FROM Shekhar_Pen_c WHERE name = 'Kenny'];
VarPenRecordShare.ParentId = VarP.id;
//Get record id of Tom user to whom we want to share above record
User VarU = [SELECT id FROM user WHERE firstname = 'Tom.'];
VarPenRecordShare.UserOrGroupId = VarU.Id;
//We can set single user or public group ID as well
//Decide which access level we want to give (2 famous options: Read/Edit)
VarPenRecordShare.AccessLevel = 'Read';
// Commit to the database
Database.insert (VarPenRecordShare, false);
Metadata is data about data. The main purpose of Metadata API is to move metadata between Salesforce orgs during the development process. Use Metadata API to deploy, retrieve, create, update, or delete customization information, such as custom object definitions and page layouts. You can move metadata with one of two ways. The first method is with Metadata API deploy() and retrieve() calls. Admins often use the deploy() and retrieve() calls to move the full metadata model.