restforce-db 2.5.0 → 2.6.0

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 (19) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -0
  3. data/lib/restforce/db/associator.rb +21 -9
  4. data/lib/restforce/db/initializer.rb +4 -4
  5. data/lib/restforce/db/model.rb +52 -0
  6. data/lib/restforce/db/synchronizer.rb +12 -12
  7. data/lib/restforce/db/timestamp_cache.rb +1 -1
  8. data/lib/restforce/db/version.rb +1 -1
  9. data/lib/restforce/db/worker.rb +21 -22
  10. 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 +188 -112
  11. 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 +175 -99
  12. data/test/cassettes/Restforce_DB_Model/given_a_database_model_which_includes_the_module/_force_sync_/given_a_previously-synchronized_record_for_a_mapped_model/force-updates_both_synchronized_records.yml +288 -0
  13. data/test/cassettes/Restforce_DB_Model/given_a_database_model_which_includes_the_module/_force_sync_/given_an_unsynchronized_record_for_a_mapped_model/creates_a_matching_record_in_Salesforce.yml +251 -0
  14. data/test/cassettes/Restforce_DB_Worker/a_race_condition_during_synchronization/does_not_change_the_user-entered_name_on_the_database_record.yml +512 -0
  15. data/test/cassettes/Restforce_DB_Worker/a_race_condition_during_synchronization/overrides_the_stale-but-more-recent_name_on_the_Salesforce.yml +551 -0
  16. data/test/lib/restforce/db/model_test.rb +86 -14
  17. data/test/lib/restforce/db/timestamp_cache_test.rb +5 -4
  18. data/test/lib/restforce/db/worker_test.rb +63 -0
  19. metadata +7 -2
@@ -4,25 +4,97 @@ describe Restforce::DB::Model do
4
4
 
5
5
  configure!
6
6
 
7
- let(:database_model) { CustomObject }
8
- let(:salesforce_model) { "CustomObject__c" }
7
+ describe "given a database model which includes the module" do
8
+ let(:database_model) { CustomObject }
9
+ let(:salesforce_model) { "CustomObject__c" }
9
10
 
10
- before do
11
- database_model.send(:include, Restforce::DB::Model)
12
- end
13
-
14
- describe ".sync_with" do
15
11
  before do
16
- database_model.sync_with(salesforce_model) do
17
- maps(
18
- name: "Name",
19
- example: "Example_Field__c",
20
- )
12
+ database_model.send(:include, Restforce::DB::Model)
13
+ end
14
+
15
+ describe ".sync_with" do
16
+ before do
17
+ database_model.sync_with(salesforce_model) do
18
+ maps(
19
+ name: "Name",
20
+ example: "Example_Field__c",
21
+ )
22
+ end
23
+ end
24
+
25
+ it "adds a mapping to the global Restforce::DB::Registry" do
26
+ expect(Restforce::DB::Registry[database_model]).to_not_be :empty?
21
27
  end
22
28
  end
23
29
 
24
- it "adds a mapping to the global Restforce::DB::Registry" do
25
- expect(Restforce::DB::Registry[database_model]).to_not_be :empty?
30
+ describe "#force_sync!", :vcr do
31
+ let(:mapping) { Restforce::DB::Registry[database_model].first }
32
+
33
+ before do
34
+ database_model.sync_with(salesforce_model) do
35
+ maps(
36
+ name: "Name",
37
+ example: "Example_Field__c",
38
+ )
39
+ end
40
+ end
41
+
42
+ describe "given an unpersisted record for a mapped model" do
43
+ let(:record) { database_model.new }
44
+
45
+ it "does nothing" do
46
+ expect(record.force_sync!).to_equal false
47
+ end
48
+ end
49
+
50
+ describe "given an unsynchronized record for a mapped model" do
51
+ let(:record) { database_model.create!(attributes) }
52
+ let(:attributes) do
53
+ {
54
+ name: "Frederick's Flip-flop",
55
+ example: "Yes, we only have the left one.",
56
+ }
57
+ end
58
+
59
+ before do
60
+ expect(record.force_sync!).to_equal true
61
+ Salesforce.records << [salesforce_model, record.salesforce_id]
62
+ end
63
+
64
+ it "creates a matching record in Salesforce" do
65
+ salesforce_record = mapping.salesforce_record_type.find(
66
+ record.salesforce_id,
67
+ ).record
68
+
69
+ expect(salesforce_record.Name).to_equal attributes[:name]
70
+ expect(salesforce_record.Example_Field__c).to_equal attributes[:example]
71
+ end
72
+ end
73
+
74
+ describe "given a previously-synchronized record for a mapped model" do
75
+ let(:attributes) do
76
+ {
77
+ name: "Sally's Seashells",
78
+ example: "She sells them down by the seashore.",
79
+ }
80
+ end
81
+ let(:salesforce_id) do
82
+ Salesforce.create!(
83
+ salesforce_model,
84
+ "Name" => attributes[:name],
85
+ "Example_Field__c" => attributes[:example],
86
+ )
87
+ end
88
+ let(:record) { database_model.create!(attributes.merge(salesforce_id: salesforce_id)) }
89
+
90
+ it "force-updates both synchronized records" do
91
+ record.update!(name: "Sarah's Seagulls")
92
+ expect(record.force_sync!).to_equal true
93
+
94
+ salesforce_record = mapping.salesforce_record_type.find(salesforce_id).record
95
+ expect(salesforce_record.Name).to_equal record.name
96
+ end
97
+ end
26
98
  end
27
99
  end
28
100
 
@@ -8,8 +8,9 @@ describe Restforce::DB::TimestampCache do
8
8
 
9
9
  let(:timestamp) { Time.now }
10
10
  let(:id) { "some-id" }
11
- let(:instance_class) { Struct.new(:id, :last_update) }
12
- let(:instance) { instance_class.new(id, timestamp) }
11
+ let(:record_type) { "CustomObject__c" }
12
+ let(:instance_class) { Struct.new(:id, :record_type, :last_update) }
13
+ let(:instance) { instance_class.new(id, record_type, timestamp) }
13
14
 
14
15
  describe "#cache_timestamp" do
15
16
  before { cache.cache_timestamp instance }
@@ -20,7 +21,7 @@ describe Restforce::DB::TimestampCache do
20
21
  end
21
22
 
22
23
  describe "#changed?" do
23
- let(:new_instance) { instance_class.new(id, timestamp) }
24
+ let(:new_instance) { instance_class.new(id, record_type, timestamp) }
24
25
 
25
26
  describe "when the passed instance was not internally updated" do
26
27
  before do
@@ -57,7 +58,7 @@ describe Restforce::DB::TimestampCache do
57
58
  end
58
59
 
59
60
  describe "and a stale timestamp is cached" do
60
- let(:new_instance) { instance_class.new(id, timestamp + 1) }
61
+ let(:new_instance) { instance_class.new(id, record_type, timestamp + 1) }
61
62
  before { cache.cache_timestamp instance }
62
63
 
63
64
  it "returns true" do
@@ -0,0 +1,63 @@
1
+ require_relative "../../../test_helper"
2
+
3
+ describe Restforce::DB::Worker do
4
+
5
+ configure!
6
+ mappings!
7
+
8
+ let(:worker) { Restforce::DB::Worker.new(delay: 0) }
9
+ let(:runner) { worker.send(:runner) }
10
+
11
+ describe "a race condition during synchronization", vcr: { match_requests_on: [:method, VCR.request_matchers.uri_without_param(:q)] } do
12
+ let(:database_record) { mapping.database_model.create! }
13
+ let(:new_name) { "A New User-Entered Name" }
14
+
15
+ before do
16
+ # 0. A record is added to the database.
17
+ database_record
18
+
19
+ # 1. The first loop runs
20
+
21
+ ## 1b. The record is synced to Salesforce.
22
+ worker.send :reset!
23
+ worker.send :propagate, mapping
24
+ expect(database_record.reload).to_be :salesforce_id?
25
+ Salesforce.records << [salesforce_model, database_record.salesforce_id]
26
+
27
+ ## 1c. The database record is updated (externally) by a user.
28
+ database_record.update! name: new_name
29
+
30
+ # We stub `last_update` to get around issues with VCR's cached timestamp;
31
+ # we need the Salesforce record timestamp to be contemporary with this
32
+ # test run.
33
+ Restforce::DB::Instances::Salesforce.stub_any_instance(:last_update, Time.now) do
34
+
35
+ ## 1d. The record in Salesforce is touched by another mapping.
36
+ salesforce_instance = mapping.salesforce_record_type.find(
37
+ database_record.salesforce_id,
38
+ )
39
+ salesforce_instance.update! "Name" => "A Stale Synchronized Name"
40
+ runner.cache_timestamp salesforce_instance
41
+
42
+ # 2. The second loop runs.
43
+ # We sleep here to ensure we pick up our manual changes.
44
+ sleep 1 if VCR.current_cassette.recording?
45
+ worker.send :reset!
46
+ worker.send :collect, mapping
47
+ worker.send :synchronize, mapping
48
+ end
49
+ end
50
+
51
+ it "does not change the user-entered name on the database record" do
52
+ expect(database_record.reload.name).to_equal new_name
53
+ end
54
+
55
+ it "overrides the stale-but-more-recent name on the Salesforce" do
56
+ salesforce_instance = mapping.salesforce_record_type.find(
57
+ database_record.salesforce_id,
58
+ )
59
+
60
+ expect(salesforce_instance.record.Name).to_equal new_name
61
+ end
62
+ end
63
+ 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.5.0
4
+ version: 2.6.0
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-06-15 00:00:00.000000000 Z
11
+ date: 2015-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -298,6 +298,8 @@ files:
298
298
  - test/cassettes/Restforce_DB_Instances_Salesforce/_update_/updates_the_record_in_Salesforce_with_the_passed_attributes.yml
299
299
  - test/cassettes/Restforce_DB_Instances_Salesforce/_updated_internally_/when_another_user_made_the_last_change/returns_false.yml
300
300
  - test/cassettes/Restforce_DB_Instances_Salesforce/_updated_internally_/when_our_client_made_the_last_change/returns_true.yml
301
+ - test/cassettes/Restforce_DB_Model/given_a_database_model_which_includes_the_module/_force_sync_/given_a_previously-synchronized_record_for_a_mapped_model/force-updates_both_synchronized_records.yml
302
+ - test/cassettes/Restforce_DB_Model/given_a_database_model_which_includes_the_module/_force_sync_/given_an_unsynchronized_record_for_a_mapped_model/creates_a_matching_record_in_Salesforce.yml
301
303
  - test/cassettes/Restforce_DB_RecordTypes_Salesforce/_all/returns_a_list_of_the_existing_records_in_Salesforce.yml
302
304
  - test/cassettes/Restforce_DB_RecordTypes_Salesforce/_create_/creates_a_record_in_Salesforce_from_the_passed_database_record_s_attributes.yml
303
305
  - test/cassettes/Restforce_DB_RecordTypes_Salesforce/_create_/updates_the_database_record_with_the_Salesforce_record_s_ID.yml
@@ -317,6 +319,8 @@ files:
317
319
  - test/cassettes/Restforce_DB_Synchronizer/_run/given_a_Salesforce_record_with_an_associated_database_record/when_the_changes_are_current/updates_the_database_record.yml
318
320
  - test/cassettes/Restforce_DB_Synchronizer/_run/given_a_Salesforce_record_with_an_associated_database_record/when_the_changes_are_current/updates_the_salesforce_record.yml
319
321
  - test/cassettes/Restforce_DB_Synchronizer/_run/given_a_Salesforce_record_with_no_associated_database_record/does_nothing_for_this_specific_mapping.yml
322
+ - test/cassettes/Restforce_DB_Worker/a_race_condition_during_synchronization/does_not_change_the_user-entered_name_on_the_database_record.yml
323
+ - test/cassettes/Restforce_DB_Worker/a_race_condition_during_synchronization/overrides_the_stale-but-more-recent_name_on_the_Salesforce.yml
320
324
  - test/lib/restforce/db/accumulator_test.rb
321
325
  - test/lib/restforce/db/adapter_test.rb
322
326
  - test/lib/restforce/db/association_cache_test.rb
@@ -347,6 +351,7 @@ files:
347
351
  - test/lib/restforce/db/synchronizer_test.rb
348
352
  - test/lib/restforce/db/timestamp_cache_test.rb
349
353
  - test/lib/restforce/db/tracker_test.rb
354
+ - test/lib/restforce/db/worker_test.rb
350
355
  - test/lib/restforce/db_test.rb
351
356
  - test/support/active_record.rb
352
357
  - test/support/database_cleaner.rb