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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/restforce/db/association_cache.rb +4 -1
  3. data/lib/restforce/db/associations/belongs_to.rb +27 -3
  4. data/lib/restforce/db/associations/has_many.rb +3 -3
  5. data/lib/restforce/db/associations/has_one.rb +2 -2
  6. data/lib/restforce/db/associator.rb +104 -0
  7. data/lib/restforce/db/cleaner.rb +3 -15
  8. data/lib/restforce/db/collector.rb +2 -4
  9. data/lib/restforce/db/initializer.rb +10 -10
  10. data/lib/restforce/db/record_types/active_record.rb +6 -7
  11. data/lib/restforce/db/record_types/base.rb +0 -2
  12. data/lib/restforce/db/record_types/salesforce.rb +6 -6
  13. data/lib/restforce/db/runner.rb +8 -8
  14. data/lib/restforce/db/runner_cache.rb +93 -0
  15. data/lib/restforce/db/version.rb +1 -1
  16. data/lib/restforce/db/worker.rb +23 -9
  17. data/lib/restforce/db.rb +2 -0
  18. data/restforce-db.gemspec +3 -3
  19. data/test/cassettes/Restforce_DB_Associations_BelongsTo/with_an_inverse_mapping/_lookups/returns_a_hash_of_the_associated_records_lookup_IDs.yml +195 -0
  20. 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
  21. 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
  22. 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
  23. 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
  24. data/test/lib/restforce/db/accumulator_test.rb +3 -0
  25. data/test/lib/restforce/db/association_cache_test.rb +8 -0
  26. data/test/lib/restforce/db/associations/belongs_to_test.rb +18 -0
  27. data/test/lib/restforce/db/associator_test.rb +82 -0
  28. data/test/lib/restforce/db/configuration_test.rb +3 -0
  29. data/test/lib/restforce/db/dsl_test.rb +2 -0
  30. data/test/lib/restforce/db/record_types/salesforce_test.rb +3 -5
  31. data/test/lib/restforce/db/runner_cache_test.rb +60 -0
  32. data/test/lib/restforce/db/runner_test.rb +3 -0
  33. data/test/lib/restforce/db/strategies/passive_test.rb +3 -0
  34. data/test/lib/restforce/db/strategy_test.rb +2 -0
  35. data/test/lib/restforce/db/tracker_test.rb +3 -0
  36. data/test/support/stub.rb +37 -0
  37. metadata +18 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aa1372a2f8baf6d0af77a254f0360d6ad37aef11
4
- data.tar.gz: 323beb3762fbf77ef7aa79f0465924260d22432d
3
+ metadata.gz: 34a3066aca21ed017edf09525c13198135e093e1
4
+ data.tar.gz: 1a16d555df7e28351425a1b3556361a203b3f0c6
5
5
  SHA512:
6
- metadata.gz: ad8b9ed0abf1e713b416a1e352f2c65de949dde70d7ee08db7c3fb40240995a90f7ac4b912b8e34cefa625db2630fd942b40d0a9b61f15661138a69548ed1706
7
- data.tar.gz: 5d065ba0b3078f5b5f67c6f7cb8f1de520eb5049bde8cd31b2f73c5ef6cf029ff16ae9a7337e128dc1fae18828b2d54cbe028613b74511f33a62522376f772ce
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
- def initialize
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.each(conditions: lookup_id) do |instance|
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
@@ -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
- all_ids = []
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.salesforce_records { |record| valid_ids << record.id }
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.salesforce_records { |record| accumulate(record) }
34
- run.database_records { |record| accumulate(record) }
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.salesforce_records { |record| create_in_database(record) }
29
- run.database_records { |record| create_in_salesforce(record) }
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
- # record - A Restforce::DB::Instances::Salesforce.
39
+ # instance - A Restforce::DB::Instances::Salesforce.
40
40
  #
41
41
  # Returns nothing.
42
- def create_in_database(record)
43
- return unless @strategy.build?(record)
44
- @mapping.database_record_type.create!(record)
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
- # record - A Restforce::DB::Instances::ActiveRecord.
51
+ # instance - A Restforce::DB::Instances::ActiveRecord.
52
52
  #
53
53
  # Returns nothing.
54
- def create_in_salesforce(record)
55
- return if record.synced?
56
- @mapping.salesforce_record_type.create!(record)
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: Iterate through all recently-updated ActiveRecord records of
48
- # this type.
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
- # Yields a series of Restforce::DB::Instances::ActiveRecord instances.
58
- # Returns nothing.
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.find_each do |record|
65
- yield Instances::ActiveRecord.new(@record_type, record, @mapping)
63
+ scope.map do |record|
64
+ Instances::ActiveRecord.new(@record_type, record, @mapping)
66
65
  end
67
66
  end
68
67
 
@@ -8,8 +8,6 @@ module Restforce
8
8
  # models defined in the Restforce::DB::RecordTypes namespace.
9
9
  class Base
10
10
 
11
- include Enumerable
12
-
13
11
  # Public: Initialize a new Restforce::DB::RecordTypes::Base.
14
12
  #
15
13
  # record_type - The name or class of the system record type.
@@ -47,7 +47,8 @@ module Restforce
47
47
  first("Id = '#{id}'")
48
48
  end
49
49
 
50
- # Public: Iterate through all Salesforce records of this type.
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
- # Yields a series of Restforce::DB::Instances::Salesforce instances.
64
- # Returns nothing.
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)).each do |record|
73
- yield Instances::Salesforce.new(@record_type, record, @mapping)
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
 
@@ -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
- # Yields a series of Restforce::DB::Instances::Salesforce objects.
54
- # Returns nothing.
55
- def salesforce_records
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
- # Yields a series of Restforce::DB::Instances::ActiveRecord objects.
63
- # Returns nothing.
64
- def database_records
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
@@ -3,7 +3,7 @@ module Restforce
3
3
  # :nodoc:
4
4
  module DB
5
5
 
6
- VERSION = "1.2.9"
6
+ VERSION = "1.2.10"
7
7
 
8
8
  end
9
9
 
@@ -37,15 +37,7 @@ module Restforce
37
37
  #
38
38
  # Returns nothing.
39
39
  def start
40
- trap("TERM") do
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(/^exe\//) { |f| File.basename(f) }
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