restforce-db 0.2.3 → 0.3.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +39 -8
  3. data/lib/restforce/db/instances/active_record.rb +0 -9
  4. data/lib/restforce/db/instances/base.rb +7 -5
  5. data/lib/restforce/db/instances/salesforce.rb +0 -9
  6. data/lib/restforce/db/mapping.rb +91 -31
  7. data/lib/restforce/db/model.rb +5 -20
  8. data/lib/restforce/db/record_types/active_record.rb +38 -5
  9. data/lib/restforce/db/record_types/base.rb +2 -2
  10. data/lib/restforce/db/record_types/salesforce.rb +3 -3
  11. data/lib/restforce/db/version.rb +1 -1
  12. data/lib/restforce/db/worker.rb +7 -10
  13. data/lib/restforce/db.rb +0 -1
  14. data/test/cassettes/Restforce_DB/accessing_Salesforce/uses_the_configured_credentials.yml +5 -5
  15. data/test/cassettes/Restforce_DB_Instances_Salesforce/_copy_/updates_the_record_with_the_attributes_from_the_copied_object.yml +36 -35
  16. data/test/cassettes/Restforce_DB_Instances_Salesforce/_update_/updates_the_local_record_with_the_passed_attributes.yml +34 -33
  17. data/test/cassettes/Restforce_DB_Instances_Salesforce/_update_/updates_the_record_in_Salesforce_with_the_passed_attributes.yml +45 -44
  18. data/test/cassettes/Restforce_DB_RecordTypes_Salesforce/_create_/creates_a_record_in_Salesforce_from_the_passed_database_record_s_attributes.yml +28 -28
  19. data/test/cassettes/Restforce_DB_RecordTypes_Salesforce/_create_/updates_the_database_record_with_the_Salesforce_record_s_ID.yml +28 -28
  20. data/test/cassettes/Restforce_DB_RecordTypes_Salesforce/_find/finds_existing_records_in_Salesforce.yml +29 -28
  21. data/test/cassettes/Restforce_DB_RecordTypes_Salesforce/_find/returns_nil_when_no_matching_record_exists.yml +12 -12
  22. 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 +36 -36
  23. 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 +28 -104
  24. data/test/cassettes/Restforce_DB_Synchronizer/_run/given_an_existing_Salesforce_record/{populates_the_database_with_the_new_record.yml → for_a_non-root_mapping/does_not_create_a_database_record.yml} +28 -28
  25. data/test/cassettes/Restforce_DB_Synchronizer/_run/given_an_existing_Salesforce_record/for_a_root_mapping/creates_a_matching_database_record.yml +158 -0
  26. data/test/cassettes/Restforce_DB_Synchronizer/_run/given_an_existing_database_record/populates_Salesforce_with_the_new_record.yml +44 -44
  27. data/test/lib/restforce/db/instances/active_record_test.rb +4 -2
  28. data/test/lib/restforce/db/instances/salesforce_test.rb +5 -6
  29. data/test/lib/restforce/db/mapping_test.rb +40 -15
  30. data/test/lib/restforce/db/model_test.rb +6 -23
  31. data/test/lib/restforce/db/record_types/active_record_test.rb +45 -17
  32. data/test/lib/restforce/db/record_types/salesforce_test.rb +6 -7
  33. data/test/lib/restforce/db/synchronizer_test.rb +31 -17
  34. data/test/support/active_record.rb +20 -1
  35. data/test/support/utilities.rb +29 -0
  36. data/test/test_helper.rb +0 -12
  37. metadata +4 -4
  38. data/lib/restforce/db/record_type.rb +0 -77
  39. data/test/lib/restforce/db/record_type_test.rb +0 -26
@@ -3,9 +3,9 @@ require_relative "../../../../test_helper"
3
3
  describe Restforce::DB::RecordTypes::ActiveRecord do
4
4
 
5
5
  configure!
6
+ mappings!
6
7
 
7
- let(:mapping) { Restforce::DB::Mapping.new }
8
- let(:record_type) { Restforce::DB::RecordTypes::ActiveRecord.new(CustomObject, mapping) }
8
+ let(:record_type) { mapping.database_record_type }
9
9
  let(:salesforce_id) { "a001a000001E1vREAL" }
10
10
 
11
11
  describe "#sync!" do
@@ -19,10 +19,6 @@ describe Restforce::DB::RecordTypes::ActiveRecord do
19
19
  end
20
20
  let(:instance) { record_type.sync!(sync_from).record }
21
21
 
22
- before do
23
- mapping.add_mappings(name: "Name", example: "Example_Field__c")
24
- end
25
-
26
22
  describe "without an existing database record" do
27
23
 
28
24
  it "creates a new database record from the passed Salesforce record" do
@@ -35,7 +31,7 @@ describe Restforce::DB::RecordTypes::ActiveRecord do
35
31
 
36
32
  describe "with an existing database record" do
37
33
  let!(:sync_to) do
38
- CustomObject.create!(
34
+ database_model.create!(
39
35
  name: "Existing name",
40
36
  example: "Existing sample text",
41
37
  salesforce_id: salesforce_id,
@@ -53,32 +49,64 @@ describe Restforce::DB::RecordTypes::ActiveRecord do
53
49
  end
54
50
 
55
51
  describe "#create!" do
52
+ let(:attributes) do
53
+ {
54
+ name: "Some name",
55
+ example: "Some text",
56
+ }
57
+ end
58
+ let(:record) { nil }
56
59
  let(:create_from) do
57
- Struct.new(:id, :last_update, :attributes).new(
60
+ Struct.new(:id, :last_update, :attributes, :record).new(
58
61
  salesforce_id,
59
62
  Time.now,
60
- name: "Some name",
61
- example: "Some text",
63
+ attributes,
64
+ record,
62
65
  )
63
66
  end
64
67
  let(:instance) { record_type.create!(create_from).record }
65
68
 
66
- before do
67
- mapping.add_mappings(name: "Name", example: "Example_Field__c")
68
- end
69
-
70
69
  it "creates a record in the database from the passed Salesforce record's attributes" do
71
70
  expect(instance.salesforce_id).to_equal salesforce_id
72
- expect(instance.name).to_equal create_from.attributes[:name]
73
- expect(instance.example).to_equal create_from.attributes[:example]
71
+ expect(instance.name).to_equal attributes[:name]
72
+ expect(instance.example).to_equal attributes[:example]
74
73
  expect(instance.synchronized_at).to_not_be_nil
75
74
  end
75
+
76
+ describe "given a mapped association" do
77
+ let(:record) { Struct.new(:Friend__c).new(association_id) }
78
+ let(:association_id) { "a001a000001EFRIEND" }
79
+ let(:associations) { { user: "Friend__c" } }
80
+
81
+ before do
82
+ mapping = Restforce::DB::Mapping.new(User, "Contact", fields: { email: "Email" })
83
+ salesforce_record_type = mapping.salesforce_record_type
84
+
85
+ # Stub out the `#find` method on the record type
86
+ def salesforce_record_type.find(id)
87
+ Struct.new(:id, :last_update, :attributes).new(
88
+ id,
89
+ Time.now,
90
+ email: "somebody@example.com",
91
+ )
92
+ end
93
+ end
94
+
95
+ it "creates the associated record from the related Salesforce record's attributes" do
96
+ user = instance.reload.user
97
+
98
+ expect(user).to_not_be_nil
99
+ expect(user.email).to_equal("somebody@example.com")
100
+ expect(user.salesforce_id).to_equal(association_id)
101
+ expect(user.synchronized_at).to_not_be_nil
102
+ end
103
+ end
76
104
  end
77
105
 
78
106
  describe "#find" do
79
107
 
80
108
  it "finds existing records in the database by their salesforce id" do
81
- CustomObject.create!(salesforce_id: salesforce_id)
109
+ database_model.create!(salesforce_id: salesforce_id)
82
110
  expect(record_type.find(salesforce_id)).to_be_instance_of Restforce::DB::Instances::ActiveRecord
83
111
  end
84
112
 
@@ -3,23 +3,22 @@ require_relative "../../../../test_helper"
3
3
  describe Restforce::DB::RecordTypes::Salesforce do
4
4
 
5
5
  configure!
6
+ mappings!
6
7
 
7
- let(:mapping) { Restforce::DB::Mapping.new }
8
- let(:record_type) { Restforce::DB::RecordTypes::Salesforce.new("CustomObject__c", mapping) }
8
+ let(:record_type) { mapping.salesforce_record_type }
9
9
 
10
10
  describe "#create!", :vcr do
11
11
  let(:database_record) do
12
- CustomObject.create!(
12
+ database_model.create!(
13
13
  name: "Something",
14
14
  example: "Something else",
15
15
  )
16
16
  end
17
- let(:sync_from) { Restforce::DB::Instances::ActiveRecord.new(database_record, mapping) }
17
+ let(:sync_from) { Restforce::DB::Instances::ActiveRecord.new(database_model, database_record, mapping) }
18
18
  let(:instance) { record_type.create!(sync_from).record }
19
19
 
20
20
  before do
21
- mapping.add_mappings name: "Name", example: "Example_Field__c"
22
- Salesforce.records << ["CustomObject__c", instance.Id]
21
+ Salesforce.records << [salesforce_model, instance.Id]
23
22
  end
24
23
 
25
24
  it "creates a record in Salesforce from the passed database record's attributes" do
@@ -33,7 +32,7 @@ describe Restforce::DB::RecordTypes::Salesforce do
33
32
  end
34
33
 
35
34
  describe "#find", :vcr do
36
- let(:id) { Salesforce.create!("CustomObject__c") }
35
+ let(:id) { Salesforce.create!(salesforce_model) }
37
36
 
38
37
  it "finds existing records in Salesforce" do
39
38
  expect(record_type.find(id)).to_be_instance_of Restforce::DB::Instances::Salesforce
@@ -3,11 +3,9 @@ require_relative "../../../test_helper"
3
3
  describe Restforce::DB::Synchronizer do
4
4
 
5
5
  configure!
6
+ mappings!
6
7
 
7
- let(:mapping) { Restforce::DB::Mapping.new(name: "Name", example: "Example_Field__c") }
8
- let(:database_type) { Restforce::DB::RecordTypes::ActiveRecord.new(CustomObject, mapping) }
9
- let(:salesforce_type) { Restforce::DB::RecordTypes::Salesforce.new("CustomObject__c", mapping) }
10
- let(:synchronizer) { Restforce::DB::Synchronizer.new(database_type, salesforce_type) }
8
+ let(:synchronizer) { mapping.synchronizer }
11
9
 
12
10
  describe "#initialize" do
13
11
  before { Restforce::DB.last_run = Time.now }
@@ -27,39 +25,55 @@ describe Restforce::DB::Synchronizer do
27
25
  end
28
26
  let(:salesforce_id) do
29
27
  Salesforce.create!(
30
- "CustomObject__c",
31
- mapping.convert(:salesforce, attributes),
28
+ salesforce_model,
29
+ mapping.convert(salesforce_model, attributes),
32
30
  )
33
31
  end
34
32
 
35
33
  describe "given an existing Salesforce record" do
36
34
  before do
37
35
  salesforce_id
38
- synchronizer.run
39
36
  end
40
37
 
41
- it "populates the database with the new record" do
42
- record = CustomObject.last
38
+ describe "for a root mapping" do
39
+ before do
40
+ synchronizer.run
41
+ end
42
+
43
+ it "creates a matching database record" do
44
+ record = database_model.last
43
45
 
44
- expect(record.name).to_equal attributes[:name]
45
- expect(record.example).to_equal attributes[:example]
46
- expect(record.salesforce_id).to_equal salesforce_id
46
+ expect(record.name).to_equal attributes[:name]
47
+ expect(record.example).to_equal attributes[:example]
48
+ expect(record.salesforce_id).to_equal salesforce_id
49
+ end
50
+ end
51
+
52
+ describe "for a non-root mapping" do
53
+ before do
54
+ mapping.root = false
55
+ synchronizer.run
56
+ end
57
+
58
+ it "does not create a database record" do
59
+ expect(database_model.last).to_be_nil
60
+ end
47
61
  end
48
62
  end
49
63
 
50
64
  describe "given an existing database record" do
51
- let(:database_record) { CustomObject.create!(attributes) }
65
+ let(:database_record) { database_model.create!(attributes) }
52
66
  let(:salesforce_id) { database_record.reload.salesforce_id }
53
67
 
54
68
  before do
55
69
  database_record
56
70
  synchronizer.run
57
71
 
58
- Salesforce.records << ["CustomObject__c", salesforce_id]
72
+ Salesforce.records << [salesforce_model, salesforce_id]
59
73
  end
60
74
 
61
75
  it "populates Salesforce with the new record" do
62
- record = salesforce_type.find(salesforce_id).record
76
+ record = mapping.salesforce_record_type.find(salesforce_id).record
63
77
 
64
78
  expect(record.Name).to_equal attributes[:name]
65
79
  expect(record.Example_Field__c).to_equal attributes[:example]
@@ -75,14 +89,14 @@ describe Restforce::DB::Synchronizer do
75
89
  }
76
90
  end
77
91
  let(:database_record) do
78
- CustomObject.create!(database_attributes.merge(salesforce_id: salesforce_id))
92
+ database_model.create!(database_attributes.merge(salesforce_id: salesforce_id))
79
93
  end
80
94
 
81
95
  describe "when synchronization is stale" do
82
96
  before do
83
97
  # Set the synchronization timestamp to 5 seconds before the Salesforce
84
98
  # modification timestamp.
85
- updated = salesforce_type.find(salesforce_id).last_update
99
+ updated = mapping.salesforce_record_type.find(salesforce_id).last_update
86
100
  database_record.update!(synchronized_at: updated - 5)
87
101
 
88
102
  synchronizer.run
@@ -7,15 +7,34 @@ ActiveRecord::Base.establish_connection(
7
7
  )
8
8
 
9
9
  ActiveRecord::Schema.define do
10
+
10
11
  create_table :custom_objects do |table|
11
12
  table.column :name, :string
12
13
  table.column :example, :string
14
+ table.column :user_id, :integer
13
15
  table.column :salesforce_id, :string
14
16
  table.column :synchronized_at, :datetime
15
17
  table.timestamps null: false
16
18
  end
17
19
 
18
20
  add_index :custom_objects, :salesforce_id
21
+
22
+ create_table :users do |table|
23
+ table.column :email, :string
24
+ table.column :salesforce_id, :string
25
+ table.column :synchronized_at, :datetime
26
+ table.timestamps null: false
27
+ end
28
+
29
+ add_index :users, :salesforce_id
30
+
31
+ end
32
+
33
+ # :nodoc:
34
+ class CustomObject < ActiveRecord::Base
35
+
36
+ belongs_to :user
37
+
19
38
  end
20
39
 
21
- class CustomObject < ActiveRecord::Base; end
40
+ class User < ActiveRecord::Base; end
@@ -0,0 +1,29 @@
1
+ # :nodoc:
2
+ def configure!
3
+ before do
4
+ Salesforce.configure!
5
+ end
6
+
7
+ after do
8
+ Restforce::DB::Mapping.collection = {}
9
+ DatabaseCleaner.clean
10
+ Salesforce.clean!
11
+ end
12
+ end
13
+
14
+ # :nodoc:
15
+ def mappings!
16
+ let(:database_model) { CustomObject }
17
+ let(:salesforce_model) { "CustomObject__c" }
18
+ let(:fields) { { name: "Name", example: "Example_Field__c" } }
19
+ let(:associations) { {} }
20
+ let!(:mapping) do
21
+ Restforce::DB::Mapping.new(
22
+ database_model,
23
+ salesforce_model,
24
+ root: true,
25
+ fields: fields,
26
+ associations: associations,
27
+ )
28
+ end
29
+ end
data/test/test_helper.rb CHANGED
@@ -11,15 +11,3 @@ secrets_file = File.expand_path("../config/secrets.yml", __FILE__)
11
11
  Secrets = YAML.load_file(secrets_file)
12
12
 
13
13
  Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].each { |f| require f }
14
-
15
- # :nodoc:
16
- def configure!
17
- before do
18
- Salesforce.configure!
19
- end
20
-
21
- after do
22
- DatabaseCleaner.clean
23
- Salesforce.clean!
24
- end
25
- end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restforce-db
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Horner
@@ -227,7 +227,6 @@ files:
227
227
  - lib/restforce/db/instances/salesforce.rb
228
228
  - lib/restforce/db/mapping.rb
229
229
  - lib/restforce/db/model.rb
230
- - lib/restforce/db/record_type.rb
231
230
  - lib/restforce/db/record_types/active_record.rb
232
231
  - lib/restforce/db/record_types/base.rb
233
232
  - lib/restforce/db/record_types/salesforce.rb
@@ -247,14 +246,14 @@ files:
247
246
  - test/cassettes/Restforce_DB_RecordTypes_Salesforce/_find/returns_nil_when_no_matching_record_exists.yml
248
247
  - test/cassettes/Restforce_DB_Synchronizer/_run/given_a_Salesforce_record_with_an_associated_database_record/when_synchronization_is_stale/updates_the_database_record.yml
249
248
  - 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
250
- - test/cassettes/Restforce_DB_Synchronizer/_run/given_an_existing_Salesforce_record/populates_the_database_with_the_new_record.yml
249
+ - test/cassettes/Restforce_DB_Synchronizer/_run/given_an_existing_Salesforce_record/for_a_non-root_mapping/does_not_create_a_database_record.yml
250
+ - test/cassettes/Restforce_DB_Synchronizer/_run/given_an_existing_Salesforce_record/for_a_root_mapping/creates_a_matching_database_record.yml
251
251
  - test/cassettes/Restforce_DB_Synchronizer/_run/given_an_existing_database_record/populates_Salesforce_with_the_new_record.yml
252
252
  - test/lib/restforce/db/configuration_test.rb
253
253
  - test/lib/restforce/db/instances/active_record_test.rb
254
254
  - test/lib/restforce/db/instances/salesforce_test.rb
255
255
  - test/lib/restforce/db/mapping_test.rb
256
256
  - test/lib/restforce/db/model_test.rb
257
- - test/lib/restforce/db/record_type_test.rb
258
257
  - test/lib/restforce/db/record_types/active_record_test.rb
259
258
  - test/lib/restforce/db/record_types/salesforce_test.rb
260
259
  - test/lib/restforce/db/synchronizer_test.rb
@@ -263,6 +262,7 @@ files:
263
262
  - test/support/active_record.rb
264
263
  - test/support/database_cleaner.rb
265
264
  - test/support/salesforce.rb
265
+ - test/support/utilities.rb
266
266
  - test/support/vcr.rb
267
267
  - test/test_helper.rb
268
268
  homepage: https://www.github.com/tablexi/restforce-db
@@ -1,77 +0,0 @@
1
- module Restforce
2
-
3
- module DB
4
-
5
- # Restforce::DB::RecordType is an abstraction for a two-way binding between
6
- # an ActiveRecord class and a Salesforce object type. It provides an
7
- # interface for mapping database columns to Salesforce fields.
8
- class RecordType
9
-
10
- class << self
11
-
12
- include Enumerable
13
- attr_accessor :collection
14
-
15
- # Public: Get the Restforce::DB::RecordType entry for the specified
16
- # database model.
17
- #
18
- # database_model - A Class compatible with ActiveRecord::Base.
19
- #
20
- # Returns a Restforce::DB::RecordType.
21
- def [](database_model)
22
- collection[database_model]
23
- end
24
-
25
- # Public: Iterate through all registered Restforce::DB::RecordTypes.
26
- #
27
- # Yields one RecordType for each database-to-Salesforce mapping.
28
- # Returns nothing.
29
- def each
30
- collection.each do |database_model, record_type|
31
- yield database_model.name, record_type
32
- end
33
- end
34
-
35
- end
36
-
37
- self.collection ||= {}
38
- attr_reader :mapping, :synchronizer
39
-
40
- # Public: Initialize and register a Restforce::DB::RecordType.
41
- #
42
- # database_model - A Class compatible with ActiveRecord::Base.
43
- # salesforce_model - A String name of an object type in Salesforce.
44
- # mappings - A Hash of mappings between database columns and
45
- # fields in Salesforce.
46
- def initialize(database_model, salesforce_model, **mappings)
47
- @mapping = Mapping.new(mappings)
48
- @database_record_type = RecordTypes::ActiveRecord.new(database_model, @mapping)
49
- @salesforce_record_type = RecordTypes::Salesforce.new(salesforce_model, @mapping)
50
- @synchronizer = Synchronizer.new(@database_record_type, @salesforce_record_type)
51
-
52
- self.class.collection[database_model] = self
53
- end
54
-
55
- # Public: Append the passed mappings to this model.
56
- #
57
- # mappings - A Hash of database column names mapped to Salesforce fields.
58
- #
59
- # Returns nothing.
60
- def add_mappings(mappings)
61
- @mapping.add_mappings mappings
62
- end
63
-
64
- # Public: Synchronize the records between the database and Salesforce.
65
- #
66
- # options - A Hash of options to pass to the synchronizer.
67
- #
68
- # Returns nothing.
69
- def synchronize(options = {})
70
- @synchronizer.run(options)
71
- end
72
-
73
- end
74
-
75
- end
76
-
77
- end
@@ -1,26 +0,0 @@
1
- require_relative "../../../test_helper"
2
-
3
- describe Restforce::DB::RecordType do
4
-
5
- configure!
6
-
7
- let(:database_model) { CustomObject }
8
- let(:salesforce_model) { "CustomObject__c" }
9
- let!(:record_type) { Restforce::DB::RecordType.new(database_model, salesforce_model) }
10
-
11
- describe "#initialize" do
12
-
13
- it "registers the record type in the master collection" do
14
- expect(Restforce::DB::RecordType[database_model]).to_equal(record_type)
15
- end
16
- end
17
-
18
- describe ".each" do
19
-
20
- # Restforce::DB::RecordType actually implements Enumerable, so we're just
21
- # going with a trivially testable portion of the Enumerable API.
22
- it "yields the registered record types" do
23
- expect(Restforce::DB::RecordType.first).to_equal [database_model.name, record_type]
24
- end
25
- end
26
- end