Transitioning from RForcecom

Steven M. Mortimer

2021-07-03

While writing the {salesforcer} package we were keenly aware that many folks were already using the {RForcecom} package to connect to Salesforce. In order to foster adoption and switching between the packages {salesforcer} replicates the functionality of many {RForcecom} functions so that you will only need to swap out library(RForcecom) for library(salesforcer) and still have production scripts perform as expected.

RForcecom Removed from CRAN

As of June 9, 2021, the {RForcecom} package was removed from CRAN. You can still use it by installing from the archive, but we strongly recommend using {salesforcer} instead. The existing functionality in {RForcecom} has been further optimized within {salesforcer} and new functionality has been added too.

Authentication

{salesforcer} supports OAuth 2.0 authentication which is preferred, but for backward compatibility provides the username-password authentication routine implemented by {RForcecom}. Here is an example running the function from each of the packages side-by-side and producing the same result.

library(dplyr, warn.conflicts = FALSE)
library(salesforcer)
# the RForcecom way
# RForcecom::rforcecom.login(username, paste0(password, security_token), 
#                            apiVersion=getOption("salesforcer.api_version"))
# replicated in salesforcer package
session <- salesforcer::rforcecom.login(username, 
                                         paste0(password, security_token), 
                                         apiVersion = getOption("salesforcer.api_version"))
session['sessionID'] <- "{MASKED}"
session
#>                       sessionID                     instanceURL 
#>                      "{MASKED}" "https://na122.salesforce.com/" 
#>                      apiVersion 
#>                          "52.0"

Note that we must set the API version here because calls to session will not create a new sessionId and then we are stuck with version 35.0 (the default from RForcecom::rforcecom.login()). Some functions in {salesforcer} implement API calls that are only available after version 35.0.

CRUD Operations

“CRUD” operations (Create, Retrieve, Update, Delete) in the {RForcecom} package only operate on one record at a time. One benefit to using the {salesforcer} package is that these operations will accept a named vector (one record) or an entire data.frame or tbl_df of records to churn through. However, rest assured that the replicated functions behave exactly the same way if you are hesitant to making the switch.

object <- "Contact"
fields <- c(FirstName="Test", LastName="Contact-Create-Compatibility")

# the RForcecom way
# RForcecom::rforcecom.create(session, objectName=object, fields)

# replicated in salesforcer package
result <- salesforcer::rforcecom.create(session, objectName=object, fields)
result
#>                   id success
#> 1 0033s000017uOxTAAU    TRUE

Here is an example showing the reduction in code of using {salesforcer} if you would like to create multiple records.

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

# the RForcecom way (requires a loop)
# 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)
# }

# the better way in salesforcer to do multiple records
salesforcer_results <- sf_create(new_contacts, object_name="Contact")
salesforcer_results
#> # A tibble: 2 x 2
#>   id                 success
#>   <chr>              <lgl>  
#> 1 0033s000017uOxYAAU TRUE   
#> 2 0033s000017uOxZAAU TRUE

Query

{salesforcer} also has better printing and type-casting when returning query result thanks to features of the {readr} package.

this_soql <- "SELECT Id, Email FROM Contact LIMIT 5"

# the RForcecom way
# RForcecom::rforcecom.query(session, soqlQuery = this_soql)

# replicated in salesforcer package
result <- salesforcer::rforcecom.query(session, soqlQuery = this_soql)
result
#> # A tibble: 5 x 1
#>   Id                
#>   <chr>             
#> 1 0033s000013wlpjAAA
#> 2 0033s000013wlpkAAA
#> 3 0033s000014CRFoAAO
#> 4 0033s000014CRFpAAO
#> 5 0033s000014AG4HAAW

# the better way in salesforcer to query
salesforcer_results <- sf_query(this_soql)
salesforcer_results
#> # A tibble: 5 x 1
#>   Id                
#>   <chr>             
#> 1 0033s000013wlpjAAA
#> 2 0033s000013wlpkAAA
#> 3 0033s000014CRFoAAO
#> 4 0033s000014CRFpAAO
#> 5 0033s000014AG4HAAW

Describe

The {RForcecom} package has the function rforcecom.getObjectDescription() which returns a data.frame with one row per field on an object. The same function in {salesforcer} is named sf_describe_object_fields() and also has better printing and datatype casting by using tibbles.

# the RForcecom way
# RForcecom::rforcecom.getObjectDescription(session, objectName='Account')

# backwards compatible in the salesforcer package
result <- salesforcer::rforcecom.getObjectDescription(session, objectName='Account')

# the better way in salesforcer to get object fields
result2 <- salesforcer::sf_describe_object_fields('Account')
result2
#> # A tibble: 68 x 39
#>   aggregatable aiPredictionField autoNumber byteLength calculated caseSensitive
#>   <chr>        <chr>             <chr>      <chr>      <chr>      <chr>        
#> 1 true         false             false      18         false      false        
#> 2 false        false             false      0          false      false        
#> 3 true         false             false      18         false      false        
#> 4 true         false             false      765        false      false        
#> 5 true         false             false      765        false      false        
#> # … with 63 more rows, and 33 more variables: compoundFieldName <chr>,
#> #   createable <chr>, custom <chr>, defaultValue <list>,
#> #   defaultedOnCreate <chr>, deprecatedAndHidden <chr>, digits <chr>,
#> #   externalId <chr>, extraTypeInfo <chr>, filterable <chr>, groupable <chr>,
#> #   idLookup <chr>, label <chr>, length <chr>, name <chr>, nameField <chr>,
#> #   namePointing <chr>, nillable <chr>, permissionable <chr>,
#> #   picklistValues <list>, polymorphicForeignKey <chr>, precision <chr>,
#> #   queryByDistance <chr>, referenceTo <chr>, relationshipName <chr>,
#> #   restrictedPicklist <chr>, scale <chr>, searchPrefilterable <chr>,
#> #   soapType <chr>, sortable <chr>, type <chr>, unique <chr>, updateable <chr>