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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 56c8050e52f4dd1c6895fd3e89c12eeab60e8f19
4
- data.tar.gz: 7ade6ae5a5a2fbaa5da19dd9d8de761b5e16e769
3
+ metadata.gz: 82f216e7b819a1f7b0a01c9d6c5e01ba488bae78
4
+ data.tar.gz: ab224735759865beefcb5a3ccd1d8fd5e59f6ecd
5
5
  SHA512:
6
- metadata.gz: 94bde1299413a5afd4612cb0e632a54cc453a9fff90b0e2d493a71e1e4d19843cb495852a7694870f28839f421e83d8ced4196d4fba84d86fd56b6f1ef6a3ffe
7
- data.tar.gz: c0a9885db5ac8165562a582854d252de327c23c7260cd11aa5c79f7597273335d928e383f0abe77177329ff4a7d5e99857ae3355a6783e8d4004ff23aded4505
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/object"
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
- inject([]) { |plan,obj|
22
-
23
- begin
24
- obj["properties"].merge! obj["properties"].
25
- map { |k,v|
26
- if v.is_a?(Hash) && v["$ref"]
27
- v = Hana::Pointer.new(v["$ref"]).eval(plan)
28
- end
29
- [k,v]
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
- sobject = sobject_for(key) || key
22
- properties.merge! Hash[foreign_key_for(key), {"$ref" => "/#{parent}/properties/Id"}] if parent
23
-
24
- {
25
- "sObject" => sobject,
26
- "properties" => properties
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
- credentials = JSON.parse(File.read(@attributes[:credentials]))
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: credentials["username"],
16
- password: credentials["password"],
17
- security_token: credentials["token"],
18
- client_id: credentials["key"],
19
- client_secret: credentials["secret"],
20
- host: credentials["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
@@ -1,5 +1,5 @@
1
1
  module Fn
2
2
  module Salesforce
3
- VERSION = "0.1.0.pre"
3
+ VERSION = "0.2.0.pre"
4
4
  end
5
5
  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.1.0.pre
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-08-14 00:00:00.000000000 Z
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