fn-salesforce 0.1.0.pre → 0.2.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|