fn-salesforce 0.1.0.pre → 0.2.0.pre
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 +4 -4
- data/README.md +32 -0
- data/lib/fn/salesforce.rb +15 -36
- data/lib/fn/salesforce/object_factory.rb +32 -7
- data/lib/fn/salesforce/operation.rb +51 -0
- data/lib/fn/salesforce/options.rb +12 -8
- data/lib/fn/salesforce/rollback.rb +29 -0
- data/lib/fn/salesforce/transaction.rb +89 -0
- data/lib/fn/salesforce/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82f216e7b819a1f7b0a01c9d6c5e01ba488bae78
|
4
|
+
data.tar.gz: ab224735759865beefcb5a3ccd1d8fd5e59f6ecd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8cf89d992698421808b9a31831e0d7cd949b8c201391bbdb6451c6645ec7413a8d9130f483743937d73a4ee2c17802e0b270ff66564b467051fdb0a80053d6b7
|
7
|
+
data.tar.gz: b51b76d9286030d2411c668d869f026b55dcf99299e71939b24c40c2b9039f9d25eb10dcd051ef77d9cedbe6b2433ca6d1386f844529ffd09e0dc795e0b4ef18
|
data/README.md
CHANGED
@@ -12,6 +12,10 @@ and external data. It supports nested JSON data, and can resolve
|
|
12
12
|
basic dependency graphs while providing access to intermediary data
|
13
13
|
formats.
|
14
14
|
|
15
|
+
Install
|
16
|
+
-------
|
17
|
+
|
18
|
+
`$ gem install fn-salesforce --pre`
|
15
19
|
|
16
20
|
Artifacts
|
17
21
|
---------
|
@@ -82,6 +86,34 @@ the value without talking to Salesforce first.
|
|
82
86
|
|
83
87
|
The array of objects received are assumed to be in a reliable order.
|
84
88
|
|
89
|
+
### Message types
|
90
|
+
|
91
|
+
**create**
|
92
|
+
|
93
|
+
```js
|
94
|
+
{
|
95
|
+
"sObject": "my__ObjectName__c",
|
96
|
+
"properties": {
|
97
|
+
"Id": "12345"
|
98
|
+
}
|
99
|
+
}
|
100
|
+
```
|
101
|
+
|
102
|
+
**upsert**
|
103
|
+
|
104
|
+
```js
|
105
|
+
{
|
106
|
+
"sObject": "my__ObjectName__c",
|
107
|
+
"properties": {
|
108
|
+
"Foo": "Bar",
|
109
|
+
"External_Baz__c": "Catatafish"
|
110
|
+
},
|
111
|
+
"action": "upsert",
|
112
|
+
"externalID": "External_Baz__c"
|
113
|
+
}
|
114
|
+
```
|
115
|
+
|
116
|
+
|
85
117
|
## describe
|
86
118
|
|
87
119
|
Returns the object description from Salesforce.
|
data/lib/fn/salesforce.rb
CHANGED
@@ -2,7 +2,9 @@ require "json"
|
|
2
2
|
require "fn/salesforce/version"
|
3
3
|
require "fn/salesforce/environment"
|
4
4
|
require "fn/salesforce/options"
|
5
|
-
require "fn/salesforce/
|
5
|
+
require "fn/salesforce/operation"
|
6
|
+
require "fn/salesforce/rollback"
|
7
|
+
require "fn/salesforce/transaction"
|
6
8
|
require "fn/salesforce/object_factory"
|
7
9
|
require "fn/salesforce/walker"
|
8
10
|
require "restforce"
|
@@ -14,43 +16,20 @@ module Fn
|
|
14
16
|
# Push
|
15
17
|
# ----
|
16
18
|
# Takes a prepared payload, and sends it to Salesforce.
|
17
|
-
def self.push(credentials, message)
|
19
|
+
def self.push(credentials, message, logger: Logger.new(STDOUT))
|
18
20
|
|
19
21
|
client = Restforce.new(credentials)
|
20
|
-
message
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
map { |i| Hash[*i] }.inject(&:merge)
|
32
|
-
|
33
|
-
$stderr.puts "Creating #{obj["sObject"]}: #{ obj["properties"] }"
|
34
|
-
id = client.create!(obj["sObject"], obj["properties"])
|
35
|
-
|
36
|
-
obj["properties"]["Id"] = id
|
37
|
-
plan.push obj
|
38
|
-
|
39
|
-
rescue Exception => e
|
40
|
-
$stderr.puts e
|
41
|
-
if plan.any?
|
42
|
-
$stderr.puts "Rolling back previous objects"
|
43
|
-
plan.each { |obj|
|
44
|
-
id = obj["properties"]["Id"]
|
45
|
-
object = obj["sObject"]
|
46
|
-
$stderr.puts "Deleting #{object}##{id}"
|
47
|
-
client.destroy(object, id)
|
48
|
-
}
|
49
|
-
end
|
50
|
-
break
|
51
|
-
end
|
52
|
-
|
53
|
-
}
|
22
|
+
plan = Plan.new(message)
|
23
|
+
|
24
|
+
transaction = Transaction.new(client, plan, logger: logger)
|
25
|
+
|
26
|
+
transaction.execute
|
27
|
+
|
28
|
+
transaction.rollback! if transaction.failed
|
29
|
+
|
30
|
+
# Return true for success, false for failure.
|
31
|
+
!transaction.failed
|
32
|
+
|
54
33
|
end
|
55
34
|
|
56
35
|
# Prepare
|
@@ -18,19 +18,44 @@ class Fn::Salesforce::ObjectFactory
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def create(key, properties, parent=nil)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
|
22
|
+
[
|
23
|
+
->(obj) {
|
24
|
+
{ "properties" => properties }
|
25
|
+
},
|
26
|
+
->(obj) {
|
27
|
+
{ "action" => "create" }
|
28
|
+
},
|
29
|
+
->(obj) {
|
30
|
+
{ "sObject" => sobject_for(key) || key }
|
31
|
+
},
|
32
|
+
->(obj) {
|
33
|
+
return {} unless parent
|
34
|
+
{
|
35
|
+
"properties" => obj["properties"].merge(
|
36
|
+
Hash[ foreign_key_for(key), {"$ref" => "/#{parent}/Id"} ]
|
37
|
+
)
|
38
|
+
}
|
39
|
+
},
|
40
|
+
->(obj) {
|
41
|
+
return {} unless lookup_key = lookup_key_for(key)
|
42
|
+
{
|
43
|
+
"action" => "update",
|
44
|
+
"lookupWith" => Hash[lookup_key, obj["properties"][lookup_key]],
|
45
|
+
"properties" => obj["properties"].select { |k| k != lookup_key }
|
46
|
+
}
|
47
|
+
}
|
48
|
+
].reduce({}) { |memo,op| memo.merge op[memo] }
|
28
49
|
end
|
29
50
|
|
30
51
|
def sobject_for(key)
|
31
52
|
JsonPath.on(schema,"..properties.#{key}.sObject").first
|
32
53
|
end
|
33
54
|
|
55
|
+
def lookup_key_for(key)
|
56
|
+
JsonPath.on(schema,"..properties.#{key}.lookupKey").first
|
57
|
+
end
|
58
|
+
|
34
59
|
def foreign_key_for(relationship_key)
|
35
60
|
JsonPath.on(schema,"..properties.#{relationship_key}.foreignKey").first
|
36
61
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Fn
|
4
|
+
module Salesforce
|
5
|
+
|
6
|
+
class Operation < Hashie::Mash
|
7
|
+
|
8
|
+
def action
|
9
|
+
(super || :create ).to_sym
|
10
|
+
end
|
11
|
+
|
12
|
+
def s_object
|
13
|
+
sObject
|
14
|
+
end
|
15
|
+
|
16
|
+
def lookup_with
|
17
|
+
lookupWith
|
18
|
+
end
|
19
|
+
|
20
|
+
def previous_properties
|
21
|
+
previousProperties
|
22
|
+
end
|
23
|
+
|
24
|
+
def references
|
25
|
+
properties.select { |k,v|
|
26
|
+
v.is_a? Hash
|
27
|
+
}.select { |k,v|
|
28
|
+
v.keys.any? { |key| key == "$ref" }
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def replace_refs(data)
|
33
|
+
properties.merge! properties.
|
34
|
+
map { |k,v|
|
35
|
+
if v.is_a?(Hash) && v["$ref"]
|
36
|
+
v = Hana::Pointer.new(v["$ref"]).eval(data)
|
37
|
+
end
|
38
|
+
[k,v]
|
39
|
+
}.
|
40
|
+
map { |i| Hash[*i] }.inject(&:merge)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Plan < SimpleDelegator
|
45
|
+
def initialize(operations)
|
46
|
+
super(operations.map { |op| Operation.new(op) })
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -10,16 +10,20 @@ class Fn::Salesforce::Options
|
|
10
10
|
def credentials
|
11
11
|
return @credentials if @credentials
|
12
12
|
|
13
|
-
|
13
|
+
if @attributes[:credentials].is_a? String
|
14
|
+
attributes = JSON.parse(File.read(@attributes[:credentials]))
|
15
|
+
else
|
16
|
+
attributes = @attributes[:credentials]
|
17
|
+
end
|
18
|
+
|
14
19
|
@credentials = Hashie::Mash.new( {
|
15
|
-
username:
|
16
|
-
password:
|
17
|
-
security_token:
|
18
|
-
client_id:
|
19
|
-
client_secret:
|
20
|
-
host:
|
20
|
+
username: attributes["username"],
|
21
|
+
password: attributes["password"],
|
22
|
+
security_token: attributes["token"],
|
23
|
+
client_id: attributes["key"],
|
24
|
+
client_secret: attributes["secret"],
|
25
|
+
host: attributes["host"]
|
21
26
|
} ).to_hash(symbolize_keys: true)
|
22
|
-
|
23
27
|
end
|
24
28
|
|
25
29
|
def schema
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Fn
|
2
|
+
module Salesforce
|
3
|
+
class Rollback < SimpleDelegator
|
4
|
+
|
5
|
+
def initialize(plan)
|
6
|
+
super(
|
7
|
+
plan.map { |operation|
|
8
|
+
operation.dup
|
9
|
+
}.map { |operation| UndoFactory.coerce(operation) }
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
class UndoFactory
|
16
|
+
def self.coerce(operation)
|
17
|
+
case operation.action
|
18
|
+
when :create
|
19
|
+
operation["action"] = "delete"
|
20
|
+
when :update
|
21
|
+
operation["properties"] = operation.previous_properties
|
22
|
+
end
|
23
|
+
|
24
|
+
operation
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Fn
|
2
|
+
module Salesforce
|
3
|
+
|
4
|
+
class Transaction
|
5
|
+
|
6
|
+
attr_reader :client, :plan, :failed, :result
|
7
|
+
attr_accessor :logger
|
8
|
+
|
9
|
+
def initialize(client, plan, logger: Logger.new(STDOUT))
|
10
|
+
@logger = logger
|
11
|
+
@client = client
|
12
|
+
@plan = plan
|
13
|
+
@result = []
|
14
|
+
@failed = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute
|
18
|
+
@result = plan.inject([]) { |results, operation|
|
19
|
+
begin
|
20
|
+
operation.replace_refs(results)
|
21
|
+
results << perform(operation)
|
22
|
+
rescue Exception => e
|
23
|
+
logger.error "Transaction failed at ##{plan.index(operation)}"
|
24
|
+
logger.error "Error message: #{e.message}"
|
25
|
+
@failed = true
|
26
|
+
break results
|
27
|
+
end
|
28
|
+
results
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def rollback!
|
34
|
+
Rollback.new(result).each { |operation| perform(operation) }
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def perform(operation)
|
40
|
+
case operation.action
|
41
|
+
|
42
|
+
when :create
|
43
|
+
logger.info "Creating #{operation.s_object}: #{ operation.properties.to_hash }"
|
44
|
+
id = client.create!( operation.s_object, operation.properties.dup )
|
45
|
+
operation.merge!( {"Id" => id} )
|
46
|
+
when :update
|
47
|
+
logger.info "Finding #{operation.s_object}: #{ operation.lookup_with.to_hash }"
|
48
|
+
object = client.find(
|
49
|
+
operation.s_object,
|
50
|
+
*operation.lookup_with.invert.to_a.flatten
|
51
|
+
)
|
52
|
+
|
53
|
+
logger.info "Found #{operation.s_object}(#{object.Id})"
|
54
|
+
# Keep the Id we get back from the find operation, we
|
55
|
+
# use this for updating the object, and for rolling back if needed.
|
56
|
+
operation.merge!( {"Id" => object.Id} )
|
57
|
+
|
58
|
+
# Slice off the fields we are about to change, and put them on
|
59
|
+
# 'previousProperties'.
|
60
|
+
operation.previousProperties = object.select { |key, value|
|
61
|
+
operation.properties.keys.include?(key)
|
62
|
+
}
|
63
|
+
logger.info "Updating #{operation.s_object}: #{ operation.properties.to_hash }"
|
64
|
+
|
65
|
+
client.update!(
|
66
|
+
operation.s_object,
|
67
|
+
Hash["Id", operation["Id"]].merge( operation.properties )
|
68
|
+
)
|
69
|
+
|
70
|
+
when :delete
|
71
|
+
logger.info "Deleting #{operation.s_object}(#{operation.Id})"
|
72
|
+
client.destroy!(operation.sObject, operation.Id)
|
73
|
+
|
74
|
+
when :upsert
|
75
|
+
logger.info "Upserting #{operation.s_object}(#{operation.externalID})"
|
76
|
+
client.upsert!(operation.sObject, operation.externalID, operation.properties)
|
77
|
+
else
|
78
|
+
logger.warn "Warning: No action found for #{operation.inspect}. Skipping."
|
79
|
+
end
|
80
|
+
|
81
|
+
operation
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fn-salesforce
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stuart Corbishley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-10-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -187,7 +187,10 @@ files:
|
|
187
187
|
- lib/fn/salesforce/environment.rb
|
188
188
|
- lib/fn/salesforce/object.rb
|
189
189
|
- lib/fn/salesforce/object_factory.rb
|
190
|
+
- lib/fn/salesforce/operation.rb
|
190
191
|
- lib/fn/salesforce/options.rb
|
192
|
+
- lib/fn/salesforce/rollback.rb
|
193
|
+
- lib/fn/salesforce/transaction.rb
|
191
194
|
- lib/fn/salesforce/version.rb
|
192
195
|
- lib/fn/salesforce/walker.rb
|
193
196
|
homepage: https://github.com/stuartc/fn-salesforce
|