restforce-db 2.1.2 → 2.1.3
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.
- checksums.yaml +4 -4
- data/lib/restforce/db/cleaner.rb +34 -3
- data/lib/restforce/db/client.rb +52 -0
- data/lib/restforce/db/version.rb +1 -1
- data/lib/restforce/db.rb +2 -1
- data/test/cassettes/Restforce_DB_Cleaner/_run/given_a_synchronized_Salesforce_record/when_the_record_has_been_deleted_in_Salesforce/drops_the_synchronized_database_record.yml +120 -0
- data/test/lib/restforce/db/cleaner_test.rb +22 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc23c3050b689f6b9b3c447c772bb8289fcaeb57
|
4
|
+
data.tar.gz: ebd65047805ff8e683d5e53a1db6574f2552ce8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28f57aaa66794e85dc0cfcf07ffca6728b229730a58df3b3d1c900b3d1aabe1ceab0c87dbdc7dd722b9c0705fdd923741c697a742cf69d7bd1effd9513d12c3f
|
7
|
+
data.tar.gz: aa32cae89906cf79f976e52d6c54ffd1b5a262131402d03ee75da5d0b3b0c045e7f9705ac3823262437681490407c9e9a2e9eb439fe7097b10084b7eba299c5d
|
data/lib/restforce/db/cleaner.rb
CHANGED
@@ -3,9 +3,15 @@ module Restforce
|
|
3
3
|
module DB
|
4
4
|
|
5
5
|
# Restforce::DB::Cleaner is responsible for culling the matching database
|
6
|
-
# records when a Salesforce record no longer
|
6
|
+
# records when a Salesforce record is no longer available to synchronize
|
7
|
+
# for a specific mapping.
|
7
8
|
class Cleaner
|
8
9
|
|
10
|
+
# Salesforce can take a few minutes to register record deletion. This
|
11
|
+
# buffer gives us a window of time (in seconds) to look back and see
|
12
|
+
# records which may not have been visible in previous runs.
|
13
|
+
DELETION_READ_BUFFER = 3 * 60
|
14
|
+
|
9
15
|
# Public: Initialize a Restforce::DB::Cleaner.
|
10
16
|
#
|
11
17
|
# mapping - A Restforce::DB::Mapping.
|
@@ -20,18 +26,43 @@ module Restforce
|
|
20
26
|
#
|
21
27
|
# Returns nothing.
|
22
28
|
def run
|
23
|
-
|
24
|
-
@mapping.database_record_type.destroy_all(invalid_salesforce_ids)
|
29
|
+
@mapping.database_record_type.destroy_all(dropped_salesforce_ids)
|
25
30
|
end
|
26
31
|
|
27
32
|
private
|
28
33
|
|
34
|
+
# Internal: Get a comprehensive list of Salesforce IDs corresponding to
|
35
|
+
# records which should be dropped from synchronization for this mapping.
|
36
|
+
#
|
37
|
+
# Returns an Array of IDs.
|
38
|
+
def dropped_salesforce_ids
|
39
|
+
deleted_salesforce_ids + invalid_salesforce_ids
|
40
|
+
end
|
41
|
+
|
42
|
+
# Internal: Get the IDs of records which have been removed from Salesforce
|
43
|
+
# for this mapping within the DELETION_BUFFER for this run.
|
44
|
+
#
|
45
|
+
# Returns an Array of IDs.
|
46
|
+
def deleted_salesforce_ids
|
47
|
+
return [] unless @runner.after
|
48
|
+
|
49
|
+
response = Restforce::DB.client.get_deleted_between(
|
50
|
+
@mapping.salesforce_model,
|
51
|
+
@runner.after - DELETION_READ_BUFFER,
|
52
|
+
@runner.before,
|
53
|
+
)
|
54
|
+
|
55
|
+
response.deletedRecords.map(&:id)
|
56
|
+
end
|
57
|
+
|
29
58
|
# Internal: Get the IDs of records which are in the larger collection
|
30
59
|
# of Salesforce records, but which do not meet the specific conditions for
|
31
60
|
# this mapping.
|
32
61
|
#
|
33
62
|
# Returns an Array of IDs.
|
34
63
|
def invalid_salesforce_ids
|
64
|
+
return [] if @mapping.conditions.empty? || @strategy.passive?
|
65
|
+
|
35
66
|
all_salesforce_ids - valid_salesforce_ids
|
36
67
|
end
|
37
68
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Restforce
|
2
|
+
|
3
|
+
module DB
|
4
|
+
|
5
|
+
# Restforce::DB::Client is a thin abstraction on top of the default
|
6
|
+
# Restforce::Data::Client class, which adds support for an API endpoint
|
7
|
+
# not yet supported by the base gem.
|
8
|
+
class Client < ::Restforce::Data::Client
|
9
|
+
|
10
|
+
# Public: Get a list of Salesforce records which have been deleted between
|
11
|
+
# the specified times.
|
12
|
+
#
|
13
|
+
# sobject - The Salesforce object type to query against.
|
14
|
+
# start_time - A Time or Time-compatible object indicating the earliest
|
15
|
+
# time for which to find deleted records.
|
16
|
+
# end_time - A Time or Time-compatible object indicating the latest time
|
17
|
+
# for which to find deleted records. Defaults to the current
|
18
|
+
# time.
|
19
|
+
#
|
20
|
+
# Example
|
21
|
+
#
|
22
|
+
# Restforce::DB.client.get_deleted_between(
|
23
|
+
# "CustomObject__c",
|
24
|
+
# Time.now - 300,
|
25
|
+
# Time.now,
|
26
|
+
# )
|
27
|
+
#
|
28
|
+
# #=> #<Restforce::Mash
|
29
|
+
# latestDateCovered="2015-05-18T22:31:00.000+0000"
|
30
|
+
# earliestDateAvailable="2015-04-11T06:44:00.000+0000"
|
31
|
+
# deletedRecords=[
|
32
|
+
# #<Restforce::Mash
|
33
|
+
# deletedDate="2015-05-18T22:31:17.000+0000"
|
34
|
+
# id="a001a000001a5vOAAQ"
|
35
|
+
# >
|
36
|
+
# ]
|
37
|
+
# >
|
38
|
+
#
|
39
|
+
# Returns a Restforce::Mash with a `deletedRecords` key.
|
40
|
+
def get_deleted_between(sobject, start_time, end_time = Time.now)
|
41
|
+
api_get(
|
42
|
+
"sobjects/#{sobject}/deleted",
|
43
|
+
start: start_time.utc.iso8601,
|
44
|
+
end: end_time.utc.iso8601,
|
45
|
+
).body
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/lib/restforce/db/version.rb
CHANGED
data/lib/restforce/db.rb
CHANGED
@@ -4,6 +4,7 @@ require "restforce"
|
|
4
4
|
require "restforce/extensions"
|
5
5
|
|
6
6
|
require "restforce/db/version"
|
7
|
+
require "restforce/db/client"
|
7
8
|
require "restforce/db/configuration"
|
8
9
|
require "restforce/db/registry"
|
9
10
|
require "restforce/db/strategy"
|
@@ -78,7 +79,7 @@ module Restforce
|
|
78
79
|
#
|
79
80
|
# Returns a Restforce::Data::Client instance.
|
80
81
|
def self.client
|
81
|
-
@client ||=
|
82
|
+
@client ||= DB::Client.new(
|
82
83
|
username: configuration.username,
|
83
84
|
password: configuration.password,
|
84
85
|
security_token: configuration.security_token,
|
@@ -0,0 +1,120 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: post
|
5
|
+
uri: https://<host>/services/oauth2/token
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: grant_type=password&client_id=<client_id>&client_secret=<client_secret>&username=<username>&password=<password><security_token>
|
9
|
+
headers:
|
10
|
+
User-Agent:
|
11
|
+
- Faraday v0.9.1
|
12
|
+
Content-Type:
|
13
|
+
- application/x-www-form-urlencoded
|
14
|
+
Accept-Encoding:
|
15
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
16
|
+
Accept:
|
17
|
+
- "*/*"
|
18
|
+
response:
|
19
|
+
status:
|
20
|
+
code: 200
|
21
|
+
message: OK
|
22
|
+
headers:
|
23
|
+
Date:
|
24
|
+
- Wed, 20 May 2015 22:04:31 GMT
|
25
|
+
Set-Cookie:
|
26
|
+
- BrowserId=bSV2iUXwSSOWJFCXI7O_RA;Path=/;Domain=.salesforce.com;Expires=Sun,
|
27
|
+
19-Jul-2015 22:04:31 GMT
|
28
|
+
Expires:
|
29
|
+
- Thu, 01 Jan 1970 00:00:00 GMT
|
30
|
+
Pragma:
|
31
|
+
- no-cache
|
32
|
+
Cache-Control:
|
33
|
+
- no-cache, no-store
|
34
|
+
Content-Type:
|
35
|
+
- application/json;charset=UTF-8
|
36
|
+
Transfer-Encoding:
|
37
|
+
- chunked
|
38
|
+
body:
|
39
|
+
encoding: ASCII-8BIT
|
40
|
+
string: '{"id":"https://login.salesforce.com/id/00D1a000000H3O9EAK/0051a000000UGT8AAO","issued_at":"1432159471623","token_type":"Bearer","instance_url":"https://<host>","signature":"vlqOCnMqVU2ShrK2bLIsZTK24INW4Z4LpDdILzgxrFs=","access_token":"00D1a000000H3O9!AQ4AQPRrI9oLwB7KrYUUG5M8JsqJ4YAQepwPNs0vuNDy1phuqeS5ZoO0WecEoXrGBflaKK9LegZ0cFfz2FZwe9v4MZFSUlc6"}'
|
41
|
+
http_version:
|
42
|
+
recorded_at: Wed, 20 May 2015 22:04:31 GMT
|
43
|
+
- request:
|
44
|
+
method: post
|
45
|
+
uri: https://<host>/services/data/<api_version>/sobjects/CustomObject__c
|
46
|
+
body:
|
47
|
+
encoding: UTF-8
|
48
|
+
string: '{"Name":"Are you going to Scarborough Fair?","Example_Field__c":"Parsley,
|
49
|
+
Sage, Rosemary, and Thyme."}'
|
50
|
+
headers:
|
51
|
+
User-Agent:
|
52
|
+
- Faraday v0.9.1
|
53
|
+
Content-Type:
|
54
|
+
- application/json
|
55
|
+
Authorization:
|
56
|
+
- OAuth 00D1a000000H3O9!AQ4AQPRrI9oLwB7KrYUUG5M8JsqJ4YAQepwPNs0vuNDy1phuqeS5ZoO0WecEoXrGBflaKK9LegZ0cFfz2FZwe9v4MZFSUlc6
|
57
|
+
Accept-Encoding:
|
58
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
59
|
+
Accept:
|
60
|
+
- "*/*"
|
61
|
+
response:
|
62
|
+
status:
|
63
|
+
code: 201
|
64
|
+
message: Created
|
65
|
+
headers:
|
66
|
+
Date:
|
67
|
+
- Wed, 20 May 2015 22:04:31 GMT
|
68
|
+
Set-Cookie:
|
69
|
+
- BrowserId=iTltKbJMRMS2WMP0BxPVvg;Path=/;Domain=.salesforce.com;Expires=Sun,
|
70
|
+
19-Jul-2015 22:04:31 GMT
|
71
|
+
Expires:
|
72
|
+
- Thu, 01 Jan 1970 00:00:00 GMT
|
73
|
+
Sforce-Limit-Info:
|
74
|
+
- api-usage=9/15000
|
75
|
+
Location:
|
76
|
+
- "/services/data/<api_version>/sobjects/CustomObject__c/a001a000001aHLuAAM"
|
77
|
+
Content-Type:
|
78
|
+
- application/json;charset=UTF-8
|
79
|
+
Transfer-Encoding:
|
80
|
+
- chunked
|
81
|
+
body:
|
82
|
+
encoding: ASCII-8BIT
|
83
|
+
string: '{"id":"a001a000001aHLuAAM","success":true,"errors":[]}'
|
84
|
+
http_version:
|
85
|
+
recorded_at: Wed, 20 May 2015 22:04:31 GMT
|
86
|
+
- request:
|
87
|
+
method: delete
|
88
|
+
uri: https://<host>/services/data/<api_version>/sobjects/CustomObject__c/a001a000001aHLuAAM
|
89
|
+
body:
|
90
|
+
encoding: US-ASCII
|
91
|
+
string: ''
|
92
|
+
headers:
|
93
|
+
User-Agent:
|
94
|
+
- Faraday v0.9.1
|
95
|
+
Authorization:
|
96
|
+
- OAuth 00D1a000000H3O9!AQ4AQPRrI9oLwB7KrYUUG5M8JsqJ4YAQepwPNs0vuNDy1phuqeS5ZoO0WecEoXrGBflaKK9LegZ0cFfz2FZwe9v4MZFSUlc6
|
97
|
+
Accept-Encoding:
|
98
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
99
|
+
Accept:
|
100
|
+
- "*/*"
|
101
|
+
response:
|
102
|
+
status:
|
103
|
+
code: 204
|
104
|
+
message: No Content
|
105
|
+
headers:
|
106
|
+
Date:
|
107
|
+
- Wed, 20 May 2015 22:04:32 GMT
|
108
|
+
Set-Cookie:
|
109
|
+
- BrowserId=6WNubRIVSxG4Hy8WSNOcaw;Path=/;Domain=.salesforce.com;Expires=Sun,
|
110
|
+
19-Jul-2015 22:04:32 GMT
|
111
|
+
Expires:
|
112
|
+
- Thu, 01 Jan 1970 00:00:00 GMT
|
113
|
+
Sforce-Limit-Info:
|
114
|
+
- api-usage=9/15000
|
115
|
+
body:
|
116
|
+
encoding: UTF-8
|
117
|
+
string: ''
|
118
|
+
http_version:
|
119
|
+
recorded_at: Wed, 20 May 2015 22:04:32 GMT
|
120
|
+
recorded_with: VCR 2.9.3
|
@@ -73,6 +73,28 @@ describe Restforce::DB::Cleaner do
|
|
73
73
|
end
|
74
74
|
end
|
75
75
|
end
|
76
|
+
|
77
|
+
describe "when the record has been deleted in Salesforce" do
|
78
|
+
let(:runner) { Restforce::DB::Runner.new(0, Time.now - 300) }
|
79
|
+
let(:cleaner) { Restforce::DB::Cleaner.new(mapping, runner) }
|
80
|
+
let(:dummy_response) do
|
81
|
+
Struct.new(:deletedRecords).new([
|
82
|
+
Restforce::Mash.new(id: salesforce_id),
|
83
|
+
])
|
84
|
+
end
|
85
|
+
|
86
|
+
before do
|
87
|
+
runner.tick!
|
88
|
+
end
|
89
|
+
|
90
|
+
it "drops the synchronized database record" do
|
91
|
+
Restforce::DB::Client.stub_any_instance(:get_deleted_between, dummy_response) do
|
92
|
+
cleaner.run
|
93
|
+
end
|
94
|
+
|
95
|
+
expect(database_model.last).to_be_nil
|
96
|
+
end
|
97
|
+
end
|
76
98
|
end
|
77
99
|
end
|
78
100
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: restforce-db
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Horner
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -219,6 +219,7 @@ files:
|
|
219
219
|
- lib/restforce/db/associator.rb
|
220
220
|
- lib/restforce/db/attribute_map.rb
|
221
221
|
- lib/restforce/db/cleaner.rb
|
222
|
+
- lib/restforce/db/client.rb
|
222
223
|
- lib/restforce/db/collector.rb
|
223
224
|
- lib/restforce/db/command.rb
|
224
225
|
- lib/restforce/db/configuration.rb
|
@@ -280,6 +281,7 @@ files:
|
|
280
281
|
- test/cassettes/Restforce_DB_Cleaner/_run/given_a_synchronized_Salesforce_record/when_the_mapping_has_no_conditions/does_not_drop_the_synchronized_database_record.yml
|
281
282
|
- test/cassettes/Restforce_DB_Cleaner/_run/given_a_synchronized_Salesforce_record/when_the_record_does_not_meet_the_mapping_conditions/but_meets_conditions_for_a_parallel_mapping/does_not_drop_the_synchronized_database_record.yml
|
282
283
|
- test/cassettes/Restforce_DB_Cleaner/_run/given_a_synchronized_Salesforce_record/when_the_record_does_not_meet_the_mapping_conditions/drops_the_synchronized_database_record.yml
|
284
|
+
- test/cassettes/Restforce_DB_Cleaner/_run/given_a_synchronized_Salesforce_record/when_the_record_has_been_deleted_in_Salesforce/drops_the_synchronized_database_record.yml
|
283
285
|
- test/cassettes/Restforce_DB_Cleaner/_run/given_a_synchronized_Salesforce_record/when_the_record_meets_the_mapping_conditions/does_not_drop_the_synchronized_database_record.yml
|
284
286
|
- test/cassettes/Restforce_DB_Collector/_run/given_a_Salesforce_record_with_an_associated_database_record/returns_the_attributes_from_both_records.yml
|
285
287
|
- test/cassettes/Restforce_DB_Collector/_run/given_an_existing_Salesforce_record/returns_the_attributes_from_the_Salesforce_record.yml
|