ledger_sync 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +4 -0
- data/.rubocop_todo.yml +25 -0
- data/.travis.yml +1 -1
- data/Dockerfile +8 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +137 -8
- data/README.md +184 -9
- data/Rakefile +3 -3
- data/bin/console +3 -3
- data/docker-compose.yml +4 -0
- data/ledger_sync.gemspec +32 -11
- data/lib/ledger_sync.rb +115 -3
- data/lib/ledger_sync/adaptor_configuration.rb +55 -0
- data/lib/ledger_sync/adaptor_configuration_store.rb +52 -0
- data/lib/ledger_sync/adaptors/adaptor.rb +66 -0
- data/lib/ledger_sync/adaptors/contract.rb +16 -0
- data/lib/ledger_sync/adaptors/operation.rb +213 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/adaptor.rb +161 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/config.rb +7 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/create.rb +44 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/find.rb +35 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/update.rb +53 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/upsert.rb +42 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/customer/searcher.rb +63 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/invoice/operations/create.rb +63 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/invoice/operations/find.rb +36 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/invoice/operations/update.rb +67 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/invoice/operations/upsert.rb +44 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/create.rb +64 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/find.rb +35 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/update.rb +64 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/upsert.rb +53 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/product/operations/create.rb +46 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/product/operations/find.rb +34 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/product/operations/update.rb +50 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/product/operations/upsert.rb +43 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/util/adaptor_error_parser.rb +102 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/util/error_matcher.rb +54 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/util/error_parser.rb +27 -0
- data/lib/ledger_sync/adaptors/quickbooks_online/util/operation_error_parser.rb +96 -0
- data/lib/ledger_sync/adaptors/searcher.rb +64 -0
- data/lib/ledger_sync/adaptors/test/adaptor.rb +47 -0
- data/lib/ledger_sync/adaptors/test/config.rb +7 -0
- data/lib/ledger_sync/adaptors/test/customer/operations/create.rb +49 -0
- data/lib/ledger_sync/adaptors/test/customer/operations/find.rb +35 -0
- data/lib/ledger_sync/adaptors/test/customer/operations/invalid.rb +20 -0
- data/lib/ledger_sync/adaptors/test/customer/operations/update.rb +46 -0
- data/lib/ledger_sync/adaptors/test/customer/operations/upsert.rb +42 -0
- data/lib/ledger_sync/adaptors/test/customer/operations/valid.rb +26 -0
- data/lib/ledger_sync/adaptors/test/customer/searcher.rb +40 -0
- data/lib/ledger_sync/adaptors/test/error/adaptor_error/operations/throttle_error.rb +28 -0
- data/lib/ledger_sync/adaptors/test/payment/operations/create.rb +56 -0
- data/lib/ledger_sync/adaptors/test/payment/operations/find.rb +35 -0
- data/lib/ledger_sync/adaptors/test/payment/operations/update.rb +62 -0
- data/lib/ledger_sync/adaptors/test/payment/operations/upsert.rb +53 -0
- data/lib/ledger_sync/concerns/validatable.rb +19 -0
- data/lib/ledger_sync/core_ext/resonad.rb +16 -0
- data/lib/ledger_sync/error.rb +10 -0
- data/lib/ledger_sync/error/adaptor_errors.rb +47 -0
- data/lib/ledger_sync/error/operation_errors.rb +41 -0
- data/lib/ledger_sync/error/resource_errors.rb +20 -0
- data/lib/ledger_sync/resource.rb +92 -0
- data/lib/ledger_sync/resources/customer.rb +8 -0
- data/lib/ledger_sync/resources/invoice.rb +12 -0
- data/lib/ledger_sync/resources/payment.rb +11 -0
- data/lib/ledger_sync/resources/product.rb +6 -0
- data/lib/ledger_sync/resources/vendor.rb +6 -0
- data/lib/ledger_sync/result.rb +140 -0
- data/lib/ledger_sync/sync.rb +107 -0
- data/lib/ledger_sync/util/coordinator.rb +72 -0
- data/lib/ledger_sync/util/debug.rb +16 -0
- data/lib/ledger_sync/util/hash_helpers.rb +13 -0
- data/lib/ledger_sync/util/performer.rb +29 -0
- data/lib/ledger_sync/util/resources_builder.rb +68 -0
- data/lib/ledger_sync/util/string_helpers.rb +37 -0
- data/lib/ledger_sync/util/validator.rb +49 -0
- data/lib/ledger_sync/version.rb +1 -1
- data/release.sh +8 -0
- metadata +334 -11
- data/.rspec +0 -3
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'ledger_sync'
|
5
5
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +10,5 @@ require "ledger_sync"
|
|
10
10
|
# require "pry"
|
11
11
|
# Pry.start
|
12
12
|
|
13
|
-
require
|
13
|
+
require 'irb'
|
14
14
|
IRB.start(__FILE__)
|
data/docker-compose.yml
ADDED
data/ledger_sync.gemspec
CHANGED
@@ -5,15 +5,18 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
5
|
require 'ledger_sync/version'
|
6
6
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
|
-
spec.name
|
9
|
-
spec.version
|
10
|
-
spec.authors = ['Ryan Jackson']
|
11
|
-
spec.email = ['ryanwjackson@gmail.com']
|
8
|
+
spec.name = 'ledger_sync'
|
9
|
+
spec.version = LedgerSync::VERSION
|
12
10
|
|
13
|
-
spec.
|
14
|
-
spec.
|
15
|
-
spec.
|
16
|
-
spec.
|
11
|
+
# spec.required_rubygems_version = Gem::Requirement.new('>= 0') if spec.respond_to? :required_rubygems_version=
|
12
|
+
spec.authors = ['Ryan Jackson']
|
13
|
+
spec.date = '2019-05-21'
|
14
|
+
spec.description = 'LedgerSync is a simple library that allows you to sync common objects to popular accounting software like QuickBooks Online, Xero, NetSuite, etc.'
|
15
|
+
spec.email = ['ryanwjackson@gmail.com']
|
16
|
+
spec.homepage = 'https://github.com/LedgerSync/ledger_sync'
|
17
|
+
spec.licenses = ['MIT']
|
18
|
+
spec.rubygems_version = '3.0.3'
|
19
|
+
spec.summary = 'Sync common objects to accounting software.'
|
17
20
|
|
18
21
|
# Specify which files should be added to the gem when it is released.
|
19
22
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -24,7 +27,25 @@ Gem::Specification.new do |spec|
|
|
24
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
28
|
spec.require_paths = ['lib']
|
26
29
|
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
29
|
-
spec.add_development_dependency
|
30
|
+
spec.add_development_dependency('awesome_print', '>= 0')
|
31
|
+
spec.add_development_dependency('bump')
|
32
|
+
spec.add_development_dependency('bundler', '~> 1.16')
|
33
|
+
spec.add_development_dependency('byebug', '>= 0')
|
34
|
+
spec.add_development_dependency('rake', '~> 10.0')
|
35
|
+
spec.add_development_dependency('rspec', '~> 3.2')
|
36
|
+
spec.add_development_dependency('rubocop', '>= 0')
|
37
|
+
spec.add_development_dependency('vcr', '>= 0')
|
38
|
+
spec.add_development_dependency('webmock', '>= 0')
|
39
|
+
spec.add_runtime_dependency('colorize', '>= 0')
|
40
|
+
spec.add_runtime_dependency('coveralls')
|
41
|
+
spec.add_runtime_dependency('dry-schema')
|
42
|
+
spec.add_runtime_dependency('dry-validation')
|
43
|
+
spec.add_runtime_dependency('faraday', '>= 0')
|
44
|
+
spec.add_runtime_dependency('faraday-detailed_logger', '>= 0')
|
45
|
+
spec.add_runtime_dependency('faraday_middleware', '>= 0')
|
46
|
+
spec.add_runtime_dependency('fingerprintable', '>= 1.2.1')
|
47
|
+
spec.add_runtime_dependency('nokogiri', '>= 0')
|
48
|
+
spec.add_runtime_dependency('oauth2', '>= 0')
|
49
|
+
spec.add_runtime_dependency('resonad', '>= 0')
|
50
|
+
spec.add_runtime_dependency('simply_serializable', '>= 1.3.0')
|
30
51
|
end
|
data/lib/ledger_sync.rb
CHANGED
@@ -1,6 +1,118 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'dry-schema'
|
5
|
+
require 'dry-validation'
|
6
|
+
require 'logger'
|
7
|
+
require 'resonad'
|
8
|
+
require 'uri'
|
9
|
+
require 'colorize'
|
10
|
+
require 'fingerprintable'
|
11
|
+
require 'simply_serializable'
|
12
|
+
|
13
|
+
# Version
|
14
|
+
require 'ledger_sync/version'
|
15
|
+
|
16
|
+
# Concerns
|
17
|
+
require 'ledger_sync/concerns/validatable'
|
18
|
+
|
19
|
+
# Extensions
|
20
|
+
require 'ledger_sync/core_ext/resonad'
|
21
|
+
|
22
|
+
# Errors
|
23
|
+
require 'ledger_sync/error'
|
24
|
+
require 'ledger_sync/error/adaptor_errors'
|
25
|
+
require 'ledger_sync/error/operation_errors'
|
26
|
+
require 'ledger_sync/error/resource_errors'
|
27
|
+
|
28
|
+
# Support Classes
|
29
|
+
require 'ledger_sync/util/debug'
|
30
|
+
require 'ledger_sync/util/hash_helpers'
|
31
|
+
require 'ledger_sync/util/resources_builder'
|
32
|
+
require 'ledger_sync/adaptor_configuration'
|
33
|
+
require 'ledger_sync/adaptor_configuration_store'
|
34
|
+
require 'ledger_sync/util/coordinator'
|
35
|
+
require 'ledger_sync/util/performer'
|
36
|
+
require 'ledger_sync/util/validator'
|
37
|
+
require 'ledger_sync/util/string_helpers'
|
38
|
+
require 'ledger_sync/result'
|
39
|
+
require 'ledger_sync/adaptors/operation'
|
40
|
+
require 'ledger_sync/adaptors/contract'
|
41
|
+
|
42
|
+
# Resources (resources are registerd below)
|
43
|
+
require 'ledger_sync/resource' # Template class
|
44
|
+
require 'ledger_sync/resources/customer'
|
45
|
+
require 'ledger_sync/resources/vendor'
|
46
|
+
require 'ledger_sync/resources/invoice'
|
47
|
+
require 'ledger_sync/resources/payment'
|
48
|
+
require 'ledger_sync/resources/product'
|
49
|
+
|
50
|
+
# Synchronizer
|
51
|
+
require 'ledger_sync/sync'
|
2
52
|
|
3
53
|
module LedgerSync
|
4
|
-
|
5
|
-
|
54
|
+
@log_level = nil
|
55
|
+
@logger = nil
|
56
|
+
|
57
|
+
# map to the same values as the standard library's logger
|
58
|
+
LEVEL_DEBUG = Logger::DEBUG
|
59
|
+
LEVEL_ERROR = Logger::ERROR
|
60
|
+
LEVEL_INFO = Logger::INFO
|
61
|
+
|
62
|
+
class << self
|
63
|
+
attr_accessor :adaptors, :resources
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.log_level
|
67
|
+
@log_level
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.log_level=(val)
|
71
|
+
raise ArgumentError, 'log_level should only be set to `nil`, `debug` or `info`' if !val.nil? && ![LEVEL_DEBUG, LEVEL_ERROR, LEVEL_INFO].include?(val)
|
72
|
+
|
73
|
+
@log_level = val
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.logger
|
77
|
+
@logger
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.logger=(val)
|
81
|
+
@logger = val
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.register_adaptor(adaptor_key)
|
85
|
+
self.adaptors ||= LedgerSync::AdaptorConfigurationStore.new
|
86
|
+
self.adaptors.register_adaptor(adaptor_key)
|
87
|
+
yield(adaptors.send(adaptor_key))
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.register_resource(resource:)
|
91
|
+
self.resources ||= {}
|
92
|
+
raise "Resource key #{resource.resource_type} already exists." if resources.key?(resource.resource_type)
|
93
|
+
|
94
|
+
self.resources[resource.resource_type] = resource
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.root
|
98
|
+
File.dirname __dir__
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Adaptors
|
103
|
+
require 'ledger_sync/adaptors/adaptor'
|
104
|
+
require 'ledger_sync/adaptors/searcher'
|
105
|
+
Gem.find_files('ledger_sync/adaptors/**/*.rb').each do |path|
|
106
|
+
next if path.include?('config.rb')
|
107
|
+
|
108
|
+
require path
|
6
109
|
end
|
110
|
+
|
111
|
+
Gem.find_files('ledger_sync/adaptors/**/config.rb').each { |path| require path }
|
112
|
+
|
113
|
+
# Register Resources
|
114
|
+
LedgerSync.register_resource(resource: LedgerSync::Customer)
|
115
|
+
LedgerSync.register_resource(resource: LedgerSync::Vendor)
|
116
|
+
LedgerSync.register_resource(resource: LedgerSync::Invoice)
|
117
|
+
LedgerSync.register_resource(resource: LedgerSync::Payment)
|
118
|
+
LedgerSync.register_resource(resource: LedgerSync::Product)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
class AdaptorConfiguration
|
5
|
+
include Fingerprintable::Mixin
|
6
|
+
include SimplySerializable::Mixin
|
7
|
+
|
8
|
+
attr_accessor :module,
|
9
|
+
:name,
|
10
|
+
:rate_limiting_wait_in_seconds,
|
11
|
+
:test
|
12
|
+
|
13
|
+
attr_reader :aliases,
|
14
|
+
:root_key
|
15
|
+
|
16
|
+
serialize only: %i[
|
17
|
+
aliases
|
18
|
+
module
|
19
|
+
root_key
|
20
|
+
rate_limiting_wait_in_seconds
|
21
|
+
test
|
22
|
+
]
|
23
|
+
|
24
|
+
def initialize(root_key)
|
25
|
+
@root_key = root_key
|
26
|
+
@aliases = []
|
27
|
+
@module = LedgerSync::Util::StringHelpers.camelcase(root_key)
|
28
|
+
end
|
29
|
+
|
30
|
+
def adaptor_klass
|
31
|
+
@adaptor_klass ||= base_module::Adaptor
|
32
|
+
end
|
33
|
+
|
34
|
+
def base_module
|
35
|
+
@base_module ||= begin
|
36
|
+
LedgerSync::Adaptors.const_get(@module)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_alias(new_alias)
|
41
|
+
@aliases << new_alias
|
42
|
+
LedgerSync.adaptors.add_alias(new_alias, self)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Delegate new to the adaptor class enabling faster adaptor initialization
|
46
|
+
# e.g. LedgerSync.adaptors.test.new(...)
|
47
|
+
def new(*args)
|
48
|
+
adaptor_klass.new(*args)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test?
|
52
|
+
test == true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module LedgerSync
|
2
|
+
class AdaptorConfigurationStore
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :configs
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@keys = []
|
9
|
+
@configs = {}
|
10
|
+
@klass_configs = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_alias(adaptor_key, existing_config)
|
14
|
+
if respond_to?(adaptor_key)
|
15
|
+
raise LedgerSync::ConfigurationError, "Alias already taken: #{adaptor_key}" if send(adaptor_key) != existing_config
|
16
|
+
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
instance_methods_for(adaptor_key, existing_config)
|
21
|
+
end
|
22
|
+
|
23
|
+
def config_from_klass(klass:)
|
24
|
+
@klass_configs.fetch(klass)
|
25
|
+
end
|
26
|
+
|
27
|
+
def each
|
28
|
+
configs.each { |k, v| yield(k, v) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def register_adaptor(adaptor_key)
|
32
|
+
instance_methods_for(adaptor_key)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def instance_methods_for(adaptor_key, existing_config = nil)
|
38
|
+
@keys << adaptor_key.to_sym
|
39
|
+
|
40
|
+
config = existing_config || LedgerSync::AdaptorConfiguration.new(adaptor_key)
|
41
|
+
@configs[adaptor_key] = config
|
42
|
+
@klass_configs[config.adaptor_klass] = config
|
43
|
+
|
44
|
+
instance_variable_set(
|
45
|
+
"@#{adaptor_key}",
|
46
|
+
config
|
47
|
+
)
|
48
|
+
|
49
|
+
self.class.class_eval { attr_reader adaptor_key }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Adaptors
|
5
|
+
class Adaptor
|
6
|
+
include Fingerprintable::Mixin
|
7
|
+
include SimplySerializable::Mixin
|
8
|
+
include Validatable
|
9
|
+
|
10
|
+
serialize only: %i[
|
11
|
+
adaptor_configuration
|
12
|
+
]
|
13
|
+
|
14
|
+
def initialize(*)
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def adaptor_configuration
|
19
|
+
self.class.config
|
20
|
+
end
|
21
|
+
|
22
|
+
def base_module
|
23
|
+
adaptor_configuration.base_module
|
24
|
+
end
|
25
|
+
|
26
|
+
def ledger_attributes_to_save
|
27
|
+
return {} if self.class.ledger_attributes_to_save.nil?
|
28
|
+
|
29
|
+
Hash[self.class.ledger_attributes_to_save.map do |attribute|
|
30
|
+
[attribute, send(attribute)]
|
31
|
+
end]
|
32
|
+
end
|
33
|
+
|
34
|
+
def searcher_for?(resource_type:)
|
35
|
+
searcher_klass_for(resource_type: resource_type)
|
36
|
+
rescue NameError
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def searcher_klass_for(resource_type:)
|
41
|
+
base_module.const_get(LedgerSync::Util::StringHelpers.camelcase(resource_type.to_s))::Searcher
|
42
|
+
end
|
43
|
+
|
44
|
+
def url_for(*_args)
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_operation_error(*)
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.config
|
53
|
+
@config ||= LedgerSync.adaptors.config_from_klass(klass: self)
|
54
|
+
end
|
55
|
+
|
56
|
+
# These are attributes that must always be saved after the adaptor is called.
|
57
|
+
# For example, the library will handle refreshing tokens that will need
|
58
|
+
# to be saved back to the application layer for future use.
|
59
|
+
def self.ledger_attributes_to_save
|
60
|
+
raise NotImplementedError
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.url_for(resource: nil); end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Adaptors
|
5
|
+
class Contract < Dry::Validation::Contract
|
6
|
+
module Types
|
7
|
+
include Dry::Types()
|
8
|
+
|
9
|
+
Reference = Dry::Schema.Params do
|
10
|
+
required(:object).filled(eql?: :reference)
|
11
|
+
required(:id).filled(:string)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Adaptors
|
5
|
+
module Operation
|
6
|
+
TYPES = %i[create find update upsert].freeze
|
7
|
+
|
8
|
+
module Mixin
|
9
|
+
module ClassMethods
|
10
|
+
def adaptor_klass
|
11
|
+
@adaptor_klass ||= Class.const_get("#{name.split('::')[0..2].join('::')}::Adaptor")
|
12
|
+
end
|
13
|
+
|
14
|
+
def resource_klass
|
15
|
+
@resource_klass ||= LedgerSync.const_get(
|
16
|
+
name
|
17
|
+
.split("#{adaptor_klass.config.base_module.name}::")
|
18
|
+
.last
|
19
|
+
.split('::Operations')
|
20
|
+
.first
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.included(base)
|
26
|
+
base.include SimplySerializable::Mixin
|
27
|
+
base.include Fingerprintable::Mixin
|
28
|
+
# base.include Validatable
|
29
|
+
base.extend ClassMethods
|
30
|
+
|
31
|
+
base.class_eval do
|
32
|
+
serialize only: %i[
|
33
|
+
adaptor
|
34
|
+
after_operations
|
35
|
+
before_operations
|
36
|
+
operations
|
37
|
+
resource
|
38
|
+
root_operation
|
39
|
+
result
|
40
|
+
response
|
41
|
+
original
|
42
|
+
]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :adaptor,
|
47
|
+
:after_operations,
|
48
|
+
:before_operations,
|
49
|
+
:operations,
|
50
|
+
:resource,
|
51
|
+
:root_operation,
|
52
|
+
:result,
|
53
|
+
:response,
|
54
|
+
:original
|
55
|
+
|
56
|
+
def initialize(adaptor:, resource:)
|
57
|
+
raise 'Missing adaptor' if adaptor.nil?
|
58
|
+
raise 'Missing resource' if resource.nil?
|
59
|
+
|
60
|
+
raise "#{resource.class.name} is not a valid resource type. Expected #{self.class.resource_klass.name}" unless resource.is_a?(self.class.resource_klass)
|
61
|
+
|
62
|
+
@adaptor = adaptor
|
63
|
+
@after_operations = []
|
64
|
+
@before_operations = []
|
65
|
+
@operations = []
|
66
|
+
@resource = resource
|
67
|
+
@result = nil
|
68
|
+
@root_operation = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def add_after_operation(operation)
|
72
|
+
@operations << operation
|
73
|
+
@after_operations << operation
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_before_operation(operation)
|
77
|
+
@operations << operation
|
78
|
+
@before_operations << operation
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_root_operation(operation)
|
82
|
+
@operations << operation
|
83
|
+
@root_operation = operation
|
84
|
+
end
|
85
|
+
|
86
|
+
def perform
|
87
|
+
failure(LedgerSync::Error::OperationError::PerformedOperationError.new(operation: self)) if @performed
|
88
|
+
|
89
|
+
begin
|
90
|
+
operate
|
91
|
+
rescue LedgerSync::Error => e
|
92
|
+
failure(e)
|
93
|
+
rescue => e
|
94
|
+
parsed_error = adaptor.parse_operation_error(error: e, operation: self)
|
95
|
+
raise e unless parsed_error
|
96
|
+
|
97
|
+
failure(parsed_error)
|
98
|
+
ensure
|
99
|
+
@performed = true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def performed?
|
104
|
+
@performed == true
|
105
|
+
end
|
106
|
+
|
107
|
+
def prepare
|
108
|
+
build
|
109
|
+
end
|
110
|
+
|
111
|
+
# Results
|
112
|
+
|
113
|
+
def failure(error)
|
114
|
+
@result = LedgerSync::OperationResult.Failure(
|
115
|
+
error,
|
116
|
+
operation: self,
|
117
|
+
response: error
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
def failure?
|
122
|
+
result.failure?
|
123
|
+
end
|
124
|
+
|
125
|
+
def success(response:)
|
126
|
+
@result = LedgerSync::OperationResult.Success(
|
127
|
+
self,
|
128
|
+
operation: self,
|
129
|
+
response: response
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
def success?
|
134
|
+
result.success?
|
135
|
+
end
|
136
|
+
|
137
|
+
def validate
|
138
|
+
raise "#{self.class.name}::Contract must be defined to validate." unless self.class.const_defined?('Contract')
|
139
|
+
|
140
|
+
serializer = resource.serializer
|
141
|
+
serialized_resource = serializer.serialize[:objects][serializer.id][:data]
|
142
|
+
|
143
|
+
Util::Validator.new(
|
144
|
+
contract: self.class::Contract,
|
145
|
+
data: serialized_resource
|
146
|
+
).validate
|
147
|
+
end
|
148
|
+
|
149
|
+
def valid?
|
150
|
+
validate.success?
|
151
|
+
end
|
152
|
+
|
153
|
+
# Comparison
|
154
|
+
|
155
|
+
def ==(other)
|
156
|
+
return false unless self.class == other.class
|
157
|
+
return false unless resource == other.resource
|
158
|
+
|
159
|
+
true
|
160
|
+
end
|
161
|
+
|
162
|
+
# Type Methods
|
163
|
+
|
164
|
+
TYPES.each do |type|
|
165
|
+
define_method "#{type.to_s.downcase}?" do
|
166
|
+
false
|
167
|
+
end
|
168
|
+
|
169
|
+
define_method "convert_to_#{type.to_s.downcase}" do
|
170
|
+
update_klass_name = self.class.name.split('::')[0..-2].append(LedgerSync::Util::StringHelpers.camelcase(type.to_s)).join('::')
|
171
|
+
Object.const_get(update_klass_name).new(adaptor: adaptor, resource: resource)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def merge_into(from:, to:)
|
176
|
+
case to
|
177
|
+
when String, Integer, Array then from
|
178
|
+
else to.merge!(from) { |_key, old_value, new_value| merge_into(from: old_value, to: new_value) } if to && from
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
def build
|
185
|
+
add_root_operation self
|
186
|
+
end
|
187
|
+
|
188
|
+
def operate
|
189
|
+
raise NotImplementedError, self.class.name
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
TYPES.each do |type|
|
194
|
+
klass = Class.new do
|
195
|
+
include Operation::Mixin
|
196
|
+
|
197
|
+
define_method("#{type.to_s.downcase}?") do
|
198
|
+
true
|
199
|
+
end
|
200
|
+
end
|
201
|
+
Operation.const_set(LedgerSync::Util::StringHelpers.camelcase(type.to_s), klass)
|
202
|
+
end
|
203
|
+
|
204
|
+
def self.klass_from(adaptor:, method:, object:)
|
205
|
+
adaptor.base_module.const_get(
|
206
|
+
LedgerSync::Util::StringHelpers.camelcase(object)
|
207
|
+
)::Operations.const_get(
|
208
|
+
LedgerSync::Util::StringHelpers.camelcase(method)
|
209
|
+
)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|