gd_salesforce 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.markdown +47 -0
- data/Rakefile +1 -0
- data/lib/salesforce/version.rb +3 -0
- data/lib/salesforce.rb +174 -0
- data/salesforce.gemspec +27 -0
- metadata +116 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
This is a wrapper for couple of things I usually do with SF
|
2
|
+
===========================================================
|
3
|
+
|
4
|
+
Installation
|
5
|
+
------------
|
6
|
+
|
7
|
+
Prerequisite is having git, ruby and gems installed (also you should have XCode installed since there probably will be need to compile some C code in the background).
|
8
|
+
|
9
|
+
gem install bundler
|
10
|
+
git clone git://github.com/fluke777/salesforce.git
|
11
|
+
cd salesforce
|
12
|
+
bundle install
|
13
|
+
rake install
|
14
|
+
|
15
|
+
|
16
|
+
Grabbing fields of a module
|
17
|
+
---------------------------
|
18
|
+
Since Force.com explorer cannot search in fields and I love my text editor too much. I created a command how to download the list of fields
|
19
|
+
|
20
|
+
require 'rubygems'
|
21
|
+
require 'salesforce'
|
22
|
+
|
23
|
+
client = Salesforce::Client.new('login', 'pass+token')
|
24
|
+
fields = client.fields('Account')
|
25
|
+
|
26
|
+
# if you are looking for a specific one
|
27
|
+
fields.grep /Customer/i
|
28
|
+
|
29
|
+
# if you did not want a api name you can do whatever you want with describe. For example here I am grabbing label
|
30
|
+
response = client.describe('Account')
|
31
|
+
response[:describeSObjectResponse][:result][:fields].map {|field| field[:label]}
|
32
|
+
|
33
|
+
Downloading data
|
34
|
+
----------------
|
35
|
+
You can grab easily some data. Paging is implemented so it will download all the data.
|
36
|
+
|
37
|
+
# grabbing into array (this should probably be the default and it should return the field rather than pasing a reference inside)
|
38
|
+
x = []
|
39
|
+
client.grab :module => "User", :output => x, :fields => 'Id, Name'
|
40
|
+
x.count
|
41
|
+
|
42
|
+
# storing it into a file as CSV is easy as well
|
43
|
+
require 'fastercsv'
|
44
|
+
|
45
|
+
FasterCSV.open('my-csv.csv', 'w') do |csv|
|
46
|
+
client.grab :module => "User", :output => csv, :fields => 'Id, Name'
|
47
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/salesforce.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'salesforce/version'
|
2
|
+
require 'rforce'
|
3
|
+
require 'fastercsv'
|
4
|
+
require 'active_support/time'
|
5
|
+
require 'pry'
|
6
|
+
|
7
|
+
module Salesforce
|
8
|
+
|
9
|
+
class Client
|
10
|
+
|
11
|
+
attr_accessor :rforce_binding
|
12
|
+
|
13
|
+
def initialize(login, pass, options = {})
|
14
|
+
server = options[:server] || "www.salesforce.com"
|
15
|
+
url = options[:url] || "https://#{server}/services/Soap/u/26.0"
|
16
|
+
@rforce_binding = RForce::Binding.new url
|
17
|
+
@rforce_binding.login login, pass
|
18
|
+
end
|
19
|
+
|
20
|
+
def describe(mod)
|
21
|
+
@rforce_binding.describeSObject(:sObject => mod)
|
22
|
+
end
|
23
|
+
|
24
|
+
def modules
|
25
|
+
g = @rforce_binding.describeGlobal
|
26
|
+
modules = g[:describeGlobalResponse][:result][:sobjects]
|
27
|
+
end
|
28
|
+
|
29
|
+
def fields(mod)
|
30
|
+
result = @rforce_binding.describeSObject(:sObject => mod)
|
31
|
+
if result.has_key?(:Fault)
|
32
|
+
fail result
|
33
|
+
end
|
34
|
+
fields = result[:describeSObjectResponse][:result][:fields]
|
35
|
+
fields.map {|f| f[:name]}
|
36
|
+
end
|
37
|
+
|
38
|
+
def grab(options)
|
39
|
+
sf_module = options[:module] || fail("Specify SFDC module")
|
40
|
+
fields = options[:fields]
|
41
|
+
rforce_binding = @rforce_binding
|
42
|
+
output = options[:output]
|
43
|
+
|
44
|
+
if fields == :all
|
45
|
+
fields = fields(sf_module)
|
46
|
+
elsif fields.kind_of? String
|
47
|
+
fields = fields.split(',')
|
48
|
+
fields = fields.map {|f| f.strip}
|
49
|
+
end
|
50
|
+
|
51
|
+
values = fields.map {|v| v.to_sym}
|
52
|
+
|
53
|
+
query = "SELECT #{values.join(', ')} from #{sf_module}"
|
54
|
+
query(query, options.merge(:values => values))
|
55
|
+
end
|
56
|
+
|
57
|
+
def query(query, options)
|
58
|
+
values = options[:values]
|
59
|
+
as_hash = options[:as_hash]
|
60
|
+
counter = 1
|
61
|
+
fail "If you want to return array you need to specify fields in values key" if !as_hash && values.nil?
|
62
|
+
|
63
|
+
rforce_binding = @rforce_binding
|
64
|
+
output = options[:output]
|
65
|
+
begin
|
66
|
+
answer = rforce_binding.query({:queryString => query, :batchSize => 2000})
|
67
|
+
rescue Timeout::Error => e
|
68
|
+
puts "Timeout occured retrying"
|
69
|
+
retry
|
70
|
+
end
|
71
|
+
|
72
|
+
if answer[:queryResponse].nil? || answer[:queryResponse][:result].nil?
|
73
|
+
fail answer[:Fault][:faultstring] if answer[:Fault] && answer[:Fault][:faultstring]
|
74
|
+
fail "An unknown error occured while querying salesforce."
|
75
|
+
end
|
76
|
+
|
77
|
+
answer[:queryResponse][:result][:records].each {|row| output << (as_hash ? row : row.values_at(*values))} if answer[:queryResponse][:result][:size].to_i > 0
|
78
|
+
|
79
|
+
more_locator = answer[:queryResponse][:result][:queryLocator]
|
80
|
+
|
81
|
+
while more_locator do
|
82
|
+
answer_more = rforce_binding.queryMore({:queryLocator => more_locator, :batchSize => 2000})
|
83
|
+
answer_more[:queryMoreResponse][:result][:records].each do |row|
|
84
|
+
output << (as_hash ? row : row.values_at(*values))
|
85
|
+
end
|
86
|
+
more_locator = answer_more[:queryMoreResponse][:result][:queryLocator]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_deleted(options={})
|
91
|
+
rforce_binding = @rforce_binding
|
92
|
+
sf_module = options[:module]
|
93
|
+
end_time = options[:end_time] || Time.now
|
94
|
+
start_time = options[:start_time] || end_time.advance(:days => -15)
|
95
|
+
fail "The specified start_time cannot be the same value as, or later than, the specified end_time value" unless end_time > start_time
|
96
|
+
puts "Downloading from #{start_time} to #{end_time}"
|
97
|
+
answer = rforce_binding.getDeleted([:sObjectType, sf_module, :startDate, start_time.utc.iso8601, :endDate, end_time.utc.iso8601])
|
98
|
+
end
|
99
|
+
|
100
|
+
def download_deleted(options={})
|
101
|
+
output_file = options[:output_file]
|
102
|
+
fail "Output file not specified" if output_file.nil?
|
103
|
+
|
104
|
+
answer = get_deleted(options)
|
105
|
+
if answer[:getDeletedResponse].nil?
|
106
|
+
fail answer[:Fault][:faultstring] if answer[:Fault] && answer[:Fault][:faultstring]
|
107
|
+
fail "An unknown error occured during deleted records extraction."
|
108
|
+
end
|
109
|
+
|
110
|
+
result = answer[:getDeletedResponse][:result]
|
111
|
+
FasterCSV.open(output_file,"w") do |csv|
|
112
|
+
csv << ["Timestamp", "Id", "IsDeleted"]
|
113
|
+
unless result[:deletedRecords].nil? then
|
114
|
+
result[:deletedRecords].each do |record|
|
115
|
+
timestamp = Time.parse(record[:deletedDate]).to_i
|
116
|
+
csv << [timestamp, record[:id], "true"]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
return result[:earliestDateAvailable], result[:latestDateCovered]
|
122
|
+
end
|
123
|
+
|
124
|
+
def download_updated(options={})
|
125
|
+
module_name = options[:module]
|
126
|
+
end_time = options[:end_time] || Time.now
|
127
|
+
start_time = options[:start_time] || end_time.advance(:days => -1)
|
128
|
+
fields_list = options[:fields] || []
|
129
|
+
puts "Downloading #{module_name} from #{start_time} to #{end_time}"
|
130
|
+
update_answer = rforce_binding.getUpdated([:sObjectType, module_name, :startDate, start_time.utc.iso8601, :endDate, end_time.utc.iso8601])
|
131
|
+
results = update_answer[:getUpdatedResponse][:result][:ids]
|
132
|
+
if results.nil?
|
133
|
+
puts "#{module_name} is empty"
|
134
|
+
FasterCSV.open(options[:output_file], 'w') do |csv|
|
135
|
+
csv << fields_list
|
136
|
+
end
|
137
|
+
return update_answer[:getUpdatedResponse][:result][:latestDateCovered]
|
138
|
+
end
|
139
|
+
puts "Found and downloaded #{results.size} records"
|
140
|
+
fields_list = fields_list.map {|x| x.strip.to_sym}
|
141
|
+
|
142
|
+
converters = {
|
143
|
+
"datetime" => lambda {|f| Time.parse(f).utc.to_i rescue f},
|
144
|
+
"string" => lambda {|f| f[0..128] rescue f},
|
145
|
+
"textarea" => lambda {|f| f[0..128] rescue f},
|
146
|
+
"date" => lambda {|f| Time.parse(f).utc.to_i + (12 * 3600) rescue f},
|
147
|
+
}
|
148
|
+
|
149
|
+
answer = describe(module_name)
|
150
|
+
fields_description = answer[:describeSObjectResponse][:result][:fields]
|
151
|
+
my_fields_description = fields_list.map {|f| fields_description.detect {|fl| fl[:name].to_sym == f}}
|
152
|
+
|
153
|
+
my_converters = my_fields_description.map {|fd| converters[fd[:type]]}
|
154
|
+
FasterCSV.open(options[:output_file], 'w') do |csv|
|
155
|
+
csv << ['Timestamp'] + fields_list
|
156
|
+
results.each_slice(2000) do |slice|
|
157
|
+
ids = slice.reduce([]) do |memo, item|
|
158
|
+
memo.concat [:ID, item]
|
159
|
+
end
|
160
|
+
result = rforce_binding.retrieve([:fieldList, fields_list.join(', '), :sObjectType, module_name].concat(ids) )
|
161
|
+
result[:retrieveResponse][:result].each do |line|
|
162
|
+
|
163
|
+
values = line.values_at(*fields_list).zip(my_converters).map do |value, converter|
|
164
|
+
converter.nil? ? value : converter.call(value)
|
165
|
+
end
|
166
|
+
csv << [Time.parse(update_answer[:getUpdatedResponse][:result][:latestDateCovered]).utc.to_i] + values
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
update_answer[:getUpdatedResponse][:result][:latestDateCovered]
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
data/salesforce.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "salesforce/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "gd_salesforce"
|
7
|
+
s.version = Salesforce::VERSION
|
8
|
+
s.authors = ["Tomas Svarovsky"]
|
9
|
+
s.email = ["svarovsky.tomas@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/fluke777/salesforce"
|
11
|
+
s.summary = %q{Small gem that wraps common tasks that I do with Salesforce}
|
12
|
+
s.description = %q{Small gem that wraps common tasks that I do with Salesforce}
|
13
|
+
|
14
|
+
s.rubyforge_project = "salesforce"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
s.add_runtime_dependency "rforce"
|
24
|
+
s.add_runtime_dependency "fastercsv"
|
25
|
+
s.add_runtime_dependency "activesupport"
|
26
|
+
s.add_runtime_dependency "pry"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gd_salesforce
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tomas Svarovsky
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rforce
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: fastercsv
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: activesupport
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: pry
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Small gem that wraps common tasks that I do with Salesforce
|
79
|
+
email:
|
80
|
+
- svarovsky.tomas@gmail.com
|
81
|
+
executables: []
|
82
|
+
extensions: []
|
83
|
+
extra_rdoc_files: []
|
84
|
+
files:
|
85
|
+
- .gitignore
|
86
|
+
- Gemfile
|
87
|
+
- README.markdown
|
88
|
+
- Rakefile
|
89
|
+
- lib/salesforce.rb
|
90
|
+
- lib/salesforce/version.rb
|
91
|
+
- salesforce.gemspec
|
92
|
+
homepage: https://github.com/fluke777/salesforce
|
93
|
+
licenses: []
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project: salesforce
|
112
|
+
rubygems_version: 1.8.25
|
113
|
+
signing_key:
|
114
|
+
specification_version: 3
|
115
|
+
summary: Small gem that wraps common tasks that I do with Salesforce
|
116
|
+
test_files: []
|