salesforce.com adapter

Using the salesforce.com adapter

So this is seeing the salesforce.com adapter in action:

I have attached a zip file with source code and sample data for the To Translator component for this channel which includes the salesforce.com adapter here: SalesforceTranslator.zip

Now the great thing about this adapter is that it’s really snappy. That’s because of the SQLite caching. It’s interesting to try turning setting the clear_cache flag to true. It’s like night and day in terms of the responsiveness of the editor. With caching it’s blindingly fast and easy to do your work.

Caching does have some drawbacks – I picked a default time of 60 seconds. Cached data can be out of date – if you don’t keep it in mind it could result in unexpected results like if you do a query of data after deleting an object then it will still be there. I made it possible to override the default cache time for each query call into the adapter.

Another really neat thing about the salesforce.com module is that once you take away the meta definitions for each of the salesforce objects is that it is less than 300 lines of code. The meta definitions themselves make up the majority of the code. It makes the whole thing a breeze to maintain since there is so little code and any changes in the salesforce.com objects can easily expressed by updating the meta defintions. Beautiful!

You will need to spend some time making your comfortable with the salesforce.com data model. One interesting tit-bit I found useful was this little line:

local QueueList = S:groupList{where=“Type = ‘Queue’”}

This gives you a list of all the user defined ticket “queues” which is a workflow concept used a lot in salesforce.com. It’s not 100% obvious from the API, but I find with salesforce.com there is a lot of good information one can get via Google for these types of questions. One page I would recommend bookmarking is the object model of salesforce.com which I found had great information.

So to finish things off I will walk through how we do the CRUD operations with this API and also how to get a fresh copy of the meta data to modify, add or remove which of the salesforce.com objects we want to expose. In general I think with most salesforce.com implementations most companies will select a subset of salesforce.com to work with – it’s the old 80-20 rule.

So doing queries with this API is very convenient. The “List” methods take the optional arguments of:

  1. limit – this is the maximum number of records to return.
  2. where – this is SOQL (SQL) like WHERE clause to use to filter the results.
  3. cache – this allows one to override the default time of 60 seconds to another time threshold – or 0 for no caching.

I guess one could easily enhance my implementation to add an optional order by clause too. This screenshot shows the results coming back from a list of contacts:

Notice the custom fields “MRN__c” and “Languages__c”?

Creating and updating records is done using the xxxxModify{} methods. A new record will be created if the “id” field is not present, if “id” is present than an existing object will be updated. salesforce.com uses unique GUIDs for every object in the database.

The modify methods are really easy to use since the properties of the object are all expressed as optional parameters. This screenshot gives you an idea of how it works:

And delete methods are really obvious – xxxxDelete{id=<id of object to delete>}. Both the modify and delete methods support the “live” flag. By default in the editor the live flag is set to false since that would alter the salesforce.com application in the editor which one usually doesn’t want.

Now I deliberately left out a lot of the salesforce.com objects since I think many are probably not going used by most organizations. But it’s dead easy to add them. To do it just use the handy apiDefinition method I have supplied. If you were to do say, S:apiDefinition(‘Idea’) then this is what you would get back from that method:

objectDefs.idea = {object='Idea', fields={
                      Categories="?",
                      LastModifiedDate="?",
                      LastCommentDate="?",
                      CreatedDate="?",
                      Body="?",
                      LastCommentId="?",
                      CreatedById="?",
                      LastModifiedById="?",
                      Status="?",
                      SystemModstamp="?",
                      IsHtml="?",
                      NumComments="?",
                      ParentIdeaId="?",
                      RecordTypeId="?",
                      IsLocked="?",
                      Title="?",
                      CommunityId="?",
                      VoteTotal="?",
                      VoteScore="?",
                      IsDeleted="?"}}

Notice how the descriptions of the parameters come up as ‘?’ This is because the API that salesforce.com does supply the description of for the fields of each object. You’ll have to enter that by hand – still it’s not all that onerous to do that and I find the names that salesforce.com selected were all pretty easy to understand.

I put some smarts into the code so that if the existing meta definitions have a description of a parameter that the apiDefinition method re-uses those descriptions which is nice feature to have. So if you put descriptions in for fields you won’t lose them if you refresh the definition.

If you have any questions or comments about the code and the techniques it uses do feel free to ask. This is my first draft of the article too, so if you spot any typos then let me know. Here’s the salesforce module:

salesforce = {}

store = require 'store'

objectDefs = {}

objectDefs.contact = {object='Contact', fields={
                      OtherPhone="Alternative phone number",
                      FirstName="First name of contact",
                      AssistantPhone="Their secretary's phone",
                      MailingState="State mailing address",
                      MRN__c="MRN number",
                      Salutation="How to greet them",
                      Languages__c="Languages they speak",
                      Phone="Phone number of the contact",
                      Email="Email of contact",
                      AccountId="AccountID of the contact",
                      Birthdate="Birthdate of the contact",
                      Description="Description of the contact",
                      HomePhone="Home phone number",
                      LastName="Last name of contact",
                      LastActivityDate="Last activity date",
                      CreatedById="Who created this",
                      LastModifiedDate="When last modified",
                      LastModifiedById="Who modified this",
                      CreatedDate="Date created",
                      MailingPostalCode="Postal code for mailing address",
                      Department="What department",
                      MobilePhone="Their mobile phone",
                      MailingCity="City to mail to.",
                      MailingStreet="Street to mail to",
                      OwnerId="Owner of this contact",
                      IsDeleted="Has this contact been deleted",
                      Fax="FAX number",
                      Title="Title of contact - CEO etc."
                      }}

objectDefs.case = {object='Case', fields={
                      SuppliedPhone="Supplied phone number",
                      Description="Description",
                      Origin="Origin of the phone",
                      CreatedDate="When created",
                      IsEscalated="Has this been escalated",
                      ClosedDate="When closed",
                      Reason="Reason for ticket",
                      CaseNumber="Case number",
                      CreatedById="Who created it",
                      LastModifiedById="?",
                      Status="?",
                      Priority="?",
                      SuppliedEmail="?",
                      SuppliedName="?",
                      ParentId="?",
                      ContactId="?",
                      OwnerId="?",
                      IsDeleted="?",
                      Subject="?",
                      SystemModstamp="?",
                      LastModifiedDate="?",
                      IsClosed="?",
                      SuppliedCompany="?",
                      AccountId="?"}}

objectDefs.queueSobject = {object='QueueSobject', fields={
                      SystemModstamp="A field",
                      QueueId="A field",
                      SobjectType="A field",
                      CreatedById="A field"}}


objectDefs.group = {object='Group', fields={
                      Type="A field",
                      LastModifiedDate="A field",
                      CreatedDate="A field",
                      DoesIncludeBosses="A field",
                      Email="A field",
                      OwnerId="A field",
                      Name="A field",
                      LastModifiedById="A field",
                      RelatedId="A field",
                      SystemModstamp="A field",
                      DoesSendEmailToMembers="A field",
                      CreatedById="A field"}}

objectDefs.note = {object='Note', fields={
                      IsPrivate="A field",
                      LastModifiedDate="A field",
                      CreatedDate="A field",
                      ParentId="A field",
                      Body="A field",
                      OwnerId="A field",
                      IsDeleted="A field",
                      CreatedById="A field",
                      LastModifiedById="A field",
                      SystemModstamp="A field",
                      Title="A field"}}

objectDefs.community = {object='Community', fields={
                      IsActive="A field",
                      Description="A field",
                      CreatedById="A field",
                      LastModifiedById="A field",
                      CreatedDate="A field",
                      Name="A field",
                      SystemModstamp="A field",
                      LastModifiedDate="A field"}}

objectDefs.contentDocument = {object='ContentDocument', fields={
                      LastModifiedDate="A field",
                      CreatedDate="A field",
                      LatestPublishedVersionId="A field",
                      OwnerId="A field",
                      IsDeleted="A field",
                      Title="A field",
                      LastModifiedById="A field",
                      PublishStatus="A field",
                      SystemModstamp="A field",
                      CreatedById="A field"}}

objectDefs.event = {object='Event', fields={
                      IsPrivate="A field",
                      StartDateTime="A field",
                      IsArchived="A field",
                      CreatedDate="A field",
                      IsAllDayEvent="A field",
                      ActivityDate="A field",
                      CreatedById="A field",
                      RecurrenceInstance="A field",
                      IsGroupEvent="A field",
                      RecurrenceEndDateOnly="A field",
                      ActivityDateTime="A field",
                      WhatId="A field",
                      RecurrenceActivityId="A field",
                      EndDateTime="A field",
                      Subject="A field",
                      Location="A field",
                      IsChild="A field",
                      AccountId="A field",
                      RecurrenceType="A field",
                      Description="A field",
                      IsRecurrence="A field",
                      WhoId="A field",
                      RecurrenceDayOfWeekMask="A field",
                      RecurrenceStartDateTime="A field",
                      SystemModstamp="A field",
                      LastModifiedById="A field",
                      ReminderDateTime="A field",
                      RecurrenceDayOfMonth="A field",
                      DurationInMinutes="A field",
                      RecurrenceMonthOfYear="A field",
                      OwnerId="A field",
                      IsDeleted="A field",
                      ShowAs="A field",
                      RecurrenceInterval="A field",
                      RecurrenceTimeZoneSidKey="A field",
                      IsReminderSet="A field",
                      LastModifiedDate="A field",
                      GroupEventType="A field"}}

objectDefs.user = {object='User', fields={
                      UserPreferencesApexPagesDeveloperMode="A field",
                      FirstName="A field",
                      Division="A field",
                      FederationIdentifier="A field",
                      Alias="A field",
                      UserPreferencesHideCSNDesktopTask="A field",
                      DelegatedApproverId="A field",
                      EmailEncodingKey="A field",
                      LastLoginDate="A field",
                      UserType="A field",
                      OfflineTrialExpirationDate="A field",
                      UserPermissionsKnowledgeUser="A field",
                      CallCenterId="A field",
                      EmployeeNumber="A field",
                      City="A field",
                      LastModifiedById="A field",
                      UserPermissionsInteractionUser="A field",
                      CommunityNickname="A field",
                      MobilePhone="A field",
                      Extension="A field",
                      ContactId="A field",
                      State="A field",
                      UserRoleId="A field",
                      SmallPhotoUrl="A field",
                      Fax="A field",
                      UserPermissionsOfflineUser="A field",
                      UserPermissionsMobileUser="A field",
                      UserPreferencesEventRemindersCheckboxDefault="A field",
                      ForecastEnabled="A field",
                      UserPreferencesHideCSNGetChatterMobileTask="A field",
                      LastPasswordChangeDate="A field",
                      UserPreferencesDisableAutoSubForFeeds="A field",
                      Country="A field",
                      Phone="A field",
                      DigestFrequency="A field",
                      Email="A field",
                      UserPreferencesActivityRemindersPopup="A field",
                      ReceivesInfoEmails="A field",
                      AboutMe="A field",
                      UserPermissionsCallCenterAutoLogin="A field",
                      CompanyName="A field",
                      LastModifiedDate="A field",
                      ReceivesAdminInfoEmails="A field",
                      LastName="A field",
                      Street="A field",
                      Username="A field",
                      FullPhotoUrl="A field",
                      CurrentStatus="A field",
                      ProfileId="A field",
                      CreatedDate="A field",
                      AccountId="A field",
                      SystemModstamp="A field",
                      OfflinePdaTrialExpirationDate="A field",
                      Department="A field",
                      CreatedById="A field",
                      UserPreferencesOptOutOfTouch="A field",
                      PostalCode="A field",
                      UserPreferencesReminderSoundOff="A field",
                      LanguageLocaleKey="A field",
                      IsActive="A field",
                      UserPermissionsSupportUser="A field",
                      LocaleSidKey="A field",
                      UserPreferencesTaskRemindersCheckboxDefault="A field",
                      UserPermissionsSFContentUser="A field",
                      TimeZoneSidKey="A field",
                      ManagerId="A field",
                      UserPermissionsMarketingUser="A field",
                      Title="A field",
                      Name="A field"}}

objectDefs.product2 = {object='Product2', fields={
                      Description="A field",
                      CreatedDate="A field",
                      ProductCode="A field",
                      IsActive="A field",
                      IsDeleted="A field",
                      Name="A field",
                      LastModifiedById="A field",
                      Family="A field",
                      CreatedById="A field",
                      SystemModstamp="A field",
                      LastModifiedDate="A field"}}

objectDefs.taskStatus = {object='TaskStatus', fields={
                      LastModifiedDate="A field",
                      CreatedDate="A field",
                      MasterLabel="A field",
                      SortOrder="A field",
                      CreatedById="A field",
                      LastModifiedById="A field",
                      IsDefault="A field",
                      IsClosed="A field",
                      SystemModstamp="A field"}}



objectDefs.document = {object='Document', fields={
                      Description="A field",
                      CreatedDate="A field",
                      Body="A field",
                      NamespacePrefix="A field",
                      LastModifiedById="A field",
                      Url="A field",
                      SystemModstamp="A field",
                      AuthorId="A field",
                      BodyLength="A field",
                      DeveloperName="A field",
                      Keywords="A field",
                      LastModifiedDate="A field",
                      CreatedById="A field",
                      IsInternalUseOnly="A field",
                      IsDeleted="A field",
                      Name="A field",
                      Type="A field",
                      IsPublic="A field",
                      FolderId="A field",
                      ContentType="A field",
                      IsBodySearchable="A field"}}

objectDefs.taskPriority = {object='TaskPriority', fields={
                      LastModifiedDate="A field",
                      CreatedDate="A field",
                      MasterLabel="A field",
                      SortOrder="A field",
                      CreatedById="A field",
                      LastModifiedById="A field",
                      IsHighPriority="A field",
                      SystemModstamp="A field",
                      IsDefault="A field"}}


objectDefs.idea = {object='Idea', fields={
                      Categories="?",
                      LastModifiedDate="?",
                      LastCommentDate="?",
                      CreatedDate="?",
                      Body="?",
                      LastCommentId="?",
                      CreatedById="?",
                      LastModifiedById="?",
                      Status="?",
                      SystemModstamp="?",
                      IsHtml="?",
                      NumComments="?",
                      ParentIdeaId="?",
                      RecordTypeId="?",
                      IsLocked="?",
                      Title="?",
                      CommunityId="?",
                      VoteTotal="?",
                      VoteScore="?",
                      IsDeleted="?"}}

objectDefs.task = {object='Task', fields={
                      IsArchived="A field",
                      CreatedDate="A field",
                      ActivityDate="A field",
                      CreatedById="A field",
                      RecurrenceInstance="A field",
                      Priority="A field",
                      RecurrenceStartDateOnly="A field",
                      CallType="A field",
                      WhatId="A field",
                      CallDurationInSeconds="A field",
                      Subject="A field",
                      ReminderDateTime="A field",
                      RecurrenceInterval="A field",
                      AccountId="A field",
                      RecurrenceType="A field",
                      Description="A field",
                      IsRecurrence="A field",
                      WhoId="A field",
                      RecurrenceDayOfWeekMask="A field",
                      CallObject="A field",
                      Status="A field",
                      SystemModstamp="A field",
                      RecurrenceTimeZoneSidKey="A field",
                      RecurrenceDayOfMonth="A field",
                      LastModifiedDate="A field",
                      RecurrenceMonthOfYear="A field",
                      CallDisposition="A field",
                      IsDeleted="A field",
                      LastModifiedById="A field",
                      IsClosed="A field",
                      RecurrenceEndDateOnly="A field",
                      IsReminderSet="A field",
                      RecurrenceActivityId="A field",
                      OwnerId="A field"}}

objectDefs.account = {object='Account', fields={
                      NumberofLocations__c="A field",
                      CreatedDate="A field",
                      UpsellOpportunity__c="A field",
                      Industry="A field",
                      BillingCountry="A field",
                      AnnualRevenue="A field",
                      Sic="A field",
                      Type="A field",
                      TickerSymbol="A field",
                      Phone="A field",
                      ShippingStreet="A field",
                      NumberOfEmployees="A field",
                      SLA__c="A field",
                      AccountNumber="A field",
                      Description="A field",
                      ShippingPostalCode="A field",
                      BillingCity="A field",
                      ShippingCity="A field",
                      BillingState="A field",
                      SLASerialNumber__c="A field",
                      BillingPostalCode="A field",
                      LastModifiedById="A field",
                      Active__c="A field",
                      CustomerPriority__c="A field",
                      SystemModstamp="A field",
                      Website="A field",
                      LastModifiedDate="A field",
                      CreatedById="A field",
                      ShippingState="A field",
                      Site="A field",
                      MasterRecordId="A field",
                      Rating="A field",
                      ParentId="A field",
                      LastActivityDate="A field",
                      OwnerId="A field",
                      IsDeleted="A field",
                      Name="A field",
                      Ownership="A field",
                      ShippingCountry="A field",
                      Fax="A field",
                      SLAExpirationDate__c="A field",
                      BillingStreet="A field"}}

 
local function GetCache(Key, CacheTimeout)
   if (CacheTimeout == 0) then
      return nil
   end
   local CacheTime = store.get(Key.."T")
   if (os.ts.difftime(os.ts.time(), CacheTime) < CacheTimeout) then       
       local CachedData = store.get(Key)
       local R = json.parse{data=CachedData}
       return R
    end
    return nil
end 

local function PutCache(Key, Value)
   store.put(Key, Value)
   store.put(Key.."T", os.ts.time())
end
 
-- We only cache in development 
local function cachedHttpGet(T)
   local R, Key
   if iguana.isTest() then
      Key = T.url;       
      for K,V in pairs(T.parameters) do
          Key = Key..K..V
      end
      trace(Key) 
      R = GetCache(Key, T.cache or 60)
      if (R) then return R end
   end    
   T.cache = nil;
   local P = net.http.get(T)
   R = json.parse{data=P}
   if iguana.isTest() then
      PutCache(Key, P)
   end
   return R
end 

local function GetAccessTokenViaHTTP(CacheKey,T)
   local Url = 'https://login.salesforce.com/services/oauth2/token'
   local Auth = {grant_type = 'password',
                 client_id = T.consumer_key,
                 client_secret = T.consumer_secret,
                 username = T.username,       
                 password = T.password}
   local J = net.http.post{url=Url,
          parameters = Auth,
          live=true}
   PutCache(CacheKey, J)
   local AccessInfo = json.parse(J)
   return AccessInfo 
end 

local function CheckClearCache(DoClear)
   if DoClear then
      store.resetTableState()
   end 
end

local salesmethods = {} 
local MetaTable = {} 
MetaTable.__index = salesmethods;

function salesforce.connect(T)
   CheckClearCache(T.clear_cache)
   local P = GetCache(T.consumer_key, 1800) or
             GetAccessTokenViaHTTP(T.consumer_key, T) 
   setmetatable(P, MetaTable)
   return P 
end 

local helpinfo = {} HelpConnect = [[{"SeeAlso":[{"Title":"Salesforce","Link":"http://www.salesforce.com"}],
                 "Returns":[{"Desc":"The salesforce.com website."}],                 "Title":"salesforce.connect",
         "Parameters":[{"username":{"Desc":"User ID to login with."}},                         {"password":{"Desc":"Password of that user ID"}}
        {"consumer_key":{"Desc":"Consumer key for this connected app."}},                      {"consumer_secret":{"Desc":"Consumer secret for this connected app."}},                {"clear_cache":{"Opt" : true,"Desc":"If this is set to true then then the SQLite cache used to improve performance will be cleared."}},],
        "ParameterTable": true,          
        "Usage":" local C = salesforce.connect{clear_cache=false,
                       username='sales@interfaceware.com',
                       password='mypassword',
                       consumer_secret='585519048400883388',
                       consumer_key='3MVG9KI2HHAq33RyfdfRmZyEybpy7b_bZtwCyJW7e._mxrVtsrbM.g5n3.fIwK3vPGRl2Ly2u7joju3yYpPeO' }",
          "Desc":"Returns a connection object to salesforce instance"}]]

help.set{input_function=salesforce.connect, help_data=json.parse{data=HelpConnect}} 

local function ParseResult(Returned)
   if #Returned == 0 then
      return {}
   end 
   local R = json.parse{data=Returned}
   if #R > 0 and R[1].errorCode then
      error(R[1].message,4)
   end
   return R
end

local function patchObject(S, T, ObjectName)
   local Live = not iguana.isTest() or T.live
   local Path = S.instance_url..
       '/services/data/v20.0/sobjects/'..ObjectName..'/'
   local Method
   if (T.id) then
      trace("Updating");
      Method = 'PATCH'
      Path = Path..T.id
      T.id = nil;
   else  
      trace("New record");
      Method = 'POST'
   end
   trace(Path)
   T.live = nil;
   local Headers={}
   Headers['Content-Type']='application/json'
   Headers.Authorization ="Bearer ".. S.access_token 
   local Returned = net.http.put{data=json.serialize{data=T}, method=Method,headers=Headers, 
      url=Path,live=Live}
   return ParseResult(Returned)
end


function salesmethods:describe(Object)
   local S = self;
   local Url = S.instance_url..'/services/data/v20.0/sobjects/'..Object..'/describe/'
   trace(Url)
   local Headers={}
   Headers['Content-Type']='application/json'
   Headers.Authorization ="Bearer ".. S.access_token 
   return cachedHttpGet({headers=Headers, live=true, url=Url, parameters={}}, 50000)      
end

-- Used to generate API set
local function PrettyPrint(List, Name)
   local Def = List[Name]
   local R = "objectDefs."..Name.." = {"
   R = R.."object='"..Def.object.."', fields={\n"   
   for K,V in pairs(Def.fields) do
      R = R..'                      '..K..'="'..V..'",\n'
   end
   R = R:sub(1, #R-2).."}}\n\n"
   return R
end

function ObjectName(Name)
   return Name:sub(1,1):lower()..Name:sub(2)
end


local function GenerateAPI(S, Object)
   local Info = S:describe(Object)
   local CName = ObjectName(Object)
   local Def = {}
   Def.object = Object
   Def.fields ={}
   for i=1, #Info.fields do
      local Name = Info.fields[i].name
      trace(Name)
      trace(Info.fields[i])
      if Name ~= 'Id' then
         if (objectDefs[CName] and objectDefs[CName].fields[Name] ) then
            Def.fields[Name] = objectDefs[CName].fields[Name]
         else
            Def.fields[Name] = "?" 
         end
      end
   end
   return Def
end

-- Example objects QueueSobject, Account, Community, Contact, ContentDocument, Document, Product2, Event, Group, Note, Profile, Task, TaskPriority, TaskStatus, User
-- See https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_list.htm

function salesmethods.apiDefinition(S, Name)
   local Def = {}
   local DefName = ObjectName(Name)
   Def[DefName] = GenerateAPI(S, Name)
   return PrettyPrint(Def, DefName)
end

function GenerateModifierMethod(Name, Info)
   local FName = Name..'Modify'
   salesmethods[FName] = function (S,T) return patchObject(S, T, Info.object) end
   local F = salesmethods[FName]
   local Help = {}
   Help.Desc = "Create or update a "..Name
   Help.ParameterTable = true
   Help.Parameters = {}
   Help.Parameters[1] = {id={Opt=true, Desc="Unique id of "..Name..". If not present a new field will be created."}}
   Help.Parameters[2] = {live={Opt=true, Desc="Set to true to make this command work in the editor.  Default is false."}}
   for K,V in pairs(Info.fields) do
      Help.Parameters[#Help.Parameters+1] = {}
      Help.Parameters[#Help.Parameters][K] = {Opt=true, Desc=V}  
   end 
   help.set{input_function=F, help_data=Help}   
end

function queryObjects(S, T)
   if (T.where) then
      T.query = T.query.." WHERE "..T.where
   end
   if (T.limit) then
      T.query = T.query.." LIMIT "..T.limit
   end
   local P ={parameters={q=T.query}, url=S.instance_url..T.path,
             headers={Authorization="Bearer ".. S.access_token}, cache=T.cache, live=true}
   local R = cachedHttpGet(P)
   if #R > 0 and R[1].errorCode then
      CheckClearCache(true)
      error(R[1].message,4)
   end
   return R
end

local function selectQuery(T)
   local R = 'SELECT Id';
   for K,V in pairs(T.fields) do
      R = R..","..K
   end
   R = R.." FROM "..T.object
   return R
end

local function listObjects(S,T,D)
   T = T or {}
   T.query = selectQuery(D)
   T.path = '/services/data/v20.0/query' 
   return queryObjects(S,T)   
end

function GenerateListMethod(Name, Info)
   local FName = Name..'List'
   salesmethods[FName] = function(S,T) return listObjects(S,T,Info) end;
   local F = salesmethods[FName]
   local Help = {}
   Help.Desc = "Query list of "..Name
   Help.ParameterTable = true
   Help.Parameters = {}
   Help.Parameters[1] = {limit={Opt=true, Desc="Limit the number of results - default is no limit."}}
   Help.Parameters[2] = {where={Opt=true, Desc="Give a WHERE clause."}}
   Help.Parameters[3] = {cache={Opt=true, Desc="Specific time to cache results (seconds). Default is 60 seconds."}}
   help.set{input_function=F, help_data=Help}         
end

local function deleteObject(S, T, ObjectName)
   local Live = not iguana.isTest() or T.live
   local Path = S.instance_url..
       '/services/data/v20.0/sobjects/'..ObjectName..'/'..T.id
   local Headers={}
   Headers['Content-Type']='application/json'
   Headers.Authorization ="Bearer ".. S.access_token        
   local Returned = net.http.put{data=json.serialize{data=T}, method='DELETE',headers=Headers, 
      url=Path,live=Live}
   return ParseResult(Returned)   
end

function GenerateDeleteMethod(Name, Info)
   local FName = Name..'Delete'
   salesmethods[FName] = function (S,T) return deleteObject(S,T,Info.object) end
   local F = salesmethods[FName]
   local Help = {}
   Help.Desc = "Delete a "..Name
   Help.ParameterTable = true
   Help.Parameters = {}
   Help.Parameters[1] = {id={Desc="Unique id of "..Name.." that will be deleted."}}
   Help.Parameters[2] = {live={Opt=true, Desc="Set to true to make this command work in the editor.  Default is false."}}
   help.set{input_function=F, help_data=Help}      
end

function BuildMethods(Objects)
   for K,V in pairs(Objects) do
      GenerateListMethod(K,V)
      GenerateModifierMethod(K,V)
      GenerateDeleteMethod(K,V)
   end
end

BuildMethods(objectDefs)