restforce-db 1.2.9 → 1.2.10
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/association_cache.rb +4 -1
- data/lib/restforce/db/associations/belongs_to.rb +27 -3
- data/lib/restforce/db/associations/has_many.rb +3 -3
- data/lib/restforce/db/associations/has_one.rb +2 -2
- data/lib/restforce/db/associator.rb +104 -0
- data/lib/restforce/db/cleaner.rb +3 -15
- data/lib/restforce/db/collector.rb +2 -4
- data/lib/restforce/db/initializer.rb +10 -10
- data/lib/restforce/db/record_types/active_record.rb +6 -7
- data/lib/restforce/db/record_types/base.rb +0 -2
- data/lib/restforce/db/record_types/salesforce.rb +6 -6
- data/lib/restforce/db/runner.rb +8 -8
- data/lib/restforce/db/runner_cache.rb +93 -0
- data/lib/restforce/db/version.rb +1 -1
- data/lib/restforce/db/worker.rb +23 -9
- data/lib/restforce/db.rb +2 -0
- data/restforce-db.gemspec +3 -3
- data/test/cassettes/Restforce_DB_Associations_BelongsTo/with_an_inverse_mapping/_lookups/returns_a_hash_of_the_associated_records_lookup_IDs.yml +195 -0
- data/test/cassettes/Restforce_DB_Associations_BelongsTo/with_an_inverse_mapping/_lookups/when_there_is_currently_no_associated_record/returns_a_nil_lookup_value_in_the_hash.yml +119 -0
- data/test/cassettes/Restforce_DB_Associator/_run/given_a_BelongsTo_association/given_another_record_for_association/when_the_Salesforce_association_is_out_of_date/updates_the_association_ID_in_Salesforce.yml +421 -0
- data/test/cassettes/Restforce_DB_Associator/_run/given_a_BelongsTo_association/given_another_record_for_association/when_the_database_association_is_out_of_date/updates_the_associated_record_in_the_database.yml +459 -0
- data/test/cassettes/Restforce_DB_RecordTypes_Salesforce/{_each/loops_through_the_existing_records_in_Salesforce.yml → _all/returns_a_list_of_the_existing_records_in_Salesforce.yml} +0 -0
- data/test/lib/restforce/db/accumulator_test.rb +3 -0
- data/test/lib/restforce/db/association_cache_test.rb +8 -0
- data/test/lib/restforce/db/associations/belongs_to_test.rb +18 -0
- data/test/lib/restforce/db/associator_test.rb +82 -0
- data/test/lib/restforce/db/configuration_test.rb +3 -0
- data/test/lib/restforce/db/dsl_test.rb +2 -0
- data/test/lib/restforce/db/record_types/salesforce_test.rb +3 -5
- data/test/lib/restforce/db/runner_cache_test.rb +60 -0
- data/test/lib/restforce/db/runner_test.rb +3 -0
- data/test/lib/restforce/db/strategies/passive_test.rb +3 -0
- data/test/lib/restforce/db/strategy_test.rb +2 -0
- data/test/lib/restforce/db/tracker_test.rb +3 -0
- data/test/support/stub.rb +37 -0
- metadata +18 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34a3066aca21ed017edf09525c13198135e093e1
|
4
|
+
data.tar.gz: 1a16d555df7e28351425a1b3556361a203b3f0c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae15efd7ee63784a25e2a4946135d92ccb32a11950d377058e4f869f16130f4bc0e66b034507caf8dfba280e3c84dfa9d91c55ea0d37997449372a0169d7770a
|
7
|
+
data.tar.gz: 0a8268c35e4b99ae4586d51b5b69a34ae654c0a2939c07020133726fb6a0ea8ddbdc82cb623d75b3703ce27027fe009916ef109bce1d7f806fa0d49f79896062
|
@@ -10,8 +10,11 @@ module Restforce
|
|
10
10
|
attr_reader :cache
|
11
11
|
|
12
12
|
# Public: Initialize a new Restforce::DB::AssociationCache.
|
13
|
-
|
13
|
+
#
|
14
|
+
# record - An instance of ActiveRecord::Base (optional).
|
15
|
+
def initialize(record = nil)
|
14
16
|
@cache = Hash.new { |h, k| h[k] = [] }
|
17
|
+
self << record if record
|
15
18
|
end
|
16
19
|
|
17
20
|
# Public: Add a record to the cache.
|
@@ -14,10 +14,10 @@ module Restforce
|
|
14
14
|
#
|
15
15
|
# database_record - An instance of an ActiveRecord::Base subclass.
|
16
16
|
# salesforce_record - A Hashie::Mash representing a Salesforce object.
|
17
|
-
# cache - A Restforce::DB::AssociationCache.
|
17
|
+
# cache - A Restforce::DB::AssociationCache (optional).
|
18
18
|
#
|
19
19
|
# Returns an Array of constructed association records.
|
20
|
-
def build(database_record, salesforce_record, cache = AssociationCache.new)
|
20
|
+
def build(database_record, salesforce_record, cache = AssociationCache.new(database_record))
|
21
21
|
@cache = cache
|
22
22
|
|
23
23
|
lookups = {}
|
@@ -45,6 +45,30 @@ module Restforce
|
|
45
45
|
@cache = nil
|
46
46
|
end
|
47
47
|
|
48
|
+
# Public: Get a Hash of Lookup IDs for a specified database record,
|
49
|
+
# based on the current record for this association.
|
50
|
+
#
|
51
|
+
# database_record - An instance of an ActiveRecord::Base subclass.
|
52
|
+
#
|
53
|
+
# Returns a Hash.
|
54
|
+
def lookups(database_record)
|
55
|
+
ids = {}
|
56
|
+
|
57
|
+
for_mappings(database_record) do |mapping, lookup|
|
58
|
+
associated = database_record.association(name).reader
|
59
|
+
|
60
|
+
ids[lookup] =
|
61
|
+
if associated
|
62
|
+
# It's possible to define a belongs_to association in a Mapping
|
63
|
+
# for what is actually a one-to-many association on the
|
64
|
+
# ActiveRecord object.
|
65
|
+
Array(associated).first.send(mapping.lookup_column)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
ids
|
70
|
+
end
|
71
|
+
|
48
72
|
private
|
49
73
|
|
50
74
|
# Internal: Iterate through all relevant mappings for the target
|
@@ -65,7 +89,7 @@ module Restforce
|
|
65
89
|
# Internal: Get the Salesforce ID belonging to the associated record
|
66
90
|
# for a supplied instance. Must be implemented per-association.
|
67
91
|
#
|
68
|
-
# instance - A Restforce::DB::Instances::Base
|
92
|
+
# instance - A Restforce::DB::Instances::Base.
|
69
93
|
#
|
70
94
|
# Returns a String.
|
71
95
|
def associated_salesforce_id(instance)
|
@@ -14,17 +14,17 @@ module Restforce
|
|
14
14
|
#
|
15
15
|
# database_record - An instance of an ActiveRecord::Base subclass.
|
16
16
|
# salesforce_record - A Hashie::Mash representing a Salesforce object.
|
17
|
-
# cache - A Restforce::DB::AssociationCache.
|
17
|
+
# cache - A Restforce::DB::AssociationCache (optional).
|
18
18
|
#
|
19
19
|
# Returns an Array of constructed association records.
|
20
|
-
def build(database_record, salesforce_record, cache = AssociationCache.new)
|
20
|
+
def build(database_record, salesforce_record, cache = AssociationCache.new(database_record))
|
21
21
|
@cache = cache
|
22
22
|
|
23
23
|
target = target_mapping(database_record)
|
24
24
|
lookup_id = "#{lookup_field(target, database_record)} = '#{salesforce_record.Id}'"
|
25
25
|
|
26
26
|
records = []
|
27
|
-
target.salesforce_record_type.
|
27
|
+
target.salesforce_record_type.all(conditions: lookup_id).each do |instance|
|
28
28
|
records << construct_for(database_record, instance)
|
29
29
|
end
|
30
30
|
|
@@ -14,10 +14,10 @@ module Restforce
|
|
14
14
|
#
|
15
15
|
# database_record - An instance of an ActiveRecord::Base subclass.
|
16
16
|
# salesforce_record - A Hashie::Mash representing a Salesforce object.
|
17
|
-
# cache - A Restforce::DB::AssociationCache.
|
17
|
+
# cache - A Restforce::DB::AssociationCache (optional).
|
18
18
|
#
|
19
19
|
# Returns an Array of constructed association records.
|
20
|
-
def build(database_record, salesforce_record, cache = AssociationCache.new)
|
20
|
+
def build(database_record, salesforce_record, cache = AssociationCache.new(database_record))
|
21
21
|
@cache = cache
|
22
22
|
|
23
23
|
target = target_mapping(database_record)
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Restforce
|
2
|
+
|
3
|
+
module DB
|
4
|
+
|
5
|
+
# Restforce::DB::Associator is responsible for determining when one or more
|
6
|
+
# associations have been updated to point to a new Salesforce/database
|
7
|
+
# record, and propagate the modification to the opposite system when this
|
8
|
+
# occurs.
|
9
|
+
class Associator
|
10
|
+
|
11
|
+
# Public: Initialize a new Restforce::DB::Associator.
|
12
|
+
#
|
13
|
+
# mapping - A Restforce::DB::Mapping instance.
|
14
|
+
# runner - A Restforce::DB::Runner instance.
|
15
|
+
def initialize(mapping, runner = Runner.new)
|
16
|
+
@mapping = mapping
|
17
|
+
@runner = runner
|
18
|
+
end
|
19
|
+
|
20
|
+
# Public: Run the re-association process, pulling in records from
|
21
|
+
# Salesforce and the database to determine the most recently attached
|
22
|
+
# association, then propagating the change between systems.
|
23
|
+
#
|
24
|
+
# Returns nothing.
|
25
|
+
def run
|
26
|
+
return if belongs_to_associations.empty?
|
27
|
+
|
28
|
+
@runner.run(@mapping) do |run|
|
29
|
+
run.salesforce_instances.each { |instance| verify_associations(instance) }
|
30
|
+
run.database_instances.each { |instance| verify_associations(instance) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Internal: Ensure integrity between the lookup columns in Salesforce and
|
37
|
+
# the synchronized records in the database.
|
38
|
+
#
|
39
|
+
# instance - A Restforce::DB::Instances::Base.
|
40
|
+
#
|
41
|
+
# Returns nothing.
|
42
|
+
def verify_associations(instance)
|
43
|
+
database_instance, salesforce_instance =
|
44
|
+
case instance
|
45
|
+
when Restforce::DB::Instances::Salesforce
|
46
|
+
[@mapping.database_record_type.find(instance.id), instance]
|
47
|
+
when Restforce::DB::Instances::ActiveRecord
|
48
|
+
[instance, @mapping.salesforce_record_type.find(instance.id)]
|
49
|
+
end
|
50
|
+
|
51
|
+
return unless database_instance && salesforce_instance
|
52
|
+
sync_associations(database_instance, salesforce_instance)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Internal: Given a database record and corresponding Salesforce data,
|
56
|
+
# synchronize the record associations in whichever system has the least
|
57
|
+
# recent data.
|
58
|
+
#
|
59
|
+
# database_instance - A Restforce::DB::Instances::ActiveRecord.
|
60
|
+
# salesforce_instance - A Restforce::DB::Instances::Salesforce.
|
61
|
+
#
|
62
|
+
# Returns nothing.
|
63
|
+
def sync_associations(database_instance, salesforce_instance)
|
64
|
+
ids = belongs_to_association_ids(database_instance)
|
65
|
+
return if ids.all? { |field, id| salesforce_instance.record[field] == id }
|
66
|
+
|
67
|
+
if database_instance.last_update > salesforce_instance.last_update
|
68
|
+
salesforce_instance.update!(ids)
|
69
|
+
else
|
70
|
+
database_record = database_instance.record
|
71
|
+
belongs_to_associations.each do |association|
|
72
|
+
association.build(database_record, salesforce_instance.record)
|
73
|
+
end
|
74
|
+
database_record.save!
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Internal: Get a Hash of associated lookup IDs for the passed database
|
79
|
+
# record.
|
80
|
+
#
|
81
|
+
# database_instance - A Restforce::DB::Instances::ActiveRecord.
|
82
|
+
#
|
83
|
+
# Returns a Hash.
|
84
|
+
def belongs_to_association_ids(database_instance)
|
85
|
+
belongs_to_associations.inject({}) do |ids, association|
|
86
|
+
ids.merge(association.lookups(database_instance.record))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Internal: Get a list of the BelongsTo associations defined for the
|
91
|
+
# target mapping.
|
92
|
+
#
|
93
|
+
# Returns an Array of Restforce::DB::Association::BelongsTo objects.
|
94
|
+
def belongs_to_associations
|
95
|
+
@belongs_to_associations ||= @mapping.associations.select do |association|
|
96
|
+
association.is_a?(Restforce::DB::Associations::BelongsTo)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
data/lib/restforce/db/cleaner.rb
CHANGED
@@ -20,7 +20,7 @@ module Restforce
|
|
20
20
|
#
|
21
21
|
# Returns nothing.
|
22
22
|
def run
|
23
|
-
return if @strategy.passive?
|
23
|
+
return if @mapping.conditions.empty? || @strategy.passive?
|
24
24
|
@mapping.database_record_type.destroy_all(invalid_salesforce_ids)
|
25
25
|
end
|
26
26
|
|
@@ -40,15 +40,7 @@ module Restforce
|
|
40
40
|
#
|
41
41
|
# Returns an Array of IDs.
|
42
42
|
def all_salesforce_ids
|
43
|
-
|
44
|
-
|
45
|
-
@mapping.unscoped do |map|
|
46
|
-
@runner.run(map) do |run|
|
47
|
-
run.salesforce_records { |record| all_ids << record.id }
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
all_ids
|
43
|
+
@mapping.unscoped { valid_salesforce_ids }
|
52
44
|
end
|
53
45
|
|
54
46
|
# Internal: Get the IDs of the recently-modified Salesforce records which
|
@@ -56,13 +48,9 @@ module Restforce
|
|
56
48
|
#
|
57
49
|
# Returns an Array of IDs.
|
58
50
|
def valid_salesforce_ids
|
59
|
-
valid_ids = []
|
60
|
-
|
61
51
|
@runner.run(@mapping) do |run|
|
62
|
-
run.
|
52
|
+
run.salesforce_instances.map(&:id)
|
63
53
|
end
|
64
|
-
|
65
|
-
valid_ids
|
66
54
|
end
|
67
55
|
|
68
56
|
end
|
@@ -8,8 +8,6 @@ module Restforce
|
|
8
8
|
# locate recently-updated records and fetch their attributes.
|
9
9
|
class Collector
|
10
10
|
|
11
|
-
attr_reader :last_run
|
12
|
-
|
13
11
|
# Public: Initialize a new Restforce::DB::Collector.
|
14
12
|
#
|
15
13
|
# mapping - A Restforce::DB::Mapping instance.
|
@@ -30,8 +28,8 @@ module Restforce
|
|
30
28
|
@accumulated_changes = accumulator || accumulated_changes
|
31
29
|
|
32
30
|
@runner.run(@mapping) do |run|
|
33
|
-
run.
|
34
|
-
run.
|
31
|
+
run.salesforce_instances.each { |instance| accumulate(instance) }
|
32
|
+
run.database_instances.each { |instance| accumulate(instance) }
|
35
33
|
end
|
36
34
|
|
37
35
|
accumulated_changes
|
@@ -25,8 +25,8 @@ module Restforce
|
|
25
25
|
return if @strategy.passive?
|
26
26
|
|
27
27
|
@runner.run(@mapping) do |run|
|
28
|
-
run.
|
29
|
-
run.
|
28
|
+
run.salesforce_instances.each { |instance| create_in_database(instance) }
|
29
|
+
run.database_instances.each { |instance| create_in_salesforce(instance) }
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
@@ -36,24 +36,24 @@ module Restforce
|
|
36
36
|
# passed Salesforce record. Does nothing if the Salesforce record has
|
37
37
|
# already been synchronized into the system at least once.
|
38
38
|
#
|
39
|
-
#
|
39
|
+
# instance - A Restforce::DB::Instances::Salesforce.
|
40
40
|
#
|
41
41
|
# Returns nothing.
|
42
|
-
def create_in_database(
|
43
|
-
return unless @strategy.build?(
|
44
|
-
@mapping.database_record_type.create!(
|
42
|
+
def create_in_database(instance)
|
43
|
+
return unless @strategy.build?(instance)
|
44
|
+
@mapping.database_record_type.create!(instance)
|
45
45
|
end
|
46
46
|
|
47
47
|
# Internal: Attempt to create a partner record in Salesforce for the
|
48
48
|
# passed database record. Does nothing if the database record already has
|
49
49
|
# an associated Salesforce record.
|
50
50
|
#
|
51
|
-
#
|
51
|
+
# instance - A Restforce::DB::Instances::ActiveRecord.
|
52
52
|
#
|
53
53
|
# Returns nothing.
|
54
|
-
def create_in_salesforce(
|
55
|
-
return if
|
56
|
-
@mapping.salesforce_record_type.create!(
|
54
|
+
def create_in_salesforce(instance)
|
55
|
+
return if instance.synced?
|
56
|
+
@mapping.salesforce_record_type.create!(instance)
|
57
57
|
end
|
58
58
|
|
59
59
|
end
|
@@ -44,8 +44,8 @@ module Restforce
|
|
44
44
|
Instances::ActiveRecord.new(@record_type, record, @mapping)
|
45
45
|
end
|
46
46
|
|
47
|
-
# Public:
|
48
|
-
#
|
47
|
+
# Public: Get a collection of all modified ActiveRecord records of this
|
48
|
+
# type which match the passed criteria.
|
49
49
|
#
|
50
50
|
# options - A Hash of options which should be applied to the set of
|
51
51
|
# fetched records. Allowed options are:
|
@@ -54,15 +54,14 @@ module Restforce
|
|
54
54
|
# :after - A Time object defining the least recent update
|
55
55
|
# timestamp for which records should be returned.
|
56
56
|
#
|
57
|
-
#
|
58
|
-
|
59
|
-
def each(options = {})
|
57
|
+
# Returns an Array of Restforce::DB::Instances::ActiveRecord instances.
|
58
|
+
def all(options = {})
|
60
59
|
scope = @record_type.where("updated_at > synchronized_at OR synchronized_at IS NULL")
|
61
60
|
scope = scope.where("updated_at < ?", options[:before]) if options[:before]
|
62
61
|
scope = scope.where("updated_at >= ?", options[:after]) if options[:after]
|
63
62
|
|
64
|
-
scope.
|
65
|
-
|
63
|
+
scope.map do |record|
|
64
|
+
Instances::ActiveRecord.new(@record_type, record, @mapping)
|
66
65
|
end
|
67
66
|
end
|
68
67
|
|
@@ -47,7 +47,8 @@ module Restforce
|
|
47
47
|
first("Id = '#{id}'")
|
48
48
|
end
|
49
49
|
|
50
|
-
# Public:
|
50
|
+
# Public: Get a collection of all Salesforce records of this type which
|
51
|
+
# match the passed criteria.
|
51
52
|
#
|
52
53
|
# options - A Hash of options which should be applied to the set of
|
53
54
|
# fetched records. Allowed options are:
|
@@ -60,17 +61,16 @@ module Restforce
|
|
60
61
|
# :conditions - An Array of conditions to append to the lookup
|
61
62
|
# query.
|
62
63
|
#
|
63
|
-
#
|
64
|
-
|
65
|
-
def each(options = {})
|
64
|
+
# Returns an Array of Restforce::DB::Instances::Salesforce instances.
|
65
|
+
def all(options = {})
|
66
66
|
constraints = [
|
67
67
|
("SystemModstamp < #{options[:before].utc.iso8601}" if options[:before]),
|
68
68
|
("SystemModstamp >= #{options[:after].utc.iso8601}" if options[:after]),
|
69
69
|
*options[:conditions],
|
70
70
|
]
|
71
71
|
|
72
|
-
DB.client.query(query(*constraints)).
|
73
|
-
|
72
|
+
DB.client.query(query(*constraints)).map do |record|
|
73
|
+
Instances::Salesforce.new(@record_type, record, @mapping)
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
data/lib/restforce/db/runner.rb
CHANGED
@@ -19,6 +19,7 @@ module Restforce
|
|
19
19
|
def initialize(delay = 0, last_run_time = DB.last_run)
|
20
20
|
@delay = delay
|
21
21
|
@last_run = last_run_time
|
22
|
+
@cache = RunnerCache.new
|
22
23
|
end
|
23
24
|
|
24
25
|
# Public: Indicate that a new phase of the run is beginning. Updates the
|
@@ -26,6 +27,7 @@ module Restforce
|
|
26
27
|
#
|
27
28
|
# Returns the new run Time.
|
28
29
|
def tick!
|
30
|
+
@cache.reset
|
29
31
|
run_time = Time.now
|
30
32
|
|
31
33
|
@before = run_time - @delay
|
@@ -50,19 +52,17 @@ module Restforce
|
|
50
52
|
# Public: Iterate through recently-updated records for the Salesforce
|
51
53
|
# record type defined by the current mapping.
|
52
54
|
#
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
@mapping.salesforce_record_type.each(options) { |record| yield record }
|
55
|
+
# Returns an Enumerator yielding Restforce::DB::Instances::Salesforces.
|
56
|
+
def salesforce_instances
|
57
|
+
@cache.collection(@mapping, :salesforce_record_type, options)
|
57
58
|
end
|
58
59
|
|
59
60
|
# Public: Iterate through recently-updated records for the database model
|
60
61
|
# record type defined by the current mapping.
|
61
62
|
#
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
@mapping.database_record_type.each(options) { |record| yield record }
|
63
|
+
# Returns an Enumerator yielding Restforce::DB::Instances::ActiveRecords.
|
64
|
+
def database_instances
|
65
|
+
@cache.collection(@mapping, :database_record_type, options)
|
66
66
|
end
|
67
67
|
|
68
68
|
private
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Restforce
|
2
|
+
|
3
|
+
module DB
|
4
|
+
|
5
|
+
# Restforce::DB::RunnerCache serves as a means of caching the collections of
|
6
|
+
# recently-updated database and Salesforce instances for passed mappings.
|
7
|
+
# The general goal is to avoid making repetitive Salesforce API calls or
|
8
|
+
# database queries, and ensure a consistent list of objects during a
|
9
|
+
# synchronization run.
|
10
|
+
class RunnerCache
|
11
|
+
|
12
|
+
# Public: Initialize a new Restforce::DB::RunnerCache.
|
13
|
+
def initialize
|
14
|
+
reset
|
15
|
+
end
|
16
|
+
|
17
|
+
# Public: Iterate through the recently-updated instances of the specified
|
18
|
+
# type for the passed mapping. Memoizes the records if the collection has
|
19
|
+
# not previously been cached.
|
20
|
+
#
|
21
|
+
# mapping - A Restforce::DB::Mapping.
|
22
|
+
# record_type - A Symbol naming a mapping record type. Valid values are
|
23
|
+
# :salesforce_record_type or :database_record_type.
|
24
|
+
# options - A Hash of options to pass to `all` (optional).
|
25
|
+
#
|
26
|
+
# Returns an Array of Restforce::DB::Instances::Base.
|
27
|
+
def collection(mapping, record_type, options = {})
|
28
|
+
return cached_value(mapping, record_type) if cached?(mapping, record_type)
|
29
|
+
cache(mapping, record_type, mapping.send(record_type).all(options))
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Reset the cache. Should be invoked between runs to ensure that
|
33
|
+
# new options are respected.
|
34
|
+
#
|
35
|
+
# Returns nothing.
|
36
|
+
def reset
|
37
|
+
@cache = Hash.new { |h, k| h[k] = {} }
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Internal: Store the supplied value in the cache for the passed mapping
|
43
|
+
# and record type.
|
44
|
+
#
|
45
|
+
# mapping - A Restforce::DB::Mapping.
|
46
|
+
# record_type - A Symbol naming a mapping record type. Valid values are
|
47
|
+
# :salesforce_record_type or :database_record_type.
|
48
|
+
#
|
49
|
+
# Returns the cached value.
|
50
|
+
def cache(mapping, record_type, value)
|
51
|
+
@cache[record_type][key_for(mapping)] = value
|
52
|
+
end
|
53
|
+
|
54
|
+
# Internal: Get the cached collection for the passed mapping and record
|
55
|
+
# type.
|
56
|
+
#
|
57
|
+
# mapping - A Restforce::DB::Mapping.
|
58
|
+
# record_type - A Symbol naming a mapping record type. Valid values are
|
59
|
+
# :salesforce_record_type or :database_record_type.
|
60
|
+
#
|
61
|
+
# Returns nil or an Array.
|
62
|
+
def cached_value(mapping, record_type)
|
63
|
+
@cache[record_type][key_for(mapping)]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Internal: Have we cached a collection for the passed mapping and record
|
67
|
+
# type?
|
68
|
+
#
|
69
|
+
# mapping - A Restforce::DB::Mapping.
|
70
|
+
# record_type - A Symbol naming a mapping record type. Valid values are
|
71
|
+
# :salesforce_record_type or :database_record_type.
|
72
|
+
#
|
73
|
+
# Returns a Boolean.
|
74
|
+
def cached?(mapping, record_type)
|
75
|
+
!cached_value(mapping, record_type).nil?
|
76
|
+
end
|
77
|
+
|
78
|
+
# Internal: Get a unique key with enough information to look up the passed
|
79
|
+
# mapping in the cache. Scopes the mapping by its current list of
|
80
|
+
# conditions.
|
81
|
+
#
|
82
|
+
# mapping - A Restforce::DB::Mapping.
|
83
|
+
#
|
84
|
+
# Returns an Object.
|
85
|
+
def key_for(mapping)
|
86
|
+
[mapping, mapping.conditions]
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/lib/restforce/db/version.rb
CHANGED
data/lib/restforce/db/worker.rb
CHANGED
@@ -37,15 +37,7 @@ module Restforce
|
|
37
37
|
#
|
38
38
|
# Returns nothing.
|
39
39
|
def start
|
40
|
-
|
41
|
-
Thread.new { log "Exiting..." }
|
42
|
-
stop
|
43
|
-
end
|
44
|
-
|
45
|
-
trap("INT") do
|
46
|
-
Thread.new { log "Exiting..." }
|
47
|
-
stop
|
48
|
-
end
|
40
|
+
trap_signals
|
49
41
|
|
50
42
|
loop do
|
51
43
|
runtime = Benchmark.realtime { perform }
|
@@ -60,11 +52,22 @@ module Restforce
|
|
60
52
|
#
|
61
53
|
# Returns nothing.
|
62
54
|
def stop
|
55
|
+
Thread.new { log "Exiting..." }
|
63
56
|
@exit = true
|
64
57
|
end
|
65
58
|
|
66
59
|
private
|
67
60
|
|
61
|
+
# Internal: Configure the main loop to trap specific signals, triggering
|
62
|
+
# an exit once the loop completes.
|
63
|
+
#
|
64
|
+
# Return nothing.
|
65
|
+
def trap_signals
|
66
|
+
%w(TERM INT).each do |signal|
|
67
|
+
trap(signal) { stop }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
68
71
|
# Internal: Perform the synchronization loop, recording the time that the
|
69
72
|
# run is performed so that future runs can pick up where the last run
|
70
73
|
# left off.
|
@@ -79,6 +82,7 @@ module Restforce
|
|
79
82
|
task("PROPAGATING RECORDS", mapping) { propagate mapping }
|
80
83
|
task("CLEANING RECORDS", mapping) { clean mapping }
|
81
84
|
task("COLLECTING CHANGES", mapping) { collect mapping }
|
85
|
+
task("UPDATING ASSOCIATIONS", mapping) { associate mapping }
|
82
86
|
end
|
83
87
|
|
84
88
|
# NOTE: We can only perform the synchronization after all record
|
@@ -151,6 +155,16 @@ module Restforce
|
|
151
155
|
Collector.new(mapping, runner).run(@changes)
|
152
156
|
end
|
153
157
|
|
158
|
+
# Internal: Update the associated records and Salesforce lookups for
|
159
|
+
# records belonging to the passed mapping.
|
160
|
+
#
|
161
|
+
# mapping - A Restforce::DB::Mapping.
|
162
|
+
#
|
163
|
+
# Returns nothing.
|
164
|
+
def associate(mapping)
|
165
|
+
Associator.new(mapping, runner).run
|
166
|
+
end
|
167
|
+
|
154
168
|
# Internal: Apply the aggregated changes to the objects in both systems,
|
155
169
|
# according to the defined mappings.
|
156
170
|
#
|
data/lib/restforce/db.rb
CHANGED
@@ -28,9 +28,11 @@ require "restforce/db/strategies/always"
|
|
28
28
|
require "restforce/db/strategies/associated"
|
29
29
|
require "restforce/db/strategies/passive"
|
30
30
|
|
31
|
+
require "restforce/db/runner_cache"
|
31
32
|
require "restforce/db/runner"
|
32
33
|
|
33
34
|
require "restforce/db/accumulator"
|
35
|
+
require "restforce/db/associator"
|
34
36
|
require "restforce/db/attribute_map"
|
35
37
|
require "restforce/db/cleaner"
|
36
38
|
require "restforce/db/collector"
|
data/restforce-db.gemspec
CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
|
|
21
21
|
|
22
22
|
spec.files = `git ls-files`.split($RS)
|
23
23
|
spec.bindir = "exe"
|
24
|
-
spec.executables = spec.files.grep(
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
25
|
spec.require_paths = ["lib"]
|
26
26
|
|
27
27
|
spec.add_dependency "activerecord"
|
@@ -30,11 +30,11 @@ Gem::Specification.new do |spec|
|
|
30
30
|
|
31
31
|
spec.add_development_dependency "bundler", "~> 1.8"
|
32
32
|
spec.add_development_dependency "database_cleaner"
|
33
|
-
spec.add_development_dependency "minitest"
|
33
|
+
spec.add_development_dependency "minitest", "5.5.1"
|
34
34
|
spec.add_development_dependency "minitest-spec-expect"
|
35
35
|
spec.add_development_dependency "minitest-vcr"
|
36
36
|
spec.add_development_dependency "rake", "~> 10.0"
|
37
|
-
spec.add_development_dependency "rubocop"
|
37
|
+
spec.add_development_dependency "rubocop", ">= 0.30.0"
|
38
38
|
spec.add_development_dependency "sqlite3"
|
39
39
|
spec.add_development_dependency "webmock"
|
40
40
|
end
|