restforce-db 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/file_daemon.rb +42 -0
  3. data/lib/restforce/db/accumulator.rb +41 -0
  4. data/lib/restforce/db/attribute_map.rb +132 -0
  5. data/lib/restforce/db/collector.rb +79 -0
  6. data/lib/restforce/db/initializer.rb +62 -0
  7. data/lib/restforce/db/instances/base.rb +1 -14
  8. data/lib/restforce/db/instances/salesforce.rb +7 -0
  9. data/lib/restforce/db/mapping.rb +33 -79
  10. data/lib/restforce/db/record_types/base.rb +0 -30
  11. data/lib/restforce/db/runner.rb +80 -0
  12. data/lib/restforce/db/synchronizer.rb +29 -37
  13. data/lib/restforce/db/version.rb +1 -1
  14. data/lib/restforce/db/worker.rb +53 -40
  15. data/lib/restforce/db.rb +6 -0
  16. data/test/cassettes/{Restforce_DB_Synchronizer/_run/given_a_Salesforce_record_with_an_associated_database_record/when_synchronization_is_stale/updates_the_database_record.yml → Restforce_DB_Collector/_run/given_a_Salesforce_record_with_an_associated_database_record/returns_the_attributes_from_both_records.yml} +36 -36
  17. data/test/cassettes/Restforce_DB_Collector/_run/given_an_existing_Salesforce_record/returns_the_attributes_from_the_Salesforce_record.yml +197 -0
  18. data/test/cassettes/Restforce_DB_Collector/_run/given_an_existing_database_record/returns_the_attributes_from_the_database_record.yml +81 -0
  19. data/test/cassettes/Restforce_DB_Initializer/_run/given_an_existing_Salesforce_record/for_a_non-root_mapping/does_not_create_a_database_record.yml +119 -0
  20. data/test/cassettes/{Restforce_DB_Synchronizer/_run/given_a_Salesforce_record_with_an_associated_database_record/when_synchronization_is_up-to-date/does_not_update_the_database_record.yml → Restforce_DB_Initializer/_run/given_an_existing_Salesforce_record/for_a_root_mapping/creates_a_matching_database_record.yml} +28 -28
  21. data/test/cassettes/{Restforce_DB_Synchronizer/_run/given_an_existing_database_record → Restforce_DB_Initializer/_run/given_an_existing_database_record/for_a_root_mapping}/populates_Salesforce_with_the_new_record.yml +44 -44
  22. data/test/cassettes/{Restforce_DB_Synchronizer/_run/given_an_existing_Salesforce_record/for_a_non-root_mapping/does_not_create_a_database_record.yml → Restforce_DB_Instances_Salesforce/_synced_/when_a_matching_database_record_exists/returns_true.yml} +30 -30
  23. data/test/cassettes/{Restforce_DB_Synchronizer/_run/given_an_existing_Salesforce_record/for_a_root_mapping/creates_a_matching_database_record.yml → Restforce_DB_Instances_Salesforce/_synced_/when_no_matching_database_record_exists/returns_false.yml} +30 -30
  24. data/test/cassettes/Restforce_DB_Synchronizer/_run/given_a_Salesforce_record_with_an_associated_database_record/updates_the_database_record.yml +194 -0
  25. data/test/cassettes/Restforce_DB_Synchronizer/_run/given_a_Salesforce_record_with_an_associated_database_record/updates_the_salesforce_record.yml +233 -0
  26. data/test/lib/restforce/db/accumulator_test.rb +71 -0
  27. data/test/lib/restforce/db/attribute_map_test.rb +70 -0
  28. data/test/lib/restforce/db/collector_test.rb +91 -0
  29. data/test/lib/restforce/db/initializer_test.rb +92 -0
  30. data/test/lib/restforce/db/instances/active_record_test.rb +0 -13
  31. data/test/lib/restforce/db/instances/salesforce_test.rb +20 -13
  32. data/test/lib/restforce/db/mapping_test.rb +1 -37
  33. data/test/lib/restforce/db/record_types/active_record_test.rb +0 -40
  34. data/test/lib/restforce/db/runner_test.rb +40 -0
  35. data/test/lib/restforce/db/synchronizer_test.rb +26 -86
  36. metadata +23 -7
@@ -0,0 +1,233 @@
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
+ - Mon, 06 Apr 2015 19:27:32 GMT
25
+ Set-Cookie:
26
+ - BrowserId=H_ENFEUGR5WZTS-rk624sw;Path=/;Domain=.salesforce.com;Expires=Fri,
27
+ 05-Jun-2015 19:27:32 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":"1428348452182","token_type":"Bearer","instance_url":"https://<host>","signature":"P3OMePrj1WH7novmwXpFbmqH0DW5LbNUf9WsrRrMAUA=","access_token":"00D1a000000H3O9!AQ4AQEol4wrcAcAfVF3HH0vTJXCmqHaqBZX0.AfMcJFa2P3Ssl68tDLqC_BJKakn3tKiFCy4SzendJ0gWnbHWkqrzvFuNH6B"}'
41
+ http_version:
42
+ recorded_at: Mon, 06 Apr 2015 19:27:32 GMT
43
+ - request:
44
+ method: post
45
+ uri: https://<host>/services/data/v26.0/sobjects/CustomObject__c
46
+ body:
47
+ encoding: UTF-8
48
+ string: '{"Name":"Custom object","Example_Field__c":"Some sample text"}'
49
+ headers:
50
+ User-Agent:
51
+ - Faraday v0.9.1
52
+ Content-Type:
53
+ - application/json
54
+ Authorization:
55
+ - OAuth 00D1a000000H3O9!AQ4AQEol4wrcAcAfVF3HH0vTJXCmqHaqBZX0.AfMcJFa2P3Ssl68tDLqC_BJKakn3tKiFCy4SzendJ0gWnbHWkqrzvFuNH6B
56
+ Accept-Encoding:
57
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
58
+ Accept:
59
+ - "*/*"
60
+ response:
61
+ status:
62
+ code: 201
63
+ message: Created
64
+ headers:
65
+ Date:
66
+ - Mon, 06 Apr 2015 19:27:33 GMT
67
+ Set-Cookie:
68
+ - BrowserId=CYLdStRyQ7-Gcf0PlAt6tg;Path=/;Domain=.salesforce.com;Expires=Fri,
69
+ 05-Jun-2015 19:27:33 GMT
70
+ Expires:
71
+ - Thu, 01 Jan 1970 00:00:00 GMT
72
+ Sforce-Limit-Info:
73
+ - api-usage=53/15000
74
+ Location:
75
+ - "/services/data/v26.0/sobjects/CustomObject__c/a001a000001LHpaAAG"
76
+ Content-Type:
77
+ - application/json;charset=UTF-8
78
+ Transfer-Encoding:
79
+ - chunked
80
+ body:
81
+ encoding: ASCII-8BIT
82
+ string: '{"id":"a001a000001LHpaAAG","success":true,"errors":[]}'
83
+ http_version:
84
+ recorded_at: Mon, 06 Apr 2015 19:27:33 GMT
85
+ - request:
86
+ method: get
87
+ uri: https://<host>/services/data/v26.0/query?q=select%20Id,%20SystemModstamp,%20Name,%20Example_Field__c%20from%20CustomObject__c%20where%20Id%20=%20%27a001a000001LHpaAAG%27
88
+ body:
89
+ encoding: US-ASCII
90
+ string: ''
91
+ headers:
92
+ User-Agent:
93
+ - Faraday v0.9.1
94
+ Authorization:
95
+ - OAuth 00D1a000000H3O9!AQ4AQEol4wrcAcAfVF3HH0vTJXCmqHaqBZX0.AfMcJFa2P3Ssl68tDLqC_BJKakn3tKiFCy4SzendJ0gWnbHWkqrzvFuNH6B
96
+ Accept-Encoding:
97
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
98
+ Accept:
99
+ - "*/*"
100
+ response:
101
+ status:
102
+ code: 200
103
+ message: OK
104
+ headers:
105
+ Date:
106
+ - Mon, 06 Apr 2015 19:27:34 GMT
107
+ Set-Cookie:
108
+ - BrowserId=_tii4-GdTbyU69ifAuYSlw;Path=/;Domain=.salesforce.com;Expires=Fri,
109
+ 05-Jun-2015 19:27:34 GMT
110
+ Expires:
111
+ - Thu, 01 Jan 1970 00:00:00 GMT
112
+ Sforce-Limit-Info:
113
+ - api-usage=53/15000
114
+ Content-Type:
115
+ - application/json;charset=UTF-8
116
+ Transfer-Encoding:
117
+ - chunked
118
+ body:
119
+ encoding: ASCII-8BIT
120
+ string: '{"totalSize":1,"done":true,"records":[{"attributes":{"type":"CustomObject__c","url":"/services/data/v26.0/sobjects/CustomObject__c/a001a000001LHpaAAG"},"Id":"a001a000001LHpaAAG","SystemModstamp":"2015-04-06T19:27:33.000+0000","Name":"Custom
121
+ object","Example_Field__c":"Some sample text"}]}'
122
+ http_version:
123
+ recorded_at: Mon, 06 Apr 2015 19:27:34 GMT
124
+ - request:
125
+ method: patch
126
+ uri: https://<host>/services/data/v26.0/sobjects/CustomObject__c/a001a000001LHpaAAG
127
+ body:
128
+ encoding: UTF-8
129
+ string: '{"Name":"Some new name","Example_Field__c":"New sample text"}'
130
+ headers:
131
+ User-Agent:
132
+ - Faraday v0.9.1
133
+ Content-Type:
134
+ - application/json
135
+ Authorization:
136
+ - OAuth 00D1a000000H3O9!AQ4AQEol4wrcAcAfVF3HH0vTJXCmqHaqBZX0.AfMcJFa2P3Ssl68tDLqC_BJKakn3tKiFCy4SzendJ0gWnbHWkqrzvFuNH6B
137
+ Accept-Encoding:
138
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
139
+ Accept:
140
+ - "*/*"
141
+ response:
142
+ status:
143
+ code: 204
144
+ message: No Content
145
+ headers:
146
+ Date:
147
+ - Mon, 06 Apr 2015 19:27:35 GMT
148
+ Set-Cookie:
149
+ - BrowserId=w1GFuFqdRyG3lFp9_6eHRw;Path=/;Domain=.salesforce.com;Expires=Fri,
150
+ 05-Jun-2015 19:27:35 GMT
151
+ Expires:
152
+ - Thu, 01 Jan 1970 00:00:00 GMT
153
+ Sforce-Limit-Info:
154
+ - api-usage=53/15000
155
+ body:
156
+ encoding: UTF-8
157
+ string: ''
158
+ http_version:
159
+ recorded_at: Mon, 06 Apr 2015 19:27:35 GMT
160
+ - request:
161
+ method: get
162
+ uri: https://<host>/services/data/v26.0/query?q=select%20Id,%20SystemModstamp,%20Name,%20Example_Field__c%20from%20CustomObject__c%20where%20Id%20=%20%27a001a000001LHpaAAG%27
163
+ body:
164
+ encoding: US-ASCII
165
+ string: ''
166
+ headers:
167
+ User-Agent:
168
+ - Faraday v0.9.1
169
+ Authorization:
170
+ - OAuth 00D1a000000H3O9!AQ4AQEol4wrcAcAfVF3HH0vTJXCmqHaqBZX0.AfMcJFa2P3Ssl68tDLqC_BJKakn3tKiFCy4SzendJ0gWnbHWkqrzvFuNH6B
171
+ Accept-Encoding:
172
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
173
+ Accept:
174
+ - "*/*"
175
+ response:
176
+ status:
177
+ code: 200
178
+ message: OK
179
+ headers:
180
+ Date:
181
+ - Mon, 06 Apr 2015 19:27:36 GMT
182
+ Set-Cookie:
183
+ - BrowserId=QeOpfDAmRk2Do-IQgxE7AQ;Path=/;Domain=.salesforce.com;Expires=Fri,
184
+ 05-Jun-2015 19:27:36 GMT
185
+ Expires:
186
+ - Thu, 01 Jan 1970 00:00:00 GMT
187
+ Sforce-Limit-Info:
188
+ - api-usage=53/15000
189
+ Content-Type:
190
+ - application/json;charset=UTF-8
191
+ Transfer-Encoding:
192
+ - chunked
193
+ body:
194
+ encoding: ASCII-8BIT
195
+ string: '{"totalSize":1,"done":true,"records":[{"attributes":{"type":"CustomObject__c","url":"/services/data/v26.0/sobjects/CustomObject__c/a001a000001LHpaAAG"},"Id":"a001a000001LHpaAAG","SystemModstamp":"2015-04-06T19:27:35.000+0000","Name":"Some
196
+ new name","Example_Field__c":"New sample text"}]}'
197
+ http_version:
198
+ recorded_at: Mon, 06 Apr 2015 19:27:36 GMT
199
+ - request:
200
+ method: delete
201
+ uri: https://<host>/services/data/v26.0/sobjects/CustomObject__c/a001a000001LHpaAAG
202
+ body:
203
+ encoding: US-ASCII
204
+ string: ''
205
+ headers:
206
+ User-Agent:
207
+ - Faraday v0.9.1
208
+ Authorization:
209
+ - OAuth 00D1a000000H3O9!AQ4AQEol4wrcAcAfVF3HH0vTJXCmqHaqBZX0.AfMcJFa2P3Ssl68tDLqC_BJKakn3tKiFCy4SzendJ0gWnbHWkqrzvFuNH6B
210
+ Accept-Encoding:
211
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
212
+ Accept:
213
+ - "*/*"
214
+ response:
215
+ status:
216
+ code: 204
217
+ message: No Content
218
+ headers:
219
+ Date:
220
+ - Mon, 06 Apr 2015 19:27:37 GMT
221
+ Set-Cookie:
222
+ - BrowserId=3oZtqDLySCS_3xvQ0quJiQ;Path=/;Domain=.salesforce.com;Expires=Fri,
223
+ 05-Jun-2015 19:27:37 GMT
224
+ Expires:
225
+ - Thu, 01 Jan 1970 00:00:00 GMT
226
+ Sforce-Limit-Info:
227
+ - api-usage=53/15000
228
+ body:
229
+ encoding: UTF-8
230
+ string: ''
231
+ http_version:
232
+ recorded_at: Mon, 06 Apr 2015 19:27:37 GMT
233
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,71 @@
1
+ require_relative "../../../test_helper"
2
+
3
+ describe Restforce::DB::Accumulator do
4
+ let(:accumulator) { Restforce::DB::Accumulator.new }
5
+
6
+ describe "#store" do
7
+ let(:timestamp) { Time.now }
8
+ let(:changes) { { one_fish: "Two Fish", red_fish: "Blue Fish" } }
9
+
10
+ before do
11
+ accumulator.store(timestamp, changes)
12
+ end
13
+
14
+ it "stores the passed changeset in the accumulator" do
15
+ expect(accumulator[timestamp]).to_equal changes
16
+ end
17
+ end
18
+
19
+ describe "#attributes" do
20
+ let(:timestamp) { Time.now }
21
+ let(:changes) { { one_fish: "Two Fish", red_fish: "Blue Fish" } }
22
+
23
+ before do
24
+ accumulator.store(timestamp, changes)
25
+ end
26
+
27
+ it "returns the attributes from the recorded changeset" do
28
+ expect(accumulator.attributes).to_equal changes
29
+ end
30
+
31
+ it "combines multiple changesets" do
32
+ additional_changes = { hootie: "Blow Fish" }
33
+
34
+ accumulator.store Time.now, additional_changes
35
+ expect(accumulator.attributes).to_equal changes.merge(additional_changes)
36
+ end
37
+
38
+ describe "with conflicting changesets" do
39
+ let(:new_changes) { { one_fish: "No Fish", red_fish: "Glow Fish" } }
40
+
41
+ it "respects the most recently updated values" do
42
+ accumulator.store timestamp + 1, new_changes
43
+ expect(accumulator.attributes).to_equal new_changes
44
+ end
45
+
46
+ it "ignores less recently updated values" do
47
+ accumulator.store timestamp - 1, new_changes
48
+ expect(accumulator.attributes).to_equal changes
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "#diff" do
54
+ let(:attributes) { { wocket: "Pocket", jertain: "Curtain", zelf: "Shelf" } }
55
+
56
+ before do
57
+ accumulator.store(Time.now, attributes)
58
+ end
59
+
60
+ it "returns the differences from the attributes hash" do
61
+ expect(accumulator.diff(wocket: "Locket", zelf: "Belfrey")).to_equal(
62
+ wocket: "Pocket",
63
+ zelf: "Shelf",
64
+ )
65
+ end
66
+
67
+ it "ignores attributes not set in the Accumulator" do
68
+ expect(accumulator.diff(yottle: "Bottle")).to_be :empty?
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,70 @@
1
+ require_relative "../../../test_helper"
2
+
3
+ describe Restforce::DB::AttributeMap do
4
+
5
+ configure!
6
+
7
+ let(:database_model) { CustomObject }
8
+ let(:salesforce_model) { "CustomObject__c" }
9
+ let(:fields) do
10
+ {
11
+ column_one: "SF_Field_One__c",
12
+ column_two: "SF_Field_Two__c",
13
+ }
14
+ end
15
+
16
+ let(:attribute_map) { Restforce::DB::AttributeMap.new(database_model, salesforce_model, fields) }
17
+
18
+ describe "#attributes" do
19
+ let(:mapping) { Restforce::DB::Mapping.new(database_model, salesforce_model, fields: fields) }
20
+
21
+ it "builds a normalized Hash of database attribute values" do
22
+ attributes = attribute_map.attributes(database_model) do |attribute|
23
+ expect(mapping.database_fields.include?(attribute)).to_equal true
24
+ attribute
25
+ end
26
+
27
+ expect(attributes.keys).to_equal(mapping.database_fields)
28
+ expect(attributes.values).to_equal(mapping.database_fields)
29
+ end
30
+
31
+ it "builds a normalized Hash of Salesforce field values" do
32
+ attributes = attribute_map.attributes(salesforce_model) do |attribute|
33
+ expect(mapping.salesforce_fields.include?(attribute)).to_equal true
34
+ attribute
35
+ end
36
+
37
+ expect(attributes.keys).to_equal(mapping.database_fields)
38
+ expect(attributes.values).to_equal(mapping.salesforce_fields)
39
+ end
40
+ end
41
+
42
+ describe "#convert" do
43
+ let(:attributes) { { column_one: "some value" } }
44
+
45
+ it "converts an attribute Hash to a Salesforce-compatible form" do
46
+ expect(attribute_map.convert(salesforce_model, attributes)).to_equal(
47
+ fields[attributes.keys.first] => attributes.values.first,
48
+ )
49
+ end
50
+
51
+ it "performs no special conversion for database columns" do
52
+ expect(attribute_map.convert(database_model, attributes)).to_equal(attributes)
53
+ end
54
+ end
55
+
56
+ describe "#convert_from_salesforce" do
57
+ let(:attributes) { { "SF_Field_One__c" => "some value" } }
58
+
59
+ it "converts an attribute Hash to a database-compatible form" do
60
+ expect(attribute_map.convert_from_salesforce(database_model, attributes)).to_equal(
61
+ fields.key(attributes.keys.first) => attributes.values.first,
62
+ )
63
+ end
64
+
65
+ it "performs no special conversion for Salesforce fields" do
66
+ expect(attribute_map.convert_from_salesforce(salesforce_model, attributes)).to_equal(attributes)
67
+ end
68
+ end
69
+
70
+ end
@@ -0,0 +1,91 @@
1
+ require_relative "../../../test_helper"
2
+
3
+ describe Restforce::DB::Collector do
4
+
5
+ configure!
6
+ mappings!
7
+
8
+ let(:collector) { Restforce::DB::Collector.new(mapping) }
9
+
10
+ describe "#run", vcr: { match_requests_on: [:method, VCR.request_matchers.uri_without_param(:q)] } do
11
+ let(:attributes) do
12
+ {
13
+ name: "Custom object",
14
+ example: "Some sample text",
15
+ }
16
+ end
17
+ let(:salesforce_id) do
18
+ Salesforce.create!(
19
+ salesforce_model,
20
+ mapping.convert(salesforce_model, attributes),
21
+ )
22
+ end
23
+ let(:key) { [salesforce_id, salesforce_model] }
24
+
25
+ subject { collector.run }
26
+
27
+ describe "given an existing Salesforce record" do
28
+ before { salesforce_id }
29
+
30
+ it "returns the attributes from the Salesforce record" do
31
+ record = mapping.salesforce_record_type.find(salesforce_id)
32
+
33
+ expect(subject[key]).to_equal(
34
+ record.last_update => {
35
+ "Name" => attributes[:name],
36
+ "Example_Field__c" => attributes[:example],
37
+ },
38
+ )
39
+ end
40
+ end
41
+
42
+ describe "given an existing database record" do
43
+ let(:salesforce_id) { "a001a000001E1vREAL" }
44
+ let(:database_metadata) { { salesforce_id: salesforce_id, synchronized_at: Time.now } }
45
+ let(:database_record) { database_model.create!(attributes.merge(database_metadata)) }
46
+
47
+ before { database_record }
48
+
49
+ it "returns the attributes from the database record" do
50
+ record = mapping.database_record_type.find(salesforce_id)
51
+
52
+ expect(subject[key]).to_equal(
53
+ record.last_update => {
54
+ "Name" => attributes[:name],
55
+ "Example_Field__c" => attributes[:example],
56
+ },
57
+ )
58
+ end
59
+ end
60
+
61
+ describe "given a Salesforce record with an associated database record" do
62
+ let(:database_attributes) do
63
+ {
64
+ name: "Some existing name",
65
+ example: "Some existing sample text",
66
+ }
67
+ end
68
+ let(:database_metadata) { { salesforce_id: salesforce_id, synchronized_at: Time.now } }
69
+ let(:database_record) { database_model.create!(database_attributes.merge(database_metadata)) }
70
+
71
+ before { database_record }
72
+
73
+ it "returns the attributes from both records" do
74
+ sf_record = mapping.salesforce_record_type.find(salesforce_id)
75
+ db_record = mapping.database_record_type.find(salesforce_id)
76
+
77
+ expect(subject[key]).to_equal(
78
+ sf_record.last_update => {
79
+ "Name" => attributes[:name],
80
+ "Example_Field__c" => attributes[:example],
81
+ },
82
+ db_record.last_update => {
83
+ "Name" => database_attributes[:name],
84
+ "Example_Field__c" => database_attributes[:example],
85
+ },
86
+ )
87
+ end
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,92 @@
1
+ require_relative "../../../test_helper"
2
+
3
+ describe Restforce::DB::Initializer do
4
+
5
+ configure!
6
+ mappings!
7
+
8
+ let(:initializer) { Restforce::DB::Initializer.new(mapping) }
9
+
10
+ describe "#run", vcr: { match_requests_on: [:method, VCR.request_matchers.uri_without_param(:q)] } do
11
+ let(:attributes) do
12
+ {
13
+ name: "Custom object",
14
+ example: "Some sample text",
15
+ }
16
+ end
17
+ let(:salesforce_id) do
18
+ Salesforce.create!(
19
+ salesforce_model,
20
+ mapping.convert(salesforce_model, attributes),
21
+ )
22
+ end
23
+
24
+ describe "given an existing Salesforce record" do
25
+ before do
26
+ salesforce_id
27
+ end
28
+
29
+ describe "for a root mapping" do
30
+ before do
31
+ initializer.run
32
+ end
33
+
34
+ it "creates a matching database record" do
35
+ record = database_model.last
36
+
37
+ expect(record.name).to_equal attributes[:name]
38
+ expect(record.example).to_equal attributes[:example]
39
+ expect(record.salesforce_id).to_equal salesforce_id
40
+ end
41
+ end
42
+
43
+ describe "for a non-root mapping" do
44
+ let(:through) { "SomeField__c" }
45
+
46
+ before do
47
+ initializer.run
48
+ end
49
+
50
+ it "does not create a database record" do
51
+ expect(database_model.last).to_be_nil
52
+ end
53
+ end
54
+ end
55
+
56
+ describe "given an existing database record" do
57
+ let(:database_record) { database_model.create!(attributes) }
58
+ let(:salesforce_id) { database_record.reload.salesforce_id }
59
+
60
+ before do
61
+ database_record
62
+ end
63
+
64
+ describe "for a root mapping" do
65
+ before do
66
+ initializer.run
67
+ Salesforce.records << [salesforce_model, salesforce_id]
68
+ end
69
+
70
+ it "populates Salesforce with the new record" do
71
+ record = mapping.salesforce_record_type.find(salesforce_id).record
72
+
73
+ expect(record.Name).to_equal attributes[:name]
74
+ expect(record.Example_Field__c).to_equal attributes[:example]
75
+ end
76
+ end
77
+
78
+ describe "for a non-root mapping" do
79
+ let(:through) { "SomeField__c" }
80
+
81
+ before do
82
+ initializer.run
83
+ end
84
+
85
+ it "does not create a Salesforce record" do
86
+ expect(salesforce_id).to_be_nil
87
+ end
88
+ end
89
+ end
90
+
91
+ end
92
+ end
@@ -29,17 +29,4 @@ describe Restforce::DB::Instances::ActiveRecord do
29
29
  expect(record.reload.synchronized_at).to_not_be_nil
30
30
  end
31
31
  end
32
-
33
- describe "#copy!" do
34
- let(:text) { "Copied text" }
35
- let(:copy_from) { Struct.new(:attributes).new(example: text) }
36
-
37
- before do
38
- instance.copy!(copy_from)
39
- end
40
-
41
- it "updates the record with the attributes from the copied object" do
42
- expect(record.example).to_equal text
43
- end
44
- end
45
32
  end
@@ -24,19 +24,6 @@ describe Restforce::DB::Instances::Salesforce do
24
24
  end
25
25
  end
26
26
 
27
- describe "#copy!", :vcr do
28
- let(:text) { "Copied text" }
29
- let(:copy_from) { Struct.new(:attributes).new(example: text) }
30
-
31
- before do
32
- instance.copy!(copy_from)
33
- end
34
-
35
- it "updates the record with the attributes from the copied object" do
36
- expect(instance.record.Example_Field__c).to_equal text
37
- end
38
- end
39
-
40
27
  describe "#last_update" do
41
28
  let(:timestamp) { "2015-03-18T20:28:24.000+0000" }
42
29
  let(:record) { Struct.new(:SystemModstamp).new(timestamp) }
@@ -47,4 +34,24 @@ describe Restforce::DB::Instances::Salesforce do
47
34
  end
48
35
  end
49
36
 
37
+ describe "#synced?", :vcr do
38
+
39
+ describe "when no matching database record exists" do
40
+
41
+ it "returns false" do
42
+ expect(instance).to_not_be :synced?
43
+ end
44
+ end
45
+
46
+ describe "when a matching database record exists" do
47
+ before do
48
+ database_model.create!(salesforce_id: id)
49
+ end
50
+
51
+ it "returns true" do
52
+ expect(instance).to_be :synced?
53
+ end
54
+ end
55
+ end
56
+
50
57
  end
@@ -35,6 +35,7 @@ describe Restforce::DB::Mapping do
35
35
 
36
36
  it "adds the mapping to the global collection" do
37
37
  expect(Restforce::DB::Mapping[database_model]).to_equal [mapping]
38
+ expect(Restforce::DB::Mapping[salesforce_model]).to_equal [mapping]
38
39
  end
39
40
  end
40
41
 
@@ -95,41 +96,4 @@ describe Restforce::DB::Mapping do
95
96
  end
96
97
  end
97
98
  end
98
-
99
- describe "#attributes" do
100
-
101
- it "builds a normalized Hash of database attribute values" do
102
- attributes = mapping.attributes(database_model) do |attribute|
103
- expect(mapping.database_fields.include?(attribute)).to_equal true
104
- attribute
105
- end
106
-
107
- expect(attributes.keys).to_equal(mapping.database_fields)
108
- expect(attributes.values).to_equal(mapping.database_fields)
109
- end
110
-
111
- it "builds a normalized Hash of Salesforce field values" do
112
- attributes = mapping.attributes(salesforce_model) do |attribute|
113
- expect(mapping.salesforce_fields.include?(attribute)).to_equal true
114
- attribute
115
- end
116
-
117
- expect(attributes.keys).to_equal(mapping.database_fields)
118
- expect(attributes.values).to_equal(mapping.salesforce_fields)
119
- end
120
- end
121
-
122
- describe "#convert" do
123
- let(:attributes) { { column_one: "some value" } }
124
-
125
- it "converts an attribute Hash to a Salesforce-compatible form" do
126
- expect(mapping.convert(salesforce_model, attributes)).to_equal(
127
- fields[attributes.keys.first] => attributes.values.first,
128
- )
129
- end
130
-
131
- it "performs no special conversion for database columns" do
132
- expect(mapping.convert(database_model, attributes)).to_equal(attributes)
133
- end
134
- end
135
99
  end