ledger_sync 0.1.0 → 1.0.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 +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
|