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.
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