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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8e6b77ef750532361625bbec55ad4a00e1ae4d21
4
- data.tar.gz: b88d477b1e2a2ea7f2e301b35c54b08d77612bb4
3
+ metadata.gz: df2303b0a1b785e92719f690b7d5ea5f6ec9ad09
4
+ data.tar.gz: 1add9035c044ff8022562d547c1cab48c6c35299
5
5
  SHA512:
6
- metadata.gz: 89a255caf32a25c81b24415bd5d7b389a19219e0055b7af73f3c59388a1b0914f8eb827dfe48752f528a46b65f766fb8ab9060261a6a116460b43575032da136
7
- data.tar.gz: 53d5e840aeaee28b666979b7a97e31f85bebd7bcd6bb6d6611f125f3bc6ae7c995de187749680d19ee6fdb51c3b0728ec03ae6069aad68429201fca0d444fc4d
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
- def initialize(database_model, salesforce_model, fields = {})
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
- use_mappings =
37
- case @types[from_format]
38
- when :salesforce
39
- @fields
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
- use_mappings.each_with_object({}) do |(attribute, mapping), values|
48
- values[attribute] = yield(mapping)
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
- converted[attribute] = attributes[mapping]
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
@@ -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
@@ -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
@@ -3,7 +3,7 @@ module Restforce
3
3
  # :nodoc:
4
4
  module DB
5
5
 
6
- VERSION = "1.1.0"
6
+ VERSION = "1.2.0"
7
7
 
8
8
  end
9
9
 
@@ -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
@@ -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.1.0
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-13 00:00:00.000000000 Z
11
+ date: 2015-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord