restforce-db 1.1.0 → 1.2.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.
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