restforce-db 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +21 -0
- data/lib/restforce/db/attribute_map.rb +45 -18
- data/lib/restforce/db/dsl.rb +16 -0
- data/lib/restforce/db/mapping.rb +3 -1
- data/lib/restforce/db/version.rb +1 -1
- data/test/lib/restforce/db/attribute_map_test.rb +40 -2
- data/test/lib/restforce/db/dsl_test.rb +18 -0
- data/test/support/utilities.rb +15 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df2303b0a1b785e92719f690b7d5ea5f6ec9ad09
|
4
|
+
data.tar.gz: 1add9035c044ff8022562d547c1cab48c6c35299
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7de9ccdec2e054a494cb31d1caf00636e0893de35dd474aaa11862b42b2028be1fa8c50174a600594fa61e23284f7f0510946340c8f19f4bfbcb02d2a9a9c44
|
7
|
+
data.tar.gz: dbae2200380469cd1ef662a9b05f3324309f37e6ac0a6615bb41cedb94b7e99b68e080207728eccdb45c0ec3245f4c8093196ecee698bc9523a6714b768264fc
|
data/README.md
CHANGED
@@ -42,14 +42,31 @@ class Restaurant < ActiveRecord::Base
|
|
42
42
|
has_one :chef, inverse_of: :restaurant
|
43
43
|
has_many :dishes, inverse_of: :restaurant
|
44
44
|
|
45
|
+
module StyleAdapter
|
46
|
+
|
47
|
+
def self.to_salesforce(value)
|
48
|
+
"#{value} in Salesforce"
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.to_database(value)
|
52
|
+
value.chomp(" in Salesforce")
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
45
57
|
sync_with("Restaurant__c", :always) do
|
46
58
|
where "StarRating__c > 4"
|
47
59
|
has_many :dishes, through: "Restaurant__c"
|
48
60
|
belongs_to :chef, through: %w(Chef__c Cuisine__c)
|
61
|
+
|
49
62
|
maps(
|
50
63
|
name: "Name",
|
51
64
|
style: "Style__c",
|
52
65
|
)
|
66
|
+
|
67
|
+
converts(
|
68
|
+
style: StyleAdapter,
|
69
|
+
)
|
53
70
|
end
|
54
71
|
|
55
72
|
end
|
@@ -118,6 +135,10 @@ Individual conditions supplied to `where` will be appended together with `AND` c
|
|
118
135
|
|
119
136
|
`maps` defines a set of direct field-to-field mappings. It takes a Hash as an argument; the keys should line up with your ActiveRecord attribute names, while the values should line up with the matching field names in Salesforce.
|
120
137
|
|
138
|
+
#### Field Conversions
|
139
|
+
|
140
|
+
`converts` defines a set of value adapters. It takes a Hash as an argument; the keys should line up with the ActiveRecord attribute names defined in your `maps` clause, while the values should be the corresponding adapter objects. The only requirement for an adapter is that it respond to the methods `#to_database` and `#to_salesforce`.
|
141
|
+
|
121
142
|
#### Associations
|
122
143
|
|
123
144
|
Associations in `Restforce::DB` can be a little tricky, as they depend on your ActiveRecord association mappings, but are independent of those mappings, and can even (as seen above) seem to conflict with them.
|
@@ -6,16 +6,38 @@ module Restforce
|
|
6
6
|
# various representations of attribute hashes.
|
7
7
|
class AttributeMap
|
8
8
|
|
9
|
+
# DefaultAdapter defines the default data conversions between database and
|
10
|
+
# Salesforce formats. It translates Dates and Times to ISO-8601 format for
|
11
|
+
# storage in Salesforce.
|
12
|
+
module DefaultAdapter
|
13
|
+
|
14
|
+
# :nodoc:
|
15
|
+
def self.to_database(value)
|
16
|
+
value
|
17
|
+
end
|
18
|
+
|
19
|
+
# :nodoc:
|
20
|
+
def self.to_salesforce(value)
|
21
|
+
value = value.respond_to?(:utc) ? value.utc : value
|
22
|
+
value.respond_to?(:iso8601) ? value.iso8601 : value
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
9
27
|
# Public: Initialize a Restforce::DB::AttributeMap.
|
10
28
|
#
|
11
29
|
# database_model - A Class compatible with ActiveRecord::Base.
|
12
30
|
# salesforce_model - A String name of an object type in Salesforce.
|
13
31
|
# fields - A Hash of mappings between database columns and
|
14
32
|
# fields in Salesforce.
|
15
|
-
|
33
|
+
# conversions - A Hash of mappings between database columns and the
|
34
|
+
# corresponding adapter objects which should be used to
|
35
|
+
# convert between data formats.
|
36
|
+
def initialize(database_model, salesforce_model, fields = {}, conversions = {})
|
16
37
|
@database_model = database_model
|
17
38
|
@salesforce_model = salesforce_model
|
18
39
|
@fields = fields
|
40
|
+
@conversions = conversions
|
19
41
|
|
20
42
|
@types = {
|
21
43
|
database_model => :database,
|
@@ -33,19 +55,17 @@ module Restforce
|
|
33
55
|
# Yields a series of attribute names.
|
34
56
|
# Returns a Hash.
|
35
57
|
def attributes(from_format)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
when :database
|
41
|
-
# Generate a mapping of database column names to record attributes.
|
42
|
-
@fields.keys.zip(@fields.keys)
|
43
|
-
else
|
44
|
-
raise ArgumentError
|
58
|
+
case @types[from_format]
|
59
|
+
when :salesforce
|
60
|
+
@fields.each_with_object({}) do |(attribute, mapping), values|
|
61
|
+
values[attribute] = adapter(attribute).to_database(yield(mapping))
|
45
62
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
63
|
+
when :database
|
64
|
+
@fields.keys.each_with_object({}) do |attribute, values|
|
65
|
+
values[attribute] = yield(attribute)
|
66
|
+
end
|
67
|
+
else
|
68
|
+
raise ArgumentError
|
49
69
|
end
|
50
70
|
end
|
51
71
|
|
@@ -79,10 +99,7 @@ module Restforce
|
|
79
99
|
when :salesforce
|
80
100
|
@fields.each_with_object({}) do |(attribute, mapping), converted|
|
81
101
|
next unless attributes.key?(attribute)
|
82
|
-
value = attributes[attribute]
|
83
|
-
value = value.respond_to?(:utc) ? value.utc : value
|
84
|
-
value = value.respond_to?(:iso8601) ? value.iso8601 : value
|
85
|
-
|
102
|
+
value = adapter(attribute).to_salesforce(attributes[attribute])
|
86
103
|
converted[mapping] = value
|
87
104
|
end
|
88
105
|
else
|
@@ -124,7 +141,8 @@ module Restforce
|
|
124
141
|
when :database
|
125
142
|
@fields.each_with_object({}) do |(attribute, mapping), converted|
|
126
143
|
next unless attributes.key?(mapping)
|
127
|
-
|
144
|
+
value = adapter(attribute).to_database(attributes[mapping])
|
145
|
+
converted[attribute] = value
|
128
146
|
end
|
129
147
|
when :salesforce
|
130
148
|
attributes.dup
|
@@ -133,6 +151,15 @@ module Restforce
|
|
133
151
|
end
|
134
152
|
end
|
135
153
|
|
154
|
+
# Internal: Get the data format adapter for the passed attribute. Defaults
|
155
|
+
# to DefaultAdapter if no adapter has been explicitly assigned for the
|
156
|
+
# attribute.
|
157
|
+
#
|
158
|
+
# Returns an Object.
|
159
|
+
def adapter(attribute)
|
160
|
+
@conversions[attribute] || DefaultAdapter
|
161
|
+
end
|
162
|
+
|
136
163
|
end
|
137
164
|
|
138
165
|
end
|
data/lib/restforce/db/dsl.rb
CHANGED
@@ -85,6 +85,22 @@ module Restforce
|
|
85
85
|
@mapping.fields = fields
|
86
86
|
end
|
87
87
|
|
88
|
+
# Public: Define a set of adapters which should be used to translate data
|
89
|
+
# between the database and Salesforce.
|
90
|
+
#
|
91
|
+
# fields - A Hash, with keys corresponding to attributes of the database
|
92
|
+
# record, and adapter objects as values.
|
93
|
+
#
|
94
|
+
# Raises ArgumentError if any adapter object has an incomplete interface.
|
95
|
+
# Returns nothing.
|
96
|
+
def converts(conversions)
|
97
|
+
unless conversions.values.all? { |c| c.respond_to?(:to_database) && c.respond_to?(:to_salesforce) }
|
98
|
+
raise ArgumentError, "All adapters must implement `to_database` and `to_salesforce` methods"
|
99
|
+
end
|
100
|
+
|
101
|
+
@mapping.conversions = conversions
|
102
|
+
end
|
103
|
+
|
88
104
|
end
|
89
105
|
|
90
106
|
end
|
data/lib/restforce/db/mapping.rb
CHANGED
@@ -26,6 +26,7 @@ module Restforce
|
|
26
26
|
|
27
27
|
attr_accessor(
|
28
28
|
:fields,
|
29
|
+
:conversions,
|
29
30
|
:associations,
|
30
31
|
:conditions,
|
31
32
|
:strategy,
|
@@ -44,6 +45,7 @@ module Restforce
|
|
44
45
|
@salesforce_record_type = RecordTypes::Salesforce.new(salesforce_model, self)
|
45
46
|
|
46
47
|
self.fields = {}
|
48
|
+
self.conversions = {}
|
47
49
|
self.associations = []
|
48
50
|
self.conditions = []
|
49
51
|
self.strategy = strategy
|
@@ -91,7 +93,7 @@ module Restforce
|
|
91
93
|
#
|
92
94
|
# Returns a Restforce::DB::AttributeMap.
|
93
95
|
def attribute_map
|
94
|
-
@attribute_map ||= AttributeMap.new(database_model, salesforce_model, fields)
|
96
|
+
@attribute_map ||= AttributeMap.new(database_model, salesforce_model, fields, conversions)
|
95
97
|
end
|
96
98
|
|
97
99
|
end
|
data/lib/restforce/db/version.rb
CHANGED
@@ -12,8 +12,8 @@ describe Restforce::DB::AttributeMap do
|
|
12
12
|
column_two: "SF_Field_Two__c",
|
13
13
|
}
|
14
14
|
end
|
15
|
-
|
16
|
-
let(:attribute_map) { Restforce::DB::AttributeMap.new(database_model, salesforce_model, fields) }
|
15
|
+
let(:conversions) { {} }
|
16
|
+
let(:attribute_map) { Restforce::DB::AttributeMap.new(database_model, salesforce_model, fields, conversions) }
|
17
17
|
|
18
18
|
describe "#attributes" do
|
19
19
|
let(:mapping) do
|
@@ -41,6 +41,18 @@ describe Restforce::DB::AttributeMap do
|
|
41
41
|
expect(attributes.keys).to_equal(mapping.database_fields)
|
42
42
|
expect(attributes.values).to_equal(mapping.salesforce_fields)
|
43
43
|
end
|
44
|
+
|
45
|
+
describe "when an adapter has been defined for an attribute" do
|
46
|
+
let(:conversions) { { column_one: boolean_adapter } }
|
47
|
+
|
48
|
+
it "uses the adapter to convert the value returned by the block" do
|
49
|
+
attributes = attribute_map.attributes(salesforce_model) { |_| "Yes" }
|
50
|
+
expect(attributes).to_equal(
|
51
|
+
column_one: true,
|
52
|
+
column_two: "Yes",
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
44
56
|
end
|
45
57
|
|
46
58
|
describe "#convert" do
|
@@ -66,6 +78,19 @@ describe Restforce::DB::AttributeMap do
|
|
66
78
|
)
|
67
79
|
end
|
68
80
|
end
|
81
|
+
|
82
|
+
describe "when an adapter has been defined for an attribute" do
|
83
|
+
let(:conversions) { { column_one: boolean_adapter } }
|
84
|
+
|
85
|
+
it "uses the adapter to convert that attribute to a Salesforce-compatible form" do
|
86
|
+
expect(attribute_map.convert(salesforce_model, column_one: true)).to_equal(
|
87
|
+
"SF_Field_One__c" => "Yes",
|
88
|
+
)
|
89
|
+
expect(attribute_map.convert(salesforce_model, column_one: false)).to_equal(
|
90
|
+
"SF_Field_One__c" => "No",
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
69
94
|
end
|
70
95
|
|
71
96
|
describe "#convert_from_salesforce" do
|
@@ -80,6 +105,19 @@ describe Restforce::DB::AttributeMap do
|
|
80
105
|
it "performs no special conversion for Salesforce fields" do
|
81
106
|
expect(attribute_map.convert_from_salesforce(salesforce_model, attributes)).to_equal(attributes)
|
82
107
|
end
|
108
|
+
|
109
|
+
describe "when an adapter has been defined for an attribute" do
|
110
|
+
let(:conversions) { { column_one: boolean_adapter } }
|
111
|
+
|
112
|
+
it "uses the adapter to convert that attribute to a database-compatible form" do
|
113
|
+
expect(attribute_map.convert_from_salesforce(database_model, "SF_Field_One__c" => "Yes")).to_equal(
|
114
|
+
column_one: true,
|
115
|
+
)
|
116
|
+
expect(attribute_map.convert_from_salesforce(database_model, "SF_Field_One__c" => "No")).to_equal(
|
117
|
+
column_one: false,
|
118
|
+
)
|
119
|
+
end
|
120
|
+
end
|
83
121
|
end
|
84
122
|
|
85
123
|
end
|
@@ -96,4 +96,22 @@ describe Restforce::DB::DSL do
|
|
96
96
|
expect(mapping.fields).to_equal fields
|
97
97
|
end
|
98
98
|
end
|
99
|
+
|
100
|
+
describe "#converts" do
|
101
|
+
let(:adapter) { boolean_adapter }
|
102
|
+
let(:conversions) { { some: adapter } }
|
103
|
+
|
104
|
+
it "sets the conversions for the created mapping" do
|
105
|
+
dsl.converts conversions
|
106
|
+
expect(mapping.conversions).to_equal conversions
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "when the adapter does not define the expected methods" do
|
110
|
+
let(:adapter) { Object.new }
|
111
|
+
|
112
|
+
it "raises an ArgumentError" do
|
113
|
+
expect(-> { dsl.converts conversions }).to_raise ArgumentError
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
99
117
|
end
|
data/test/support/utilities.rb
CHANGED
@@ -26,3 +26,18 @@ def mappings!
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
29
|
+
|
30
|
+
# :nodoc:
|
31
|
+
def boolean_adapter
|
32
|
+
Object.new.tap do |object|
|
33
|
+
|
34
|
+
def object.to_database(value)
|
35
|
+
value == "Yes"
|
36
|
+
end
|
37
|
+
|
38
|
+
def object.to_salesforce(value)
|
39
|
+
value ? "Yes" : "No"
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
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: 1.
|
4
|
+
version: 1.2.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-04-
|
11
|
+
date: 2015-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|