salesforce_cache 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ CHANGELOG
2
+ ---------
3
+ - **2014-03-05**: 0.0.6
4
+ - Ability to disable remote downloading from Salesforce.com when it is not desirable
5
+ - Convert README and CHANGELOG files to Markdown format
6
+ - Rename to SalesforceCache from SalesforceFsdb
7
+ - **2014-03-05**: 0.0.5
8
+ - Deprecation notice for SalesforceFsdb
9
+ - **2014-02-19**: 0.0.4
10
+ - Add "redirect" in case an Opportunity Id is presented for an Account sObject
11
+ - Add autoload to enable "require 'salesforce_fsdb'" (now require 'salesforce_cache')
12
+ - **2014-02-06**: 0.0.3
13
+ - Add SOQL result caching
14
+ - **2014-01-28**: 0.0.2
15
+ - Use sObject types in filenames to enable wider, more generic sObject support
16
+ - **2014-01-27**: 0.0.1
17
+ - Initial release
18
+ - Filesystem cache of Account, Contact, Opportunity and User sObjects.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2014 John Wang
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ SalesforceCache: SFDC sObject / SOQL Request Caching
2
+ ====================================================
3
+
4
+ Synopsis
5
+ --------
6
+
7
+ Salesforce sObject system to retrieve and cache queries from Salesforce.com including sObjects and SOQL results. This currently uses the file system to cache results though other storage methods may be supported in the future.
8
+
9
+ Prior to version 0.0.6, SalesforceCache was named SalesforceFsdb.
10
+
11
+ Installing
12
+ ----------
13
+
14
+ To install SalesforceCache, use the following command:
15
+
16
+ $ gem install salesforce_cache
17
+
18
+ #Examples
19
+ ---------
20
+
21
+ require 'salesforce_cache'
22
+
23
+ paramsGeneral = {
24
+ :api_fqdn => 'na1.salesforce.com',
25
+ :api_version => '29.0',
26
+ :data_dir => '/path/to/sf_data',
27
+ :max_age => 60*60*24*7,
28
+ :skip_remote => false # optional
29
+ }
30
+
31
+ paramsToken = {
32
+ :grant_type => 'password',
33
+ :client_id => 'my_client_id',
34
+ :client_secret => 'my_client_secret',
35
+ :username => 'my_username',
36
+ :password => 'my_password'
37
+ }
38
+
39
+ # using the filesystem cache with max_age
40
+ sfCacheClient = SalesforceCache::Client.new(paramsGeneral,paramsToken)
41
+ sObjectHash = sfCacheClient.getSobjectForSfidAndType('my_sobject_id','Account')
42
+
43
+ sSfid = '1234567890'
44
+ sSoql = %{ SELECT Id,AccountId,Name,IsWon,StageName,CloseDate from Opportunity where AccountId='\#{sSfid}' }
45
+ sPath = "AccountOpportunities/sf_act_\#{sSfid}_opps.jff"
46
+
47
+ dResults = sfCacheClient.getResForSoqlAndPath( sSoql, sPath )
48
+
49
+ # without using a filesystem cache
50
+ sfRestClient = SalesforceCache::RestClient.new(paramsGeneral,paramsToken)
51
+ sObjectHash = sfRestClient.getSobjectForSfidAndType('my_sobject_id','Account')
52
+
53
+ #Documentation
54
+ --------------
55
+
56
+ This gem is 100% documented with YARD, an exceptional documentation library. To see documentation for this, and all the gems installed on your system use:
57
+
58
+ $ gem install yard
59
+ $ yard server -g
60
+
61
+ Notes
62
+ -----
63
+
64
+ 1. Max Age
65
+
66
+ The :max_age parameter is used to indicate the maximum age in seconds for cached data. If local data exceeds maximum age, a new copy of the data is retrieved from Salesforce.com. To ensure retrieval of a new copy, use :max_age=-1.
67
+
68
+ 2. Disable Remote Requests
69
+
70
+ If the optional :skip_remote parameter is set to true, the :max_age parameter is ignored and a local copy will be used if available. A remote call to Salesforce will not be issued if a local cache is not found. This is for environments when it is undesirable to make outside requests.
71
+
72
+ The default value is false.
73
+
74
+ 3. Directory Paths
75
+
76
+ 1. Salesforce Sobjects are stored on the file system under File.join( paramsGeneral[:data_dir], Sobject_type ).
77
+
78
+ 2. SOQL results are stored in the path indicated. Sub-directories are created for SOQL paths. If a relative directory is provided, it is appended to the :data_dir. If an absolute directory is provided, it is used as an absolute directory in its entirety.
79
+
80
+ 4. Object Size
81
+
82
+ Some large Salesforce Objects may generate Salesforce errors. A future release will allow specifying specific fields for retrieval.
83
+
84
+ 5. Redirection
85
+
86
+ In the event an Account sObject request results in a NOT_FOUND error for a given Salesforce Id, the library will attempt to use the Id as an Opportunity Id and, if successful, will return the proper Account sObject.
87
+
88
+ #Change Log
89
+ -----------
90
+
91
+ - **2014-03-05**: 0.0.6
92
+ - Ability to disable remote downloading from Salesforce.com when it is not desirable
93
+ - Convert README and CHANGELOG files to Markdown format
94
+ - Rename to SalesforceCache from SalesforceFsdb
95
+ - **2014-03-05**: 0.0.5
96
+ - Deprecation notice for SalesforceFsdb
97
+ - **2014-02-19**: 0.0.4
98
+ - Add "redirect" in case an Opportunity Id is presented for an Account sObject
99
+ - Add autoload to enable "require 'salesforce_fsdb'" (now require 'salesforce_cache')
100
+ - **2014-02-06**: 0.0.3
101
+ - Add SOQL result caching
102
+ - **2014-01-28**: 0.0.2
103
+ - Use sObject types in filenames to enable wider, more generic sObject support
104
+ - **2014-01-27**: 0.0.1
105
+ - Initial release
106
+ - Filesystem cache of Account, Contact, Opportunity and User sObjects.
107
+
108
+ Please see the {file:CHANGELOG.md} document for additional release information.
109
+
110
+ #Links
111
+ ------
112
+
113
+ Salesforce REST API Reference
114
+
115
+ http://www.salesforce.com/us/developer/docs/api_rest/
116
+
117
+ #Copyright and License
118
+ ----------------------
119
+
120
+ SalesforceCache © 2014 by [John Wang](mailto:johncwang@gmail.com).
121
+
122
+ SalesforceCache is licensed under the MIT license. Please see the LICENSE document for more information.
123
+
124
+ Warranty
125
+ --------
126
+
127
+ This software is provided "as is" and without any express or implied warranties, including, without limitation, the implied warranties of merchantibility and fitness for a particular purpose.
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+
5
+ task :build => :gendoc do
6
+ system "gem build salesforce_cache.gemspec"
7
+ end
8
+
9
+ task :gendoc do
10
+ #puts 'yard doc generation disabled until JRuby build native extensions for redcarpet or yard removes the dependency.'
11
+ system "yardoc"
12
+ system "yard stats --list-undoc"
13
+ end
14
+
15
+ task :default => :test
16
+
17
+ desc 'Test the SalesforceFsdb library.'
18
+ Rake::TestTask.new do |t|
19
+ t.libs << 'lib'
20
+ t.pattern = 'test/**/test_*.rb'
21
+ t.verbose = false
22
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.6
@@ -0,0 +1,137 @@
1
+ require 'fileutils'
2
+ require 'json'
3
+ require 'salesforce_cache/rest_client'
4
+
5
+ module SalesforceCache
6
+ class Client
7
+ attr_accessor :oSfRestClient
8
+
9
+ def initialize(dParamsGeneral={},dParamsToken={})
10
+ if dParamsGeneral.has_key?(:disable_remote) && dParamsGeneral[:disable_remote]
11
+ @bRemoteEnabled = false
12
+ else
13
+ @bRemoteEnabled = true
14
+ end
15
+ @oSfRestClient = @bRemoteEnabled \
16
+ ? SalesforceCache::RestClient.new(dParamsGeneral,dParamsToken) : nil
17
+ @sFsdbBaseDir = dParamsGeneral[:data_dir] || '.'
18
+ @iMaxAgeSec = dParamsGeneral[:max_age] || 3600*24*7
19
+ end
20
+
21
+ def getFileForSfidAndType(sSfid=nil,sType=nil)
22
+ sFile = %Q{sf_#{sType}_#{sSfid}.json}
23
+ return sFile
24
+ end
25
+
26
+ def getDirForType(sType=nil)
27
+ sDir = File.join(@sFsdbBaseDir,sType)
28
+ return sDir
29
+ end
30
+
31
+ def getPathForSfidAndType(sSfid=nil,sType=nil)
32
+ sPath = File.join( self.getDirForType(sType), self.getFileForSfidAndType(sSfid,sType))
33
+ return sPath
34
+ end
35
+
36
+ def getResForSoqlAndPath(sSoql=nil,sPath=nil)
37
+ return nil if sSoql.nil?
38
+ if ! sPath.nil? && sPath !~ /^\s*\//
39
+ sPath.strip!
40
+ sPath = File.join(@sFsdbBaseDir,sPath)
41
+ end
42
+ dRes = { :meta => { :iEpochRetrieved => -1, :iStatus => 404 } }
43
+ if !sPath.nil? && File.exists?(sPath)
44
+ jTop = File.open(sPath,'r').read
45
+ dTop = JSON.parse(jTop,:symbolize_names=>true)
46
+ if dTop.has_key?(:source) && dTop.has_key?(:meta) && dTop[:meta].has_key?(:iEpochRetrieved)
47
+ iEpochRetrieved = dTop[:meta][:iEpochRetrieved]
48
+ iEpochNow = Time.now.to_i
49
+ iAgeSec = iEpochNow - iEpochRetrieved
50
+ if ! @bRemoteEnabled || ( iAgeSec < @iMaxAgeSec && dTop[:source].has_key?(:done) )
51
+ dRes = dTop[:source]
52
+ return dRes
53
+ end
54
+ end
55
+ end
56
+ return @bRemoteEnabled ? self.getResForSoqlAndPathFromRemote(sSoql,sPath) : nil
57
+ end
58
+
59
+ def getResForSoqlAndPathFromRemote(sSoql=nil,sPath=nil)
60
+ return nil if sSoql.nil?
61
+ if ! sPath.nil? && sPath !~ /^\s*\//
62
+ sPath.strip!
63
+ sPath = File.join(@sFsdbBaseDir,sPath)
64
+ end
65
+ dRes = @oSfRestClient.getSoqlResults(sSoql)
66
+ if ! dRes.nil? && dRes.has_key?(:done) && dRes[:done] == true && ! sPath.nil?
67
+ if sPath =~ /^([\S]+)\/[^\/]+$/
68
+ sDir = $1
69
+ FileUtils.mkdir_p(sDir) if ! Dir.exists?(sDir)
70
+ end
71
+ File.open(sPath,'w') do |fTop|
72
+ dTop = {
73
+ :meta => { :iEpochRetrieved => Time.now.to_i, :iStatus => 200 },
74
+ :source => dRes
75
+ }
76
+ jTop = JSON.dump(dTop)
77
+ fTop.puts(jTop)
78
+ end
79
+ return dRes
80
+ end
81
+ return nil
82
+ end
83
+
84
+ def getSobjectForSfidAndType(sSfid=nil,sType=nil)
85
+ sPath = self.getPathForSfidAndType(sSfid,sType)
86
+ dObj = { :meta => { :iEpochRetrieved => -1, :iStatus => 404 } }
87
+ if File.exists?(sPath)
88
+ jTop = File.open(sPath,'r').read
89
+ dTop = JSON.parse(jTop,:symbolize_names=>true)
90
+ if dTop.has_key?(:source) && dTop.has_key?(:meta) && dTop[:meta].has_key?(:iEpochRetrieved)
91
+ iEpochRetrieved = dTop[:meta][:iEpochRetrieved]
92
+ iEpochNow = Time.now.to_i
93
+ iAgeSec = iEpochNow - iEpochRetrieved
94
+ if ! @bRemoteEnabled || ( iAgeSec < @iMaxAgeSec && dTop[:source].has_key?(:Id) )
95
+ dObj = dTop[:source]
96
+ return dObj
97
+ end
98
+ end
99
+ end
100
+ return @bRemoteEnabled ? self.getSobjectForSfidAndTypeFromRemote(sSfid,sType) : nil
101
+ end
102
+
103
+ def getSobjectForSfidAndTypeFromRemote(sSfid=nil,sType=nil)
104
+ dObj = @oSfRestClient.getSobjectForSfidAndType(sSfid,sType)
105
+ if (dObj.is_a?(Array) && dObj[0][:errorCode] == 'NOT_FOUND') || dObj.nil?
106
+ if sType == 'Account'
107
+
108
+ dObjOppTry = self.getSobjectForSfidAndType(sSfid,'Opportunity')
109
+
110
+ if dObjOppTry.is_a?(Hash) && dObjOppTry.has_key?(:AccountId)
111
+ sSfidAct = dObjOppTry[:AccountId]
112
+ return self.getSobjectForSfidAndType(sSfidAct,sType)
113
+ end
114
+
115
+ else
116
+ return nil
117
+ end
118
+ end
119
+ if dObj.is_a?(Hash) && dObj.has_key?(:Id)
120
+ sDir = self.getDirForType(sType)
121
+ sPath = self.getPathForSfidAndType(sSfid,sType)
122
+ FileUtils.mkdir_p(sDir) if ! Dir.exists?(sDir)
123
+ File.open(sPath,'w') do |fTop|
124
+ dTop = {
125
+ :meta => { :iEpochRetrieved => Time.now.to_i, :iStatus => 200 },
126
+ :source => dObj
127
+ }
128
+ jTop = JSON.dump(dTop)
129
+ fTop.puts(jTop)
130
+ end
131
+ return dObj
132
+ end
133
+ return nil
134
+ end
135
+
136
+ end
137
+ end
@@ -0,0 +1,91 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module SalesforceCache
5
+ class RestClient
6
+
7
+ attr_accessor :sSfTokenUrl
8
+ attr_accessor :dTokenRes
9
+
10
+ def initialize(dParamsGeneral={},dParamsToken={})
11
+ @dParamsToken = dParamsToken
12
+ @sSfTokenUrl = dParamsGeneral.has_key?(:token_url) ? dParamsGeneral[:token_url] \
13
+ : 'https://login.salesforce.com/services/oauth2/token'
14
+ @sApiFqdn = dParamsGeneral.has_key?(:api_fqdn) ? dParamsGeneral[:api_fqdn] : 'na5.salesforce.com'
15
+ @sApiVersion = dParamsGeneral.has_key?(:api_version) ? dParamsGeneral[:api_version] : '27.0'
16
+ @dTokenRes = {}
17
+ self.loadToken()
18
+ end
19
+
20
+ def loadToken()
21
+ oRes = Faraday.post(@sSfTokenUrl,@dParamsToken)
22
+ @dTokenRes = JSON.parse(oRes.body,:symbolize_names=>true)
23
+ return self
24
+ end
25
+
26
+ def getAttr(yKey=nil)
27
+ yKey = yKey.to_sym if yKey.is_a?(String)
28
+ xxVal = @dTokenRes.has_key?(yKey) ? @dTokenRes[yKey] : nil
29
+ return xxVal
30
+ end
31
+
32
+ def getHeaders()
33
+ dHeaders = { 'Authorization' => 'Bearer ' + self.getAttr(:access_token) }
34
+ return dHeaders
35
+ end
36
+
37
+ def getSoqlResults(sSoql=nil)
38
+ dQry = { :q => sSoql}
39
+ sUrl = "https://#{@sApiFqdn}/services/data/v#{@sApiVersion}/query"
40
+ dHead = self.getHeaders()
41
+ oRes = Faraday.get(sUrl,dQry,dHead)
42
+ dRes = JSON.parse(oRes.body,:symbolize_names=>true)
43
+ return dRes
44
+ end
45
+
46
+ def getSfUrlForSfidAndType(sSfid=nil,sType=nil)
47
+ sUrl = "https://#{@sApiFqdn}/services/data/v#{@sApiVersion}/sobjects/#{sType}/#{sSfid}/"
48
+ return sUrl
49
+ end
50
+
51
+ def getSobjectForSfidAndType(sSfid=nil,sType=nil)
52
+ sUrl = self.getSfUrlForSfidAndType(sSfid,sType)
53
+ sHead = self.getHeaders()
54
+ oRes = Faraday.get(sUrl,{},sHead)
55
+ dRes = JSON.parse(oRes.body,:symbolize_names=>true)
56
+ return dRes
57
+ end
58
+
59
+ end
60
+ end
61
+
62
+ =begin
63
+
64
+ SFDC Error: Unable to Process Request
65
+
66
+ '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> (JSON::ParserError)
67
+
68
+ <html>
69
+ <head>
70
+ <title>Unable to Process Request</title>
71
+ <link href="/dCSS/Theme2/default/maintenance.css" type="text/css" rel="stylesheet" >
72
+ </head>
73
+
74
+ <body>
75
+ <div id="red">
76
+ <div id="box">
77
+ <h1>Unable to Process Request</h1>
78
+ <h2>We apologize for the inconvenience</h2>
79
+ <br><br>
80
+ The salesforce.com servers are temporarily unable to respond to your request. We apologize for the inconvenience.<br>
81
+ Thank you for your patience, and please try again in a few moments.
82
+ <br><br>
83
+ Visit <a href=http://trust.salesforce.com>http://trust.salesforce.com</a> for current system status and availability.
84
+ <br><br>
85
+ <form><p class="input"><a href="javascript:history.back()" class="back">back</a><span>&nbsp;</span></form>
86
+ </div>
87
+ </div>
88
+ </body>
89
+ </html>'
90
+
91
+ =end
@@ -0,0 +1,4 @@
1
+ module SalesforceCache
2
+ autoload :Client, 'salesforce_cache/client'
3
+ autoload :RestClient, 'salesforce_cache/rest_client'
4
+ end
@@ -0,0 +1,23 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'salesforce_cache'
3
+ s.version = '0.0.6'
4
+ s.date = '2014-03-05'
5
+ s.summary = 'Salesforce Filesystem Cache'
6
+ s.description = 'Filesystem cache manager for Salesforce sObjects and SOQL results'
7
+ s.authors = ['John Wang']
8
+ s.email = 'johncwang@gmail.com'
9
+ s.files = [
10
+ 'CHANGELOG.md',
11
+ 'LICENSE',
12
+ 'README.md',
13
+ 'Rakefile',
14
+ 'VERSION',
15
+ 'salesforce_cache.gemspec',
16
+ 'lib/salesforce_cache.rb',
17
+ 'lib/salesforce_cache/client.rb',
18
+ 'lib/salesforce_cache/rest_client.rb',
19
+ 'test/test_setup.rb'
20
+ ]
21
+ s.homepage = 'http://johnwang.com/'
22
+ s.license = 'MIT'
23
+ end
@@ -0,0 +1,29 @@
1
+ require 'test/unit'
2
+ require 'salesforce_cache/client'
3
+
4
+ class SalesforceCacheTest < Test::Unit::TestCase
5
+ def testSetup
6
+ paramsGeneral = {
7
+ :api_fqdn => 'na1.salesforce.com',
8
+ :api_version => '29.0',
9
+ :data_dir => '/path/to/sf_data',
10
+ :max_age => 60*60*24*7
11
+ }
12
+
13
+ paramsToken = {
14
+ :grant_type => 'password',
15
+ :client_id => 'my_client_id',
16
+ :client_secret => 'my_client_secret',
17
+ :username => 'my_username',
18
+ :password => 'my_password'
19
+ }
20
+
21
+ sfFsdbClient = SalesforceCache::Client.new(paramsGeneral,paramsToken)
22
+ accountDir = sfFsdbClient.getDirForType('Account')
23
+
24
+ assert_equal accountDir, File.join(paramsGeneral[:data_dir],'Account')
25
+
26
+ tokenUrl = 'https://login.salesforce.com/services/oauth2/token'
27
+ assert_equal tokenUrl, sfFsdbClient.oSfRestClient.sSfTokenUrl
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: salesforce_cache
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.6
6
+ platform: ruby
7
+ authors:
8
+ - John Wang
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2014-03-05 00:00:00 Z
14
+ dependencies: []
15
+
16
+ description: Filesystem cache manager for Salesforce sObjects and SOQL results
17
+ email: johncwang@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - CHANGELOG.md
26
+ - LICENSE
27
+ - README.md
28
+ - Rakefile
29
+ - VERSION
30
+ - salesforce_cache.gemspec
31
+ - lib/salesforce_cache.rb
32
+ - lib/salesforce_cache/client.rb
33
+ - lib/salesforce_cache/rest_client.rb
34
+ - test/test_setup.rb
35
+ homepage: http://johnwang.com/
36
+ licenses:
37
+ - MIT
38
+ post_install_message:
39
+ rdoc_options: []
40
+
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ requirements: []
56
+
57
+ rubyforge_project:
58
+ rubygems_version: 1.8.11
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: Salesforce Filesystem Cache
62
+ test_files: []
63
+