salesforcer

Overview

The salesforcer package is the R implementation of the Salesforce platform APIs. I created the package to build upon the functionality provided in the RForcecom package by including calls from more APIs (REST, SOAP, Bulk, and Metadata) along with following more tidy data principles when wrangling data from the APIs.

GitHub - salesforcer View source code on GitHub at: https://github.com/StevenMMortimer/salesforcer

Features

This R package supports a variety of Salesforce API operations and more will be added. Package features include:

  • OAuth 2.0 (Single Sign On) and Basic (Username-Password) Authentication methods (sf_auth())
  • CRUD (Create, Retrieve, Update, Delete) methods for records using the SOAP, REST, and Bulk APIs
  • Query records via the SOAP, REST, and Bulk 1.0 APIs using sf_query()
  • Retrieve and modify metadata (Custom Objects, Fields, etc.) using the Metadata API with:
    • sf_describe_objects(), sf_create_metadata(), sf_update_metadata()
  • Utilize backwards compatible functions for the RForcecom package, such as:
    • rforcecom.login(), rforcecom.query(), rforcecom.create(), rforcecom.update()
  • Basic utility calls (sf_user_info(), sf_server_timestamp(), sf_list_objects())

Quickstart Guide

Install salesforcer Library

# install from CRAN
install.packages("salesforcer")

# or get the latest version available on GitHub using the devtools package
# install.packages("devtools")
devtools::install_github("StevenMMortimer/salesforcer")

Authentication

The salesforcer package provides two methods to authenticate:

  1. OAuth 2.0
  2. Basic Username-Password with Security Token

It is recommended to use OAuth 2.0 so that passwords do not have to be shared or embedded within scripts. User credentials will be stored in locally cached file entitled “.httr-oauth-salesforcer” in the current working directory. These credentials will be passed to the API for each call and refreshed if necessary.

library(tidyverse)
library(salesforcer)

# Using OAuth 2.0 authentication
sf_auth()

# Using Basic Username-Password authentication
sf_auth(username = "test@gmail.com", 
        password = "{PASSWORD_HERE}",
        security_token = "{SECURITY_TOKEN_HERE}")

Support for Multiple APIs

The salesforcer package is unique in that it provides a common interface to all of the REST, SOAP, and Bulk APIs. If you would like to create a set of records, then you would use the same R function, sf_create(), in the same way and just change the function argument called api_type.

# create a dataset of 2 new contacts to create
n <- 2
new_contacts <- tibble(FirstName = rep("Test", n),
                       LastName = paste0("Contact-Create-", 1:n))

# using the REST API
rest_records <- sf_create(new_contacts, object_name="Contact", api_type="REST")
rest_records
#> # A tibble: 2 x 3
#>   id                 success errors    
#>   <chr>              <lgl>   <list>    
#> 1 0033s00000zLbIqAAK TRUE    <list [0]>
#> 2 0033s00000zLbIrAAK TRUE    <list [0]>

# using the REST API
soap_records <- sf_create(new_contacts, object_name="Contact", api_type="SOAP")
soap_records
#> # A tibble: 2 x 2
#>   id                 success
#>   <chr>              <lgl>  
#> 1 0033s00000zLbIvAAK TRUE   
#> 2 0033s00000zLbIwAAK TRUE

# using the Bulk 1.0 API
bulk_records <- sf_create(new_contacts, object_name="Contact", api_type="Bulk 1.0")
bulk_records
#> # A tibble: 2 x 4
#>   Id                 Success Created Error
#>   <chr>              <lgl>   <lgl>   <lgl>
#> 1 0033s00000zLbJ0AAK TRUE    TRUE    NA   
#> 2 0033s00000zLbJ1AAK TRUE    TRUE    NA

This form is especially useful when switching between the REST and Bulk APIs where if you start dealing with larger and larger datasets, then you can switch code easily to the Bulk API. Note: The Bulk 2.0 API does NOT guarantee the order of the data submitted is preserved in the output that is returned. This means that you must join on other data columns to match up the IDs that are returned in the output with the data you submitted. For this reason, Bulk 2.0 may not be a good solution for creating, updating, or upserting records where you need to keep track of the created IDs. The Bulk 2.0 API would be fine for deleting records where you only need to know which IDs were successfully deleted.

Support for Metadata

The salesforcer package also supports the creating, editing, and deleting of Salesforce metadata. Metadata refers to the configuration of “objects” within Salesforce. An object could be a standard or new type of record. For example, the Metadata API can configuring which fields are captured on an Account or whether they should be displayed on the page. Even more complex configurations, such as, workflows and triggers can be configured using the Metadata API. The support for the Metadata API in salesforcer is experimental, so please use at your own risk and consult the appropriate Salesforce resources when trying to use. One common use case for the Metadata API is retrieving information about an object (fields, permissions, etc.). You can use the sf_read_metadata() function to return a list of objects and their metadata. In the example below we retrieve the metadata for the Account and Contact objects. Note that the metadata_type argument is “CustomObject”. Standard Objects are an implementation of CustomObjects, so they are returned using that metadata type.

read_obj_result <- sf_read_metadata(metadata_type='CustomObject',
                                    object_names=c('Account', 'Contact'))
read_obj_result[[1]][c('fullName', 'label', 'sharingModel', 'enableHistory')]
#> $fullName
#> [1] "Account"
#> 
#> $label
#> [1] "Account"
#> 
#> $sharingModel
#> [1] "ReadWrite"
#> 
#> $enableHistory
#> [1] "false"
first_two_fields_idx <- head(which(names(read_obj_result[[1]]) == 'fields'), 2)
# show the first two returned fields of the Account object
read_obj_result[[1]][first_two_fields_idx]
#> $fields
#> $fields$fullName
#> [1] "AccountNumber"
#> 
#> $fields$trackFeedHistory
#> [1] "false"
#> 
#> 
#> $fields
#> $fields$fullName
#> [1] "AccountSource"
#> 
#> $fields$trackFeedHistory
#> [1] "false"
#> 
#> $fields$type
#> [1] "Picklist"

The data is returned as a list because object definitions are highly nested representations. You may notice that we are missing some really specific details, such as, the picklist values of a field with type “Picklist”. You can get that information using sf_describe_object_fields(). Here is an example using sf_describe_object_fields() where we get a tbl_df with one row for each field on the Account object:

acct_fields <- sf_describe_object_fields('Account')
acct_fields %>% select(name, label, length, soapType, type)
#> # A tibble: 67 x 5
#>    name              label                   length soapType    type     
#>    <chr>             <chr>                   <chr>  <chr>       <chr>    
#>  1 Id                Account ID              18     tns:ID      id       
#>  2 IsDeleted         Deleted                 0      xsd:boolean boolean  
#>  3 MasterRecordId    Master Record ID        18     tns:ID      reference
#>  4 Name              Account Name            255    xsd:string  string   
#>  5 Type              Account Type            40     xsd:string  picklist 
#>  6 ParentId          Parent Account ID       18     tns:ID      reference
#>  7 BillingStreet     Billing Street          255    xsd:string  textarea 
#>  8 BillingCity       Billing City            40     xsd:string  string   
#>  9 BillingState      Billing State/Province  80     xsd:string  string   
#> 10 BillingPostalCode Billing Zip/Postal Code 20     xsd:string  string   
#> # … with 57 more rows
# show the picklist selection options for the Account Type field
acct_fields %>% 
  filter(label == "Account Type") %>% 
  .$picklistValues
#> [[1]]
#> # A tibble: 7 x 4
#>   active defaultValue label                      value                     
#>   <lgl>  <lgl>        <chr>                      <chr>                     
#> 1 TRUE   FALSE        Prospect                   Prospect                  
#> 2 TRUE   FALSE        Customer - Direct          Customer - Direct         
#> 3 TRUE   FALSE        Customer - Channel         Customer - Channel        
#> 4 TRUE   FALSE        Channel Partner / Reseller Channel Partner / Reseller
#> 5 TRUE   FALSE        Installation Partner       Installation Partner      
#> 6 TRUE   FALSE        Technology Partner         Technology Partner        
#> 7 TRUE   FALSE        Other                      Other

Extension of the RForcecom Package

The salesforcer package can be seen as an extension of the popular RForcecom package that originally introduced R users to the Salesforce APIs. However, there are a few key differences. First, salesforcer supports more APIs. It allows for the same methods to utilize either the REST or SOAP APIs. It also supports the newer version of the Bulk API called “Bulk 2.0” and a limited set of functionality from the Metadata API. There are future plans to incorporate the Reporting and Analytics APIs. Second, salesforcer makes it easier to submit multiple records at once by supplying a data.frame instead of a named vector.

n <- 2
new_contacts <- tibble(FirstName = rep("Test", n),
                       LastName = paste0("Contact-Create-", 1:n))

# the RForcecom way
rforcecom_results <- NULL
for(i in 1:nrow(new_contacts)){
  temp <- RForcecom::rforcecom.create(session, 
                                      objectName = "Contact", 
                                      fields = unlist(slice(new_contacts,i)))
  rforcecom_results <- bind_rows(rforcecom_results, temp)
}
rforcecom_results
#>                   id success
#> 1 0033s00000zLbJ5AAK    true
#> 2 0033s00000zLbJAAA0    true

# the salesforcer way
salesforcer_results <- salesforcer::sf_create(new_contacts, object_name="Contact")
salesforcer_results
#> # A tibble: 2 x 2
#>   id                 success
#>   <chr>              <lgl>  
#> 1 0033s00000zLbJFAA0 TRUE   
#> 2 0033s00000zLbJGAA0 TRUE

Finally, the package is meant to adhere to principles outlined by the tidyverse package. Function names are snake case and data is returned as tbl_df objects. Do not worry if you are fully utilizing the RForcecom package. Many of those functions are included in the salesforcer package so you can drop the RForcecom dependency but not have to change all of your code at once.

Check out the Tests

The salesforcer package has quite a bit of unit test coverage to track any code changes and ensure package reliabilty. These tests are an excellent source of examples because they cover most all cases of utilizing the package functions.

For example, if you are not sure on how to upsert records using the Bulk API just check out the tests at https://github.com/StevenMMortimer/salesforcer/blob/master/tests/testthat/test-bulk.R.

The test-bulk.R script walks through a process of creating, searching, querying, updating, upserting, and deleting the same set of records, so it is a concise set of code for manipulating records via the Bulk API. Similar test scripts exist for the REST and SOAP APIs as well.

Credits

This application uses other open source software components. The authentication components are mostly verbatim copies of the routines established in the googlesheets package (https://github.com/jennybc/googlesheets). Methods are inspired by the RForcecom package (https://github.com/hiratake55/RForcecom). We acknowledge and are grateful to these developers for their contributions to open source.

License

The salesforcer package is licensed under the MIT License (http://choosealicense.com/licenses/mit/)

Questions

If you have further questions please submit them via email or issue on GitHub at https://github.com/StevenMMortimer/salesforcer/issues. Thank you!

Support

This project was made with love and coffee. Studies show that I write R code 3x faster after drinking one cup coffee and that productivity scales linearly. Imagine what I could accomplish if you bought me 10 cups of coffee…

Thank you for your support!