Build a Command Line App for Force.com with Ruby & Thor

Over at CloudSpokes we write a lot of ruby code (CloudSpokes.com runs on Heroku in Ruby with Database.com as the backend) for debugging, data migration, utilities and more. We typically just write a quick ruby class and run it from the command line to execute the script. However, I thought it might be cooler to write a cli for Force.com that I could distribute to the team.

So if you know a little ruby, the process isn't that difficult. All you need is the databasedoctom gem and thor, a simple tool for building self-documenting command line utilities in ruby. You then write your ruby code in the .thor file and execute if from the command line as you would any other cli.

So here are the available commands for the cli via thor's help command. Feel free to fork the code at github.

This video walks you through the functionality, setup and code for the cli in case you want more detail. The main code is below after the jump.

Assuming you have ruby installed, you need to install the databasedotcom and thor gems. It's very trivial and the instructions are available from their respective github repos. With thor, you can pass parameters to each task and setup method options. So for instance, in the thor file below, each task has an optional '-c' switch where you specify the connection file to load thus making it easy to connect to different orgs. If you omit the switch, the default databasedotcom.yml file is loaded.

require 'databasedotcom'

class Utils < Thor

 desc "query SOQL", "runs a soql query and displays the value of each record's 'name' field"
 method_option :config_file, :type => :string, :default => "databasedotcom.yml", 
  :aliases => "-c", :desc => "The name of the file containing the connection parameters." 
 def query(soql)
  client = authenticate(options[:config_file])
  # execute the soql and iterate over the results to output the name
  client.query("#{soql}").each do |r|
 puts r.Name
  end
 end

 desc "export SOQL FIELDS FILE", "runs a soql query and exports the specified 
  comma separated list of fields to a comma separated file"
 method_option :config_file, :type => :string, :default => "databasedotcom.yml", 
  :aliases => "-c", :desc => "The name of the file containing the connection parameters."
 def export(soql, fields, file)
  client = authenticate(options[:config_file])
  # query for records
  records = client.query("#{soql}")
  # open the file to write (probably local directory)
  File.open(file, 'w') do |f| 
 # interate over the records
 records.each do |r| 
  # create a single line with all field values specified
  line = ''
  fields.split(',').each do |field|
   line += "#{eval("r.#{field}")},"
  end 
  # write each line to the csv file  
  f.puts "#{line}\n"
 end
  end 
 end

 desc "describe OBJECT", "displays the describe info for a particular object"
 method_option :config_file, :type => :string, :default => "databasedotcom.yml", 
  :aliases => "-c", :desc => "The name of the file containing the connection parameters."
 def describe(object)
  client = authenticate(options[:config_file])
  # call describe on the object by name
  sobject = client.describe_sobject(object)
  # output the results -- not very useful (frowny face)
  puts sobject
 end

 desc "get_token", "retreives an access token"
 method_option :config_file, :type => :string, :default => "databasedotcom.yml", 
  :aliases => "-c", :desc => "The name of the file containing the connection parameters."
 def get_token
  client = authenticate(options[:config_file]).oauth_token
  puts "Access token: #{client}"
 end

 desc "show_config", "display the salesforce connection properties"
 method_option :config_file, :type => :string, :default => "databasedotcom.yml", 
  :aliases => "-c", :desc => "The name of the file containing the connection parameters."
 def show_config
  config = YAML.load_file(options[:config_file])
  puts config
 end

 private 

  def authenticate(file_name)
 # load the configuration file with connection parameters
 config = YAML.load_file(file_name)
 # init the databasedotcom gem with the specified yml config file
 client = Databasedotcom::Client.new(file_name)  
 # pass the credentials to authenticate
 client.authenticate :username => config['username'], :password => config['password']
 return client
  end

end

The connection file is a simple yml file containing the parameters specified by the databasedotcom gem. You can have as many connection files as you would like to for production, developer and sandbox orgs and easily load different files using the '-c' switch. For instance, to run the query task against the org specified in the sandbox.yml file you would run:

thor utils:query 'select id, name from account limie 10' -c 'sandbox.yml'
client_id: YOUR-CLIENT-ID
client_secret: YOUR-CLIENT-SECRET
host: test.salesforce.com
debugging: false
username: YOUR-USERNAME
password: YOUR-PASSWORD