restforce-db 1.2.9 → 1.2.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|