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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.gitignore +4 -0
  6. data/.rubocop.yml +4 -0
  7. data/.rubocop_todo.yml +25 -0
  8. data/.travis.yml +1 -1
  9. data/Dockerfile +8 -0
  10. data/Gemfile +3 -1
  11. data/Gemfile.lock +137 -8
  12. data/README.md +184 -9
  13. data/Rakefile +3 -3
  14. data/bin/console +3 -3
  15. data/docker-compose.yml +4 -0
  16. data/ledger_sync.gemspec +32 -11
  17. data/lib/ledger_sync.rb +115 -3
  18. data/lib/ledger_sync/adaptor_configuration.rb +55 -0
  19. data/lib/ledger_sync/adaptor_configuration_store.rb +52 -0
  20. data/lib/ledger_sync/adaptors/adaptor.rb +66 -0
  21. data/lib/ledger_sync/adaptors/contract.rb +16 -0
  22. data/lib/ledger_sync/adaptors/operation.rb +213 -0
  23. data/lib/ledger_sync/adaptors/quickbooks_online/adaptor.rb +161 -0
  24. data/lib/ledger_sync/adaptors/quickbooks_online/config.rb +7 -0
  25. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/create.rb +44 -0
  26. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/find.rb +35 -0
  27. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/update.rb +53 -0
  28. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/upsert.rb +42 -0
  29. data/lib/ledger_sync/adaptors/quickbooks_online/customer/searcher.rb +63 -0
  30. data/lib/ledger_sync/adaptors/quickbooks_online/invoice/operations/create.rb +63 -0
  31. data/lib/ledger_sync/adaptors/quickbooks_online/invoice/operations/find.rb +36 -0
  32. data/lib/ledger_sync/adaptors/quickbooks_online/invoice/operations/update.rb +67 -0
  33. data/lib/ledger_sync/adaptors/quickbooks_online/invoice/operations/upsert.rb +44 -0
  34. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/create.rb +64 -0
  35. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/find.rb +35 -0
  36. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/update.rb +64 -0
  37. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/upsert.rb +53 -0
  38. data/lib/ledger_sync/adaptors/quickbooks_online/product/operations/create.rb +46 -0
  39. data/lib/ledger_sync/adaptors/quickbooks_online/product/operations/find.rb +34 -0
  40. data/lib/ledger_sync/adaptors/quickbooks_online/product/operations/update.rb +50 -0
  41. data/lib/ledger_sync/adaptors/quickbooks_online/product/operations/upsert.rb +43 -0
  42. data/lib/ledger_sync/adaptors/quickbooks_online/util/adaptor_error_parser.rb +102 -0
  43. data/lib/ledger_sync/adaptors/quickbooks_online/util/error_matcher.rb +54 -0
  44. data/lib/ledger_sync/adaptors/quickbooks_online/util/error_parser.rb +27 -0
  45. data/lib/ledger_sync/adaptors/quickbooks_online/util/operation_error_parser.rb +96 -0
  46. data/lib/ledger_sync/adaptors/searcher.rb +64 -0
  47. data/lib/ledger_sync/adaptors/test/adaptor.rb +47 -0
  48. data/lib/ledger_sync/adaptors/test/config.rb +7 -0
  49. data/lib/ledger_sync/adaptors/test/customer/operations/create.rb +49 -0
  50. data/lib/ledger_sync/adaptors/test/customer/operations/find.rb +35 -0
  51. data/lib/ledger_sync/adaptors/test/customer/operations/invalid.rb +20 -0
  52. data/lib/ledger_sync/adaptors/test/customer/operations/update.rb +46 -0
  53. data/lib/ledger_sync/adaptors/test/customer/operations/upsert.rb +42 -0
  54. data/lib/ledger_sync/adaptors/test/customer/operations/valid.rb +26 -0
  55. data/lib/ledger_sync/adaptors/test/customer/searcher.rb +40 -0
  56. data/lib/ledger_sync/adaptors/test/error/adaptor_error/operations/throttle_error.rb +28 -0
  57. data/lib/ledger_sync/adaptors/test/payment/operations/create.rb +56 -0
  58. data/lib/ledger_sync/adaptors/test/payment/operations/find.rb +35 -0
  59. data/lib/ledger_sync/adaptors/test/payment/operations/update.rb +62 -0
  60. data/lib/ledger_sync/adaptors/test/payment/operations/upsert.rb +53 -0
  61. data/lib/ledger_sync/concerns/validatable.rb +19 -0
  62. data/lib/ledger_sync/core_ext/resonad.rb +16 -0
  63. data/lib/ledger_sync/error.rb +10 -0
  64. data/lib/ledger_sync/error/adaptor_errors.rb +47 -0
  65. data/lib/ledger_sync/error/operation_errors.rb +41 -0
  66. data/lib/ledger_sync/error/resource_errors.rb +20 -0
  67. data/lib/ledger_sync/resource.rb +92 -0
  68. data/lib/ledger_sync/resources/customer.rb +8 -0
  69. data/lib/ledger_sync/resources/invoice.rb +12 -0
  70. data/lib/ledger_sync/resources/payment.rb +11 -0
  71. data/lib/ledger_sync/resources/product.rb +6 -0
  72. data/lib/ledger_sync/resources/vendor.rb +6 -0
  73. data/lib/ledger_sync/result.rb +140 -0
  74. data/lib/ledger_sync/sync.rb +107 -0
  75. data/lib/ledger_sync/util/coordinator.rb +72 -0
  76. data/lib/ledger_sync/util/debug.rb +16 -0
  77. data/lib/ledger_sync/util/hash_helpers.rb +13 -0
  78. data/lib/ledger_sync/util/performer.rb +29 -0
  79. data/lib/ledger_sync/util/resources_builder.rb +68 -0
  80. data/lib/ledger_sync/util/string_helpers.rb +37 -0
  81. data/lib/ledger_sync/util/validator.rb +49 -0
  82. data/lib/ledger_sync/version.rb +1 -1
  83. data/release.sh +8 -0
  84. metadata +334 -11
  85. data/.rspec +0 -3
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
data/bin/console CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "ledger_sync"
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 "irb"
13
+ require 'irb'
14
14
  IRB.start(__FILE__)
@@ -0,0 +1,4 @@
1
+ version: '2'
2
+ services:
3
+ lib:
4
+ build: .
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 = 'ledger_sync'
9
- spec.version = LedgerSync::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.summary = 'Sync resources to accounting software.'
14
- spec.description = 'Ledger Sync is a simple library that allows you to sync resources to popular accounting software like QuickBooks Online, Xero, NetSuite, etc.'
15
- spec.homepage = 'https://github.com/Modern-Treasury/ledger_sync'
16
- spec.license = 'MIT'
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 'bundler', '~> 2.0'
28
- spec.add_development_dependency 'rake', '~> 10.0'
29
- spec.add_development_dependency 'rspec', '~> 3.0'
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
- require "ledger_sync/version"
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
- class Error < StandardError; end
5
- # Your code goes here...
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