{"id":18780199,"url":"https://github.com/artysta/salesforce-notes","last_synced_at":"2026-01-24T08:01:54.710Z","repository":{"id":115415258,"uuid":"408365343","full_name":"artysta/salesforce-notes","owner":"artysta","description":null,"archived":false,"fork":false,"pushed_at":"2022-05-19T18:52:56.000Z","size":326,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-12-29T10:43:01.521Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/artysta.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-09-20T08:29:06.000Z","updated_at":"2022-01-26T16:55:56.000Z","dependencies_parsed_at":null,"dependency_job_id":"343a956f-2c55-4ab6-829b-6794867f4b87","html_url":"https://github.com/artysta/salesforce-notes","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artysta%2Fsalesforce-notes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artysta%2Fsalesforce-notes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artysta%2Fsalesforce-notes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artysta%2Fsalesforce-notes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/artysta","download_url":"https://codeload.github.com/artysta/salesforce-notes/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239695407,"owners_count":19682060,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T20:25:16.308Z","updated_at":"2025-12-18T18:30:15.395Z","avatar_url":"https://github.com/artysta.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# #1 Relationships \u0026 relationship SOQL queries examples.\n\nThere are two main relationship types in Salesforce Lookup Relationship and Master-Detail relationships. There are some differences betweent these relationships:\n\n\u003ccenter\u003e\n    \u003ctable\u003e\n        \u003ctr\u003e\n            \u003cth\u003eLookup Relationship\u003c/th\u003e\n            \u003cth\u003eMaster-Detail Relationship\u003c/th\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eup to 25 per one object\u003c/td\u003e\n            \u003ctd\u003eup to 2 per one object\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eparent is not a required field\u003c/td\u003e\n            \u003ctd\u003eparent field on a child is required\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003edeleting a parent does not delete a child\u003c/td\u003e\n            \u003ctd\u003edeleting a parent automatically deletes a child\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003ecan be multiple layers deep\u003c/td\u003e\n            \u003ctd\u003ea child of one master detail relationship cannot be the parent of another one\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eno impact on a security and access\u003c/td\u003e\n            \u003ctd\u003eaccess to a parent determines access to a children\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003e-\u003c/td\u003e\n            \u003ctd\u003ethe standard object cannot be on the detail side of a relationship with a custom object\u003c/td\u003e\n        \u003c/tr\u003e\n    \u003c/table\u003e\n\u003c/center\u003e\n\n## Lookup relationships:\n\n### Standard objects:\n\n**Account** (**Parent**), **Contact** (**Child**) - Lookup field is on the **Contact** standard object | One **Account** can have 0 or many **Contacts** | One **Contact** can have 0 or one **Account**.\n\nChild-to-parent queries:\n\n```sql\nSELECT Name, Phone, Account.Id, Account.Name FROM Contact\n```\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"./screenshots/screenshot-1.png\" alt=\"screen-1.png\"/\u003e\n\u003c/p\u003e\n\n```sql\nSELECT Name, Phone, Account.Id, Account.Name FROM Contact WHERE Account.Id = '0010900000iMM6GAAW'\n```\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"./screenshots/./screenshot-2.png\" alt=\"screen-2.png\"/\u003e\n\u003c/p\u003e\n\nParent-to-child queries:\n\n```sql\nSELECT Id, Name, (SELECT Name, Phone FROM Contacts) FROM Account\n```\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"./screenshots/./screenshot-3.png\" alt=\"screen-3.png\"/\u003e\n\u003c/p\u003e\n\n```sql\nSELECT Id, Name, (SELECT Name, Phone FROM Contacts) FROM Account WHERE Id = '0010900000iMM6DAAW'\n```\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"./screenshots/./screenshot-4.png\" alt=\"screen-4.png\"/\u003e\n\u003c/p\u003e\n\n### Custom objects:\n\n**Vehicle** (**Parent**), **Driver** (**Child**) - Lookup field is on the **Driver** custom object | One **Vehicle** can have 0 or many **Drivers** | One **Driver** can have 0 or one **Vehicle**\n\nChild-to-parent queries:\n\n```sql\nSELECT Name, Nationality__c, Vehicle__r.Id, Vehicle__r.Manufacturer__c FROM Driver__c\n```\n\n```sql\nSELECT Name, Nationality__c, Vehicle__r.Id, Vehicle__r.Manufacturer__c FROM Driver__c WHERE Vehicle__r.Id = 'a000900000FThKjAAL'\n\n```\n\nParent-to-child queries:\n\n```sql\nSELECT Id, Name, Manufacturer__c, (SELECT Name, Nationality__c FROM Drivers__r) FROM Vehicle__c\n```\n\n```sql\nSELECT Id, Name, Manufacturer__c, (SELECT Name, Nationality__c FROM Drivers__r) FROM Vehicle__c WHERE Id = 'a000900000FThKjAAL'\n```\n\nNotice that it is pretty important to use **__c** suffix when you are using/querying custom object and **__r** suffix when you want to query fields of realated object. Also remember to use plural form in the second SELECT statement when you use parent-to-child queries.\n\n## Master-Detail relationships:\n\nThe Master-Detail queries look exactly the same as for the Lookup.\n\n### Standard objects:\n\n**Account** (**Parent/Master**), **Entitlement** (**Child/Detail**) - Master-detail field is on the **Entitlement** standard object | One **Account** can have 0 or many **Entitlements** | One **Entitlement** must have exactly one **Account**.\n\nChild-to-parent queries:\n\n```sql\nSELECT Name, Status, Account.Id, Account.Name FROM Entitlement\n```\n```sql\nSELECT Name, Status, Account.Id, Account.Name FROM Entitlement WHERE Account.Id = '0010900000g6FkaAAE'\n```\n\nParent-to-child queries:\n\n```sql\nSELECT Id, Name, (SELECT Name, Status FROM Entitlements) FROM Account\n```\n```sql\nSELECT Id, Name, (SELECT Name, Status FROM Entitlements) FROM Account WHERE Id = '0010900000iMM6FAAW'\n```\n\n# #2 Batches, Scheduled Jobs \u0026 state.\n\nThe idea of Apex batch jobs is to work on the huge number (thousands / millions) of records. Apex Batch class has to implement Database.Batchable\u003csObject\u003e interface and its 3 methods:\n\n - **start** (pre-processing operations) - here the records are being collected\n - **execute** - here some operations are performed on the provided records\n - **finish** (post-processing operations) - this method is called once all operations are done\n\nHere is a pretty simple implementation of this interface. This batch just deletes every Opportunity with the stage name 'Closed Won' or 'Closed Lost'.\n\n```java\npublic class OpportunitiesCleanerBatch implements Database.Batchable\u003csObject\u003e {\n    public Database.QueryLocator start(Database.BatchableContext context) {\n        System.debug('Job started!');\n        return Database.getQueryLocator([SELECT StageName FROM Opportunity]);\n    }\n    \n    public void execute(Database.BatchableContext context, List\u003cOpportunity\u003e opportunities) {\n        List\u003cOpportunity\u003e opportunitiesToRemove = new List\u003cOpportunity\u003e();\n        \n        for (Opportunity o : opportunities) {\n            if (o.StageName == 'Closed Won' || o.StageName == 'Closed Lost') {\n                opportunitiesToRemove.add(o);\n            }\n        }\n        \n        delete opportunitiesToRemove;\n\n        System.debug(\n            String.format(\n                '{0} opportunities with the \\\"{1}\\\" or \\\"{2}\\\" stage name have been deleted.',\n                new List\u003cString\u003e { String.valueOf(opportunitiesToRemove.size()), 'Closed Won', 'Closed Lost' }\n            )\n        );\n    }\n    \n    public void finish(Database.BatchableContext context) {\n        System.debug('Job finished!');\n    }\n}\n```\n\nHere is the code that allows to run the batch above from Developer Console or within another class:\n\n```java\nOpportunitiesCleanerBatch oppCleanerBatch = new OpportunitiesCleanerBatch();\nDatabase.executeBatch(oppCleanerBatch);\n```\n\n---\n\nThe interface, that works really good with batches is Schedulable interface. It allows to schedule an instance of the Apex class to run at a specific time.\n\nHere is implementation of Schedulable interface that runs OpportunitiesCleanerBatch.\n\n```java\npublic class OpportunitiesCleanerBatchSchedule implements Schedulable {\n    public void execute(SchedulableContext context) {\n        OpportunitiesCleanerBatch oppCleanerBatch = new OpportunitiesCleanerBatch();\n        Database.executeBatch(oppCleanerBatch);\n    }\n}\n```\n\nThere are two possibilities to schedule a job. Obviously you can run the code from the Developer Console or within another class.\n\n```java\nOpportunitiesCleanerBatchSchedule scheduledBatch = new OpportunitiesCleanerBatchSchedule();\n// String sch = 'SECONDS MINUTES HOURS DAY_OF_MONTH MONTH DAY_OF_WEEK OPTIONAL_YEAR';\nString sch = '00 45 6-22 ? * * *';\nSystem.schedule('Opportunities Cleaner Batch', sch, scheduledBatch);\n```\n\nYou can also use UI to do this: **Setup -\u003e Apex Jobs -\u003e Apex Classes -\u003e Schedule Apex**. There you have to add job name, select proper class and choose specific time and how often you want the job to run.\n\n---\n\nAs the Salesforce documentation says:\n\n\u003e Each execution of a batch Apex job is considered a discrete transaction.\n\nImplementing **Database.Stateful** interface in the case of the batches allows to maintain state across the transactions.\n\nExample:\n\nLets say that we have 1028 Case records in our Salesforce database. We have written a simple batch (this batch does nothing - I just want this example to be as simple as possible). This is how does it looks like:\n\n```java\npublic class CasesBatch implements Database.Batchable\u003csObject\u003e {\n    private Integer count = 0;    \n    \n    public Database.QueryLocator start(Database.BatchableContext context){\n        System.debug('count (start): ' + count);\n        String exampleQuery = 'SELECT Id FROM Case';\n        return Database.getQueryLocator(exampleQuery);\n    }\n    \n    public void execute(Database.BatchableContext context, List\u003csObject\u003e scope){\n\t\tthis.count++;\n        System.debug('scope.size(): ' + scope.size());\n        System.debug('count (execute): ' + count);\n    }\n    \n    public void finish(Database.BatchableContext BC){\n        System.debug('count (finish): ' + count);\n    }\n}\n```\n\nLest run our batch using the code below:\n\n```java\nDatabase.executeBatch(new CasesBatch());\n```\n\nWe have got couple of logs:\n\n```cmd\n#1\n19:29:03:020 USER_DEBUG [5]|DEBUG|count (start): 0\n\n#2\n19:29:03:001 USER_DEBUG [12]|DEBUG|scope.size(): 200\n19:29:03:001 USER_DEBUG [13]|DEBUG|count (execute): 1\n\n#3\n19:29:03:002 USER_DEBUG [12]|DEBUG|scope.size(): 200\n19:29:03:002 USER_DEBUG [13]|DEBUG|count (execute): 1\n\n#4\n19:29:04:001 USER_DEBUG [12]|DEBUG|scope.size(): 200\n19:29:04:001 USER_DEBUG [13]|DEBUG|count (execute): 1\n\n#5\n19:29:04:001 USER_DEBUG [12]|DEBUG|scope.size(): 200\n19:29:04:001 USER_DEBUG [13]|DEBUG|count (execute): 1\n\n#6\n19:29:04:001 USER_DEBUG [12]|DEBUG|scope.size(): 200\n19:29:04:001 USER_DEBUG [13]|DEBUG|count (execute): 1\n\n#7\n19:29:04:001 USER_DEBUG [12]|DEBUG|scope.size(): 28\n19:29:04:001 USER_DEBUG [13]|DEBUG|count (execute): 1\n\n#8\n19:29:04:025 USER_DEBUG [17]|DEBUG|count (finish): 0\n```\n\n---\n\nLets implement **Database.Stateful** interface now:\n\n```java\npublic class CasesBatch implements Database.Batchable\u003csObject\u003e, Database.Stateful {\n    private Integer count = 0;    \n    \n    public Database.QueryLocator start(Database.BatchableContext context){\n        System.debug('count (start): ' + count);\n        String exampleQuery = 'SELECT Id FROM Case';\n        return Database.getQueryLocator(exampleQuery);\n    }\n    \n    public void execute(Database.BatchableContext context, List\u003csObject\u003e scope){\n\t\tthis.count++;\n        System.debug('scope.size(): ' + scope.size());\n        System.debug('count (execute): ' + count);\n    }\n    \n    public void finish(Database.BatchableContext BC){\n        System.debug('count (finish): ' + count);\n    }\n}\n```\n\nLets run the batch again and check the logs:\n\n```cmd\n#1\n19:35:15:019 USER_DEBUG [5]|DEBUG|count (start): 0\n\n#2\n19:35:16:002 USER_DEBUG [12]|DEBUG|scope.size(): 200\n19:35:16:002 USER_DEBUG [13]|DEBUG|count (execute): 1\n\n#3\n19:35:16:001 USER_DEBUG [12]|DEBUG|scope.size(): 200\n19:35:16:001 USER_DEBUG [13]|DEBUG|count (execute): 2\n\n#4\n19:35:16:001 USER_DEBUG [12]|DEBUG|scope.size(): 200\n19:35:16:001 USER_DEBUG [13]|DEBUG|count (execute): 3\n\n#5\n19:35:16:001 USER_DEBUG [12]|DEBUG|scope.size(): 200\n19:35:16:001 USER_DEBUG [13]|DEBUG|count (execute): 4\n\n#6\n19:35:16:001 USER_DEBUG [12]|DEBUG|scope.size(): 200\n19:35:16:001 USER_DEBUG [13]|DEBUG|count (execute): 5\n\n#7\n19:35:16:001 USER_DEBUG [12]|DEBUG|scope.size(): 28\n19:35:16:001 USER_DEBUG [13]|DEBUG|count (execute): 6\n\n#8\n19:35:16:020 USER_DEBUG [17]|DEBUG|count (finish): 6\n\n```\n\n# #3 Some useful Apex code snippets.\n\n- Send an email + debug.\n\n```java\nString subject = 'Email Subject.';\nString body = 'Email Body.';\nString[] addresses = new String[] { 'test@mail.com' };\n\nMessaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();\n\nmail.setToAddresses(addresses);\nmail.setSubject(subject);\nmail.setPlainTextBody(body);\n\nMessaging.SendEmailResult[] results = Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });\n\nfor (Messaging.SendEmailResult r : results) {\n    System.debug(r);\n}\n```\n\n- Get picklist values for picklist field (StageName field values of Opportunity standard object in this case).\n\n```java\nDescribeFieldResult stageNameField = Opportunity.StageName.getDescribe();\n\nfor (Integer i = 0; i \u003c stageNameField.getPicklistValues().size(); i++) {\n    System.debug(String.format(' StageName {0}: {1}', new List\u003cString\u003e { String.valueOf(i), stageNameField.getPicklistValues()[i].value }));\n}\n```\n\n- Run batch manually using Anonymous Apex.\n\n```java\nExampleBatch batch = new ExampleBatch();\nDatabase.executeBatch(batch);\n```\n- Schedule a job using Anonymous Apex.\n\n```java\nExampleBatchSchedule scheduledBatch = new ExampleBatchSchedule();\n// String sch = 'SECONDS MINUTES HOURS DAY_OF_MONTH MONTH DAY_OF_WEEK OPTIONAL_YEAR';\nString sch = '00 45 6-22 ? * * *';\nSystem.schedule('Example Batch', sch, scheduledBatch);\n```\n\n- Easy way to get set of object Ids.\n\t\n```java\nList\u003cSObject\u003e opportunities = [SELECT Id, Name FROM Opportunity LIMIT 10];\nSet\u003cId\u003e opportunitiesIds = (new Map\u003cId, SObject\u003e(opportunities)).keySet();\t\n```\n\n# #4 Aura Components + Apex Controllers.\n\nBelow you can find an Aura Component which is using Apex Controller to read and display data. It doesn't look so good, but it is one of the simplest examples. In this case data loads after clicking the **Get Opportunities** button.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"./screenshots/aura-1.png\" alt=\"aura-1.png\"/\u003e\n\u003c/p\u003e\n\n###### Aura Component: opportunitiesList.cmp\n\n```html\n\u003caura:component implements=\"flexipage:availableForAllPageTypes\" controller=\"OpportunitiesController\"\u003e\n    \u003caura:attribute name=\"opportunities\" type=\"Opportunity[]\"/\u003e\n    \u003c!-- We are calling component controller method here (NOT Apex method!) --\u003e\n    \u003cbutton onclick=\"{!c.getOpportunities}\"\u003eGet Opportunities\u003c/button\u003e\n    \u003cp\u003eOpportunities list:\u003c/p\u003e\n    \u003cul\u003e\n        \u003caura:iteration var=\"opportunity\" items=\"{!v.opportunities}\"\u003e\n            \u003cli\u003eId: {!opportunity.Id}, Name: {!opportunity.Name}, StageName: {!opportunity.StageName}\u003c/li\u003e\n        \u003c/aura:iteration\u003e\n    \u003c/ul\u003e\n\u003c/aura:component\u003e\n```\n\n###### JavaScript Controller: opportunitiesListController.js\n\n```js\n({\n    getOpportunities: function(component, event, helper){\n        // We are calling Apex controller method here.\n        var action = component.get(\"c.getOpportunitiesList\");\n        action.setCallback(this, function(response){\n            var state = response.getState();\n            if (state === \"SUCCESS\") {\n                component.set(\"v.opportunities\", response.getReturnValue());\n            }\n        });\n        $A.enqueueAction(action);\n    }\n})\n```\n\n###### Apex Controller: OpportunitiesController.cls\n\n```java\npublic with sharing class OpportunitiesController {\n    @AuraEnabled\n    public static List\u003cOpportunity\u003e getOpportunitiesList() {\n        return [SELECT Id, Name, StageName, CreatedDate\n                FROM Opportunity\n                ORDER BY CreatedDate DESC\n                LIMIT 10];\n    }\n}\n```\n\nOf course we can use datatable to achieve much better appearance. In this case there is no need to click the button to load data.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"./screenshots/aura-2.png\" alt=\"aura-2.png\"/\u003e\n\u003c/p\u003e\n\n###### Aura Component: opportunitiesList.cmp\n\n```html\n\u003caura:component implements=\"flexipage:availableForAllPageTypes,force:hasRecordId\" controller=\"OpportunitiesController\" access=\"global\"\u003e\n    \u003caura:attribute name=\"columns\" type=\"list\"/\u003e\n    \u003caura:attribute name=\"opportunities\" type=\"Opportunity[]\"/\u003e\n    \u003c!-- We are calling component controller method here (NOT Apex method!) --\u003e\n    \u003caura:handler name='init' action=\"{!c.getOpportunities}\" value=\"{!this}\"/\u003e\n    \u003c!-- \u003cbutton onclick=\"{!c.getOpportunities}\"\u003eGet Opportunities\u003c/button\u003e --\u003e\n    \u003clightning:card title=\"Opportunities List\"\u003e\n        \u003clightning:datatable keyField=\"id\"\n                             data=\"{!v.opportunities}\"\n                             columns=\"{!v.columns}\"\n                             hideCheckboxColumn=\"true\"\n                             maxColumnWidth=\"1000\"\n                             minColumnWidth=\"150\"/\u003e\n    \u003c/lightning:card\u003e\n\u003c/aura:component\u003e\n```\n\n###### JavaScript Controller: opportunitiesListController.js\n\n```js\n({\n    getOpportunities: function (component, event, helper) {\n        component.set('v.columns', [\n            {label: 'Id', fieldName: 'Id', type: 'text'},\n            {label: 'Name', fieldName: 'Name', type: 'text'},\n            {label: 'Stage Name', fieldName: 'StageName', type: 'text'},\n            {label: 'Creation Date', fieldName: 'CreatedDate', type: 'date'},\n        ]);\n            \n        // We are calling Apex controller method here.\n        var action = component.get(\"c.getOpportunitiesList\");\n        action.setCallback(this, function(response){\n            var state = response.getState();\n            if (state === \"SUCCESS\") {\n                component.set(\"v.opportunities\", response.getReturnValue());\n            }\n        });\n        $A.enqueueAction(action);\n    }\n})\n```\n\n# #5 Object Oriented Programming in Apex.\n\nWhat is Apex?\n\nAs the Salesforce documentation says:\n\n\u003e Apex is a strongly typed, object-oriented programming language that allows developers to execute flow and transaction control statements on Salesforce servers in conjunction with calls to the API\n\nIt is just an OOP language that is pretty similar to Java or C#.\n\nBelow you can find some informations about classes and interfaces:\n\n\u003ccenter\u003e\n    \u003ctable\u003e\n    \u003ctbody\u003e\n        \u003ctr\u003e\n            \u003cth\u003e\u003c/th\u003e\n            \u003cth\u003eVirtual\u003c/th\u003e\n            \u003cth\u003eAbstract\u003c/th\u003e\n            \u003cth\u003eInterface\u003c/th\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eimplementation\u003c/td\u003e\n            \u003ctd\u003efull class implementation\u003c/td\u003e\n            \u003ctd\u003epartial class implementation\u003c/td\u003e\n            \u003ctd\u003eit's just a \"contract\"\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003ekeyword\u003c/td\u003e\n            \u003ctd\u003eextends\u003c/td\u003e\n            \u003ctd\u003eextends\u003c/td\u003e\n            \u003ctd\u003eimplements\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003ecan have variables and properties\u003c/td\u003e\n            \u003ctd\u003eyes\u003c/td\u003e\n            \u003ctd\u003eyes\u003c/td\u003e\n            \u003ctd\u003eno\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003ecan have defined methods\u003c/td\u003e\n            \u003ctd\u003eyes\u003c/td\u003e\n            \u003ctd\u003eyes\u003c/td\u003e\n            \u003ctd\u003eno\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003ecan be instantiated (directly)\u003c/td\u003e\n            \u003ctd\u003eyes\u003c/td\u003e\n            \u003ctd\u003eno\u003c/td\u003e\n            \u003ctd\u003eno\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003ecan have abstract methods\u003c/td\u003e\n            \u003ctd\u003eno\u003c/td\u003e\n            \u003ctd\u003eyes\u003c/td\u003e\n            \u003ctd\u003eyes\u003c/td\u003e\n        \u003c/tr\u003e\n    \u003c/table\u003e\n\u003c/center\u003e\n\nHere is really simple example, how this all works together in Apex:\n\n###### Tuningable.cls\n\n```java\npublic interface Tuningable {\n    // Interface methods cannot have a body.\n    void tuning();\n}\n```\n\n###### Vehicle.cls\n\n```java\n// This class is abstract so it cannot be instantiated. It can be extended.\npublic abstract class Vehicle {\n    // Automatic property - like in C#.\n    public String Name { get; set; }\n    public String Color { get; set; }\n    \n    public Vehicle(String name, String color) {\n        this.Name = name;\n        this.Color = color;\n    }\n    \n    // Abstract method that has to be implemented by the subclass. Abstract methods have no body.\n    public abstract Integer getMaxSpeed();\n    \n    // Virtual method can (but not has to) be overridden by the subclass.\n    public virtual String getInfo() {\n        return String.format('I am a {0} {1}.', new List\u003cString\u003e { this.Color, this.Name });\n    }\n}\n```\n\n###### BaseCar.cls\n\n```java\n// This is a virtual class so it can be extended by other classes. It also can be instantiated.\npublic class BaseCar extends Vehicle {\n    public Integer MaxSpeed { get; set; }\n    \n    // We can call super class constructor by using 'super' keyword - like in Java.\n    public BaseCar(String name, String color, Integer maxSpeed) {\n        super(name, color);\n        this.MaxSpeed = maxSpeed;\n    }\n    \n    public override Integer getMaxSpeed() {\n        return this.MaxSpeed;\n    }\n    \n    // We can call super class method by using 'super' keyword - like in Java.\n    // We have to use 'override' keyword if we want to override superclass method.\n    public override String getInfo() {\n        return String.format('{0} My max speed is {1} km/h.',\n                             new List\u003cString\u003e { super.getInfo(), String.valueOf(this.MaxSpeed) });\n    }\n}\n```\n\n###### SportsCar.cls\n\n```java\n// We cannot extend this class. We can extend class only if it is abstract or virtual.\npublic class SportsCar extends BaseCar implements Tuningable {\n    public Boolean HasTurbo { get; set; }\n    \n    public SportsCar(String name, String color, Integer maxSpeed) {\n        super(name, color, maxSpeed);\n    }\n    \n    // We can call another constructor by using 'this' keyword - like in Java.\n    public SportsCar(String name, String color, Integer maxSpeed, Boolean hasTurbo) {\n        this(name, color, maxSpeed);\n        this.hasTurbo = hasTurbo;\n    }\n    \n    // We do not have to (and even cannot) use 'override' keyword if we are implementing interface method.\n    public void tuning() {\n        maxSpeed += 10;\n    }\n}\n```\n\n###### Example:\n```java\nSportsCar car = new SportsCar('Nissan', 'Blue', 100, true);\ncar.tuning();\nSystem.debug(car.getInfo()); // 20:48:36:017 USER_DEBUG [70]|DEBUG|I am a Blue Nissan. My max speed is 110 km/h.\n```\n\n# #5 Apex Governor Limits.\n\nWhat are the Apex Governor Limits?\n\nAs the Salesforce documentation says:\n\n\u003e Because Apex runs in a multitenant environment, the Apex runtime engine strictly enforces limits so that runaway Apex code or processes don’t monopolize shared resources.\n\nBelow you can find one of the most common limits in Salesforce (the table is not complete):\n\n\u003ccenter\u003e\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003cth\u003eDescription\u003c/th\u003e\n        \u003cth\u003eSynchronous Limit\u003c/th\u003e\n        \u003cth\u003eAsynchronous Limit\u003c/th\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003enumber of SOQL queries\u003c/td\u003e\n        \u003ctd\u003e100\u003c/td\u003e\n        \u003ctd\u003e200\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003enumber of records retrieved by SOQL queries\u003c/td\u003e\n        \u003ctd\u003e50,000\u003c/td\u003e\n        \u003ctd\u003e50,000\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003enumber of records retrieved by Database.getQueryLocator\u003c/td\u003e\n        \u003ctd\u003e10,000\u003c/td\u003e\n        \u003ctd\u003e10,000\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003enumber of SOSL queries\u003c/td\u003e\n        \u003ctd\u003e20\u003c/td\u003e\n        \u003ctd\u003e20\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003enumber of records retrieved by a single SOSL query\u003c/td\u003e\n        \u003ctd\u003e2,000\u003c/td\u003e\n        \u003ctd\u003e2,000\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003enumber of DML statements issued\u003c/td\u003e\n        \u003ctd\u003e150\u003c/td\u003e\n        \u003ctd\u003e150\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003enumber of records processed as a result of DML statements\u003c/td\u003e\n        \u003ctd\u003e10,000\u003c/td\u003e\n        \u003ctd\u003e10,000\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eheap size\u003c/td\u003e\n        \u003ctd\u003e6 MB\u003c/td\u003e\n        \u003ctd\u003e12 MB\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003emaximum CPU time on the Salesforce servers\u003c/td\u003e\n        \u003ctd\u003e10,000 ms\u003c/td\u003e\n        \u003ctd\u003e60,000 ms\u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\u003c/center\u003e\n\nExceeding the limit will cause an exceptions to occur. Some of examples:\n\nToo many SOQL queries (synchronous limit):\n\n```java\n// 'System.LimitException: Too many SOQL queries: 101' will be thrown because max number of (synchronous) SOQL queries is equal to 100.\nstatic void queryCases() {\n    for (Integer i = 0; i \u003c 101; i++) {\n        Case c = [SELECT Id FROM Case];\n    }\n}\n```\n\nToo many SOQL queries (asynchronous limit):\n\n```java\n// 'System.LimitException: Too many SOQL queries: 201' will be thrown because max number of (asynchronous) SOQL queries is equal to 200.\n@future\nstatic void queryCases() {\n    for (Integer i = 0; i \u003c 201; i++) {\n        Case c = [SELECT Id FROM Case];\n    }\n}\n```\n\nToo many DML statements:\n\n```java\n// 'System.LimitException: Too many DML statements: 151' will be thrown because max number of DML statements is equal to 150.\nstatic void insertCases() {\n    for (Integer i = 0; i \u003c 151; i++) {\n        insert new Case();\n    }\n}\n```\n\nToo many SOSL queries:\n\n```java\n// 'System.LimitException: Too many SOSL queries: 21' will be thrown because max number of SOSL queries is equal to 20.\nstatic void queryCases() {\n    for (Integer i = 0; i \u003c 21; i++) {\n        List\u003cList\u003csObject\u003e\u003e sObjects = [FIND 'New' IN ALL FIELDS RETURNING Case(Status)];\n    }\n}\n```\n\nToo many DML rows:\n\n```java\n// 'System.LimitException: Too many DML rows: 10001' will be thrown because max number of records processed as a result of DML statements is equal to 10 000.\nstatic void insertCases() {\n    List\u003cCase\u003e cases = new List\u003cCase\u003e();\n    \n    for (Integer i = 0; i \u003c 10001; i++) {\n        cases.add(new Case());\n    }\n    \n    insert cases;\n}\n```\n\n# #6 Apex Triggers.\n\nApex Trigger is a code that executes before or after any operations are performed on the specific record.\n\nBelow you can find an example of pretty simple trigger that updates Vehicle name by appending current user name to it.\n\n```java\ntrigger VehicleTrigger on Vehicle__c (before insert) {\n    for (Vehicle__c vehicle : Trigger.New) {\n        vehicle.Name += ' of ' +  System.UserInfo.getUserName();\n    }\n}\n```\n\nPossibly types of operations are:\n\n- insert\n- update\n- delete\n- merge\n- upsert\n- undelete\n\nThere are some Trigger context variables that allow you to check which operation fired the trigger or if it was fired before or after the records were saved:\n\n\u003ccenter\u003e\n    \u003ctable\u003e\n        \u003ctr\u003e\n            \u003cth\u003eVariable\u003c/th\u003e\n            \u003cth\u003eReturns\u003c/th\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eisInsert\u003c/td\u003e\n            \u003ctd\u003etrue if the trigger was fired due to an insert operation\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eisUpdate\u003c/td\u003e\n            \u003ctd\u003etrue if the trigger was fired due to an update operation\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eisDelete\u003c/td\u003e\n            \u003ctd\u003etrue if the trigger was fired due to a delete operation\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eisUndelete\u003c/td\u003e\n            \u003ctd\u003etrue if the trigger was fired after a record is recovered from the Recycle Bin\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eisBefore\u003c/td\u003e\n            \u003ctd\u003etrue if the trigger was fired before any record was saved\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eisAfter\u003c/td\u003e\n            \u003ctd\u003etrue if this trigger was fired after all records were saved\u003c/td\u003e\n        \u003c/tr\u003e\n    \u003c/table\u003e\n\u003c/center\u003e\n\nThere are also some variables, that allow you to access the records:\n\n\u003ccenter\u003e\n    \u003ctable\u003e\n        \u003ctr\u003e\n            \u003cth\u003eVariable\u003c/th\u003e\n            \u003cth\u003eReturns\u003c/th\u003e\n            \u003cth\u003eRecords available in\u003c/th\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003enew\u003c/td\u003e\n            \u003ctd\u003ea list of the new versions of the sObject records\u003c/td\u003e\n            \u003ctd\u003einsert, update, undelete (and can be modified only before)\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003enewMap\u003c/td\u003e\n            \u003ctd\u003ea map of IDs to the new versions of the sObject records\u003c/td\u003e\n            \u003ctd\u003ebefore update, after insert, after update, after undelete\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eold\u003c/td\u003e\n            \u003ctd\u003ea list of the old versions of the sObject records\u003c/td\u003e\n            \u003ctd\u003eupdate, delete\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eoldMap\u003c/td\u003e\n            \u003ctd\u003ea map of IDs to the old versions of the sObject records\u003c/td\u003e\n            \u003ctd\u003eupdate and delete\u003c/td\u003e\n        \u003c/tr\u003e\n    \u003c/table\u003e\n\u003c/center\u003e\n\n# #7 Queues\n\nSince Queues can be the owners of the records, we can take advantage of this fact and only search for records that are owned by the Queue to which the user is assigned.\n\n```java\nString currentUserId = UserInfo.getUserId();\n\n// Get GroupMember records related to the current User (where the Group Type is Queue).\nList\u003cGroupMember\u003e groupMembers = [SELECT GroupId\n                                  FROM GroupMember\n                                  WHERE Group.Type = 'Queue'];\n\n// Create set of the Queues Ids.\nSet\u003cId\u003e currentUserQueuesIds = new Set\u003cId\u003e();\n\nfor (GroupMember member : groupMembers) {\n    currentUserQueuesIds.add(member.GroupId);\n}\n\n// Select records where the Queue related to the current User is the Owner.\nList\u003cCase\u003e cases = [SELECT Id, Owner.Name\n                    FROM Case\n                    WHERE OwnerId IN :currentUserQueuesIds];\n```\n\n# #8 Schema class\n\nAs the Salesforce documentation says Schema class:\n\u003eContains methods for obtaining schema describe information.\n\nUseful examples:\n\nGet metadata information about custom apps.\n\n```java\nSchema.DescribeTabSetResult[] results = Schema.describeTabs();\n\nfor (Schema.DescribeTabSetResult result : results) {\n    System.debug('Label: ' + result.getLabel());\n}\n```\n\n```console\n03:36:27:072 USER_DEBUG [4]|DEBUG|Label: Sales\n03:36:27:072 USER_DEBUG [4]|DEBUG|Label: Service\n03:36:27:072 USER_DEBUG [4]|DEBUG|Label: Marketing\n```\n\n---\n\nGet sObjects names.\n\n```java\nMap\u003cString, Schema.SObjectType\u003e sObjectsMap = Schema.getGlobalDescribe();\n\nfor (String key : sObjectsMap.keySet()) {\n\tSchema.DescribeSObjectResult result = sObjectsMap.get(key).getDescribe();\n\tSystem.debug('SObject name: ' + result.getName());\n}\n```\n\n```console\n04:05:09:071 USER_DEBUG [5]|DEBUG|SObject name: OpportunityStage\n04:05:09:071 USER_DEBUG [5]|DEBUG|SObject name: LeadStatus\n04:05:09:072 USER_DEBUG [5]|DEBUG|SObject name: CaseStatus\n```\n\n---\n\nGet picklist entries.\n\n```java\nSchema.DescribeFieldResult describeFieldResult = Account.Industry.getDescribe();\nList\u003cSchema.PicklistEntry\u003e picklistEntries = describeFieldResult.getPicklistValues();\n\nfor (Schema.PicklistEntry picklistEntry : picklistEntries) {\n    System.debug('Picklist entry: ' + picklistEntry);\n}\n```\n```console\n10:38:41:007 USER_DEBUG [5]|DEBUG|Picklist entry: Schema.PicklistEntry[getLabel=Consulting;getValue=Consulting;isActive=true;isDefaultValue=false;]\n10:38:41:007 USER_DEBUG [5]|DEBUG|Picklist entry: Schema.PicklistEntry[getLabel=Education;getValue=Education;isActive=true;isDefaultValue=false;]\n10:38:41:007 USER_DEBUG [5]|DEBUG|Picklist entry: Schema.PicklistEntry[getLabel=Electronics;getValue=Electronics;isActive=true;isDefaultValue=false;]\n```\n\n---\n\nGet type of sObject.\n\n```java\nSchema.DescribeSObjectResult describeResult = Account.sObjectType.getDescribe();\nSystem.debug('SObject type: ' + describeResult.getSObjectType());\n```\n\n```console\n11:08:36:015 USER_DEBUG [2]|DEBUG|SObject type: Account\n```\n\n# #8 Apex data types.\n\nPrimitive data types in Apex.\n\n\u003ccenter\u003e\n    \u003ctable\u003e\n        \u003ctr\u003e\n            \u003cth\u003eData Type\u003c/th\u003e\n            \u003cth\u003eDescription\u003c/th\u003e\n            \u003cth\u003eUsage example\u003c/th\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eBlob\u003c/td\u003e\n            \u003ctd\u003e-\u003c/td\u003e\n            \u003ctd\u003e\n            String myString = 'StringToBlob';\n            Blob myBlob = Blob.valueof(myString);\n            System.assertEquals('StringToBlob', myBlob.toString());\n            \u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eBoolean\u003c/td\u003e\n            \u003ctd\u003e-\u003c/td\u003e\n            \u003ctd\u003eBoolean isValid = true;\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eDate\u003c/td\u003e\n            \u003ctd\u003e-\u003c/td\u003e\n            \u003ctd\u003eDate myDate = Date.newInstance(2022, 2, 18); // 2022-02-18 00:00:00\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eDateTime\u003c/td\u003e\n            \u003ctd\u003e-\u003c/td\u003e\n            \u003ctd\u003eDateTime myDateTime = DateTime.newInstance(1999, 2, 11, 8, 6, 16); // 2022-02-18 13:29:15\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eDecimal\u003c/td\u003e\n            \u003ctd\u003e-\u003c/td\u003e\n            \u003ctd\u003eDecimal phi = 1.618033;\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eDouble\u003c/td\u003e\n            \u003ctd\u003e-\u003c/td\u003e\n            \u003ctd\u003eDouble pi = 3.14159;\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eId\u003c/td\u003e\n            \u003ctd\u003e-\u003c/td\u003e\n            \u003ctd\u003e0017Q000008Yo6JQAS\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eInteger\u003c/td\u003e\n            \u003ctd\u003e-\u003c/td\u003e\n            \u003ctd\u003eInteger count = 15;\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eLong\u003c/td\u003e\n            \u003ctd\u003e-\u003c/td\u003e\n            \u003ctd\u003eLong amount = 1337;\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eObject\u003c/td\u003e\n            \u003ctd\u003e-\u003c/td\u003e\n            \u003ctd\u003eObject color = 'Red';\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eString\u003c/td\u003e\n            \u003ctd\u003e-\u003c/td\u003e\n            \u003ctd\u003eString name = 'Adrian';\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003eTime\u003c/td\u003e\n            \u003ctd\u003e-\u003c/td\u003e\n            \u003ctd\u003eTime myTime = Time.newInstance(13, 47, 35, 570); // 13:47:35.570Z\u003c/td\u003e\n        \u003c/tr\u003e\n    \u003c/table\u003e\n\u003c/center\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartysta%2Fsalesforce-notes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fartysta%2Fsalesforce-notes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartysta%2Fsalesforce-notes/lists"}