ledger_sync 1.1.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +2 -3
  5. data/lib/ledger_sync.rb +12 -6
  6. data/lib/ledger_sync/adaptors/ledger_serializer.rb +124 -0
  7. data/lib/ledger_sync/adaptors/ledger_serializer_attribute.rb +148 -0
  8. data/lib/ledger_sync/adaptors/ledger_serializer_attribute_set.rb +51 -0
  9. data/lib/ledger_sync/adaptors/ledger_serializer_type/mapping_type.rb +41 -0
  10. data/lib/ledger_sync/adaptors/ledger_serializer_type/references_many_type.rb +24 -0
  11. data/lib/ledger_sync/adaptors/ledger_serializer_type/value_type.rb +17 -0
  12. data/lib/ledger_sync/adaptors/operation.rb +15 -9
  13. data/lib/ledger_sync/adaptors/quickbooks_online/account/ledger_serializer.rb +21 -0
  14. data/lib/ledger_sync/adaptors/quickbooks_online/account/operations/create.rb +8 -36
  15. data/lib/ledger_sync/adaptors/quickbooks_online/account/operations/find.rb +1 -13
  16. data/lib/ledger_sync/adaptors/quickbooks_online/account/operations/update.rb +4 -35
  17. data/lib/ledger_sync/adaptors/quickbooks_online/account/searcher.rb +3 -44
  18. data/lib/ledger_sync/adaptors/quickbooks_online/adaptor.rb +75 -28
  19. data/lib/ledger_sync/adaptors/quickbooks_online/bill/ledger_serializer.rb +40 -0
  20. data/lib/ledger_sync/adaptors/quickbooks_online/bill/operations/create.rb +9 -48
  21. data/lib/ledger_sync/adaptors/quickbooks_online/bill/operations/find.rb +10 -21
  22. data/lib/ledger_sync/adaptors/quickbooks_online/bill/operations/update.rb +9 -52
  23. data/lib/ledger_sync/adaptors/quickbooks_online/bill_line_item/ledger_serializer.rb +27 -0
  24. data/lib/ledger_sync/adaptors/quickbooks_online/customer/ledger_serializer.rb +23 -0
  25. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/create.rb +2 -25
  26. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/find.rb +2 -14
  27. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/update.rb +3 -30
  28. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/ledger_serializer.rb +35 -0
  29. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/operations/create.rb +2 -39
  30. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/operations/find.rb +2 -14
  31. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/operations/update.rb +4 -43
  32. data/lib/ledger_sync/adaptors/quickbooks_online/deposit_line_item/ledger_serializer.rb +27 -0
  33. data/lib/ledger_sync/adaptors/quickbooks_online/expense/ledger_serializer.rb +48 -0
  34. data/lib/ledger_sync/adaptors/quickbooks_online/expense/operations/create.rb +10 -51
  35. data/lib/ledger_sync/adaptors/quickbooks_online/expense/operations/find.rb +10 -22
  36. data/lib/ledger_sync/adaptors/quickbooks_online/expense/operations/update.rb +10 -55
  37. data/lib/ledger_sync/adaptors/quickbooks_online/expense_line_item/ledger_serializer.rb +27 -0
  38. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/ledger_serializer.rb +32 -0
  39. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/operations/create.rb +4 -37
  40. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/operations/find.rb +4 -15
  41. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/operations/update.rb +5 -42
  42. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry_line_item/ledger_serializer.rb +31 -0
  43. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer.rb +103 -0
  44. data/lib/ledger_sync/adaptors/quickbooks_online/{account/mapping.rb → ledger_serializer_type/account_sub_type.rb} +128 -148
  45. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/account_type.rb +51 -0
  46. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/amount_type.rb +23 -0
  47. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/classification_type.rb +23 -0
  48. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/date_type.rb +23 -0
  49. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/journal_entry_line_item_type.rb +20 -0
  50. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/payment_type.rb +21 -0
  51. data/lib/ledger_sync/adaptors/quickbooks_online/operation.rb +2 -7
  52. data/lib/ledger_sync/adaptors/quickbooks_online/operation/create.rb +29 -0
  53. data/lib/ledger_sync/adaptors/quickbooks_online/operation/find.rb +31 -0
  54. data/lib/ledger_sync/adaptors/quickbooks_online/operation/full_update.rb +43 -0
  55. data/lib/ledger_sync/adaptors/quickbooks_online/operation/sparse_update.rb +29 -0
  56. data/lib/ledger_sync/adaptors/quickbooks_online/payment/ledger_serializer.rb +23 -0
  57. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/create.rb +1 -24
  58. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/find.rb +1 -13
  59. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/update.rb +2 -29
  60. data/lib/ledger_sync/adaptors/quickbooks_online/transfer/ledger_serializer.rb +33 -0
  61. data/lib/ledger_sync/adaptors/quickbooks_online/transfer/operations/create.rb +1 -29
  62. data/lib/ledger_sync/adaptors/quickbooks_online/transfer/operations/find.rb +1 -13
  63. data/lib/ledger_sync/adaptors/quickbooks_online/transfer/operations/update.rb +4 -35
  64. data/lib/ledger_sync/adaptors/quickbooks_online/util/adaptor_error_parser.rb +2 -1
  65. data/lib/ledger_sync/adaptors/quickbooks_online/util/operation_error_parser.rb +2 -1
  66. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/ledger_serializer.rb +25 -0
  67. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/operations/create.rb +1 -23
  68. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/operations/find.rb +1 -13
  69. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/operations/update.rb +2 -29
  70. data/lib/ledger_sync/adaptors/test/account/operations/create.rb +5 -1
  71. data/lib/ledger_sync/adaptors/test/account/operations/find.rb +5 -1
  72. data/lib/ledger_sync/adaptors/test/account/operations/invalid.rb +1 -0
  73. data/lib/ledger_sync/adaptors/test/account/operations/update.rb +8 -2
  74. data/lib/ledger_sync/adaptors/test/account/operations/valid.rb +1 -0
  75. data/lib/ledger_sync/adaptors/test/customer/operations/create.rb +5 -1
  76. data/lib/ledger_sync/adaptors/test/customer/operations/find.rb +5 -1
  77. data/lib/ledger_sync/adaptors/test/customer/operations/invalid.rb +1 -0
  78. data/lib/ledger_sync/adaptors/test/customer/operations/update.rb +1 -24
  79. data/lib/ledger_sync/adaptors/test/customer/operations/valid.rb +3 -6
  80. data/lib/ledger_sync/adaptors/test/expense/operations/create.rb +8 -3
  81. data/lib/ledger_sync/adaptors/test/expense/operations/find.rb +4 -14
  82. data/lib/ledger_sync/adaptors/test/expense/operations/update.rb +2 -32
  83. data/lib/ledger_sync/adaptors/test/ledger_serializer.rb +64 -0
  84. data/lib/ledger_sync/adaptors/test/operation/create.rb +27 -0
  85. data/lib/ledger_sync/adaptors/test/operation/find.rb +29 -0
  86. data/lib/ledger_sync/adaptors/test/operation/update.rb +34 -0
  87. data/lib/ledger_sync/adaptors/test/payment/operations/create.rb +5 -1
  88. data/lib/ledger_sync/adaptors/test/payment/operations/find.rb +5 -1
  89. data/lib/ledger_sync/adaptors/test/payment/operations/update.rb +1 -23
  90. data/lib/ledger_sync/adaptors/test/transfer/operations/create.rb +1 -22
  91. data/lib/ledger_sync/adaptors/test/transfer/operations/find.rb +1 -13
  92. data/lib/ledger_sync/adaptors/test/transfer/operations/update.rb +1 -25
  93. data/lib/ledger_sync/adaptors/test/vendor/operations/create.rb +5 -1
  94. data/lib/ledger_sync/adaptors/test/vendor/operations/find.rb +7 -1
  95. data/lib/ledger_sync/adaptors/test/vendor/operations/invalid.rb +1 -0
  96. data/lib/ledger_sync/adaptors/test/vendor/operations/update.rb +1 -23
  97. data/lib/ledger_sync/adaptors/test/vendor/operations/valid.rb +1 -0
  98. data/lib/ledger_sync/error/adaptor_errors.rb +17 -5
  99. data/lib/ledger_sync/error/operation_errors.rb +12 -5
  100. data/lib/ledger_sync/error/resource_errors.rb +5 -1
  101. data/lib/ledger_sync/resource.rb +33 -6
  102. data/lib/ledger_sync/resource_attribute.rb +4 -0
  103. data/lib/ledger_sync/resource_attribute/dirty_mixin.rb +16 -0
  104. data/lib/ledger_sync/resource_attribute/mixin.rb +19 -7
  105. data/lib/ledger_sync/resource_attribute/reference/many.rb +55 -1
  106. data/lib/ledger_sync/resource_attribute_set.rb +6 -7
  107. data/lib/ledger_sync/resources/account.rb +1 -1
  108. data/lib/ledger_sync/resources/bill.rb +1 -0
  109. data/lib/ledger_sync/resources/bill_line_item.rb +2 -0
  110. data/lib/ledger_sync/resources/expense.rb +5 -1
  111. data/lib/ledger_sync/resources/expense_line_item.rb +2 -0
  112. data/lib/ledger_sync/resources/journal_entry.rb +1 -0
  113. data/lib/ledger_sync/result.rb +4 -3
  114. data/lib/ledger_sync/type/id.rb +34 -0
  115. data/lib/ledger_sync/type/reference_many.rb +8 -2
  116. data/lib/ledger_sync/type/reference_one.rb +5 -0
  117. data/lib/ledger_sync/type/value_mixin.rb +12 -0
  118. data/lib/ledger_sync/util/hash_helpers.rb +16 -0
  119. data/lib/ledger_sync/version.rb +1 -1
  120. data/qa.rb +142 -0
  121. metadata +38 -5
  122. data/lib/ledger_sync/adaptors/quickbooks_online/expense/mapping.rb +0 -15
  123. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/mapping.rb +0 -14
  124. data/lib/ledger_sync/error/sync_errors.rb +0 -22
@@ -6,6 +6,7 @@ module LedgerSync
6
6
  class Find < Operation::Find
7
7
  class Contract < LedgerSync::Adaptors::Contract
8
8
  schema do
9
+ required(:external_id).maybe(:string)
9
10
  required(:ledger_id).filled(:string)
10
11
  required(:from_account).hash(Types::Reference)
11
12
  required(:to_account).hash(Types::Reference)
@@ -15,19 +16,6 @@ module LedgerSync
15
16
  required(:transaction_date).maybe(:date?)
16
17
  end
17
18
  end
18
-
19
- private
20
-
21
- def operate
22
- return failure(nil) if resource.ledger_id.nil?
23
-
24
- response = adaptor.find(
25
- resource: 'transfer',
26
- id: resource.ledger_id
27
- )
28
-
29
- success(response: response)
30
- end
31
19
  end
32
20
  end
33
21
  end
@@ -6,6 +6,7 @@ module LedgerSync
6
6
  class Update < Operation::Update
7
7
  class Contract < LedgerSync::Adaptors::Contract
8
8
  params do
9
+ required(:external_id).maybe(:string)
9
10
  required(:ledger_id).filled(:string)
10
11
  required(:from_account).hash(Types::Reference)
11
12
  required(:to_account).hash(Types::Reference)
@@ -15,31 +16,6 @@ module LedgerSync
15
16
  required(:transaction_date).filled(:date?)
16
17
  end
17
18
  end
18
-
19
- private
20
-
21
- def operate
22
- ledger_resource_data = adaptor.find(
23
- resource: 'transfer',
24
- id: resource.ledger_id
25
- )
26
- response = adaptor.post(
27
- resource: 'transfer',
28
- payload: merge_into(from: local_resource_data, to: ledger_resource_data)
29
- )
30
-
31
- success(response: response)
32
- end
33
-
34
- def local_resource_data
35
- {
36
- 'amount' => resource.amount,
37
- 'currency' => resource.currency,
38
- 'from_account_id' => resource.from_account.ledger_id,
39
- 'to_account_id' => resource.to_account.ledger_id,
40
- 'date' => resource.transaction_date
41
- }
42
- end
43
19
  end
44
20
  end
45
21
  end
@@ -6,6 +6,7 @@ module LedgerSync
6
6
  class Create < Operation::Create
7
7
  class Contract < LedgerSync::Adaptors::Contract
8
8
  schema do
9
+ required(:external_id).maybe(:string)
9
10
  required(:ledger_id).value(:nil)
10
11
  required(:display_name).maybe(:string)
11
12
  required(:first_name).maybe(:string)
@@ -37,7 +38,10 @@ module LedgerSync
37
38
 
38
39
  resource.ledger_id = response.dig('id')
39
40
 
40
- success(response: response)
41
+ success(
42
+ resource: Test::LedgerSerializer.new(resource: resource).deserialize(hash: response),
43
+ response: response
44
+ )
41
45
  end
42
46
  end
43
47
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module LedgerSync
2
4
  module Adaptors
3
5
  module Test
@@ -6,6 +8,7 @@ module LedgerSync
6
8
  class Find < Operation::Find
7
9
  class Contract < LedgerSync::Adaptors::Contract
8
10
  params do
11
+ required(:external_id).maybe(:string)
9
12
  required(:ledger_id).filled(:string)
10
13
  optional(:display_name).maybe(:string)
11
14
  optional(:first_name).maybe(:string)
@@ -24,7 +27,10 @@ module LedgerSync
24
27
  id: resource.ledger_id
25
28
  )
26
29
 
27
- success(response: response)
30
+ success(
31
+ resource: Test::LedgerSerializer.new(resource: resource).deserialize(hash: response),
32
+ response: response
33
+ )
28
34
  end
29
35
  end
30
36
  end
@@ -6,6 +6,7 @@ module LedgerSync
6
6
  class Invalid < Operation::Create
7
7
  class Contract < LedgerSync::Adaptors::Contract
8
8
  schema do
9
+ required(:external_id).filled(:string)
9
10
  required(:ledger_id).filled(:string)
10
11
  required(:display_name).filled(:string)
11
12
  required(:first_name).filled(:string)
@@ -6,6 +6,7 @@ module LedgerSync
6
6
  class Update < Operation::Update
7
7
  class Contract < LedgerSync::Adaptors::Contract
8
8
  schema do
9
+ required(:external_id).maybe(:string)
9
10
  required(:ledger_id).filled(:string)
10
11
  required(:display_name).maybe(:string)
11
12
  required(:first_name).maybe(:string)
@@ -13,29 +14,6 @@ module LedgerSync
13
14
  optional(:email).maybe(:string)
14
15
  end
15
16
  end
16
-
17
- private
18
-
19
- def operate
20
- ledger_resource_data = adaptor.find(
21
- resource: 'vendor',
22
- id: resource.ledger_id
23
- )
24
-
25
- response = adaptor.post(
26
- resource: 'vendor',
27
- payload: merge_into(from: local_resource_data, to: ledger_resource_data)
28
- )
29
-
30
- success(response: response)
31
- end
32
-
33
- def local_resource_data
34
- {
35
- 'name' => resource.name,
36
- 'email' => resource.email
37
- }
38
- end
39
17
  end
40
18
  end
41
19
  end
@@ -6,6 +6,7 @@ module LedgerSync
6
6
  class Valid < Operation::Create
7
7
  class Contract < LedgerSync::Adaptors::Contract
8
8
  schema do
9
+ optional(:external_id).maybe(:string)
9
10
  optional(:ledger_id).maybe(:string)
10
11
  optional(:display_name).maybe(:string)
11
12
  optional(:first_name).maybe(:string)
@@ -4,15 +4,20 @@ module LedgerSync
4
4
  class Error
5
5
  class AdaptorError < Error
6
6
  attr_reader :adaptor
7
+ attr_reader :response
7
8
 
8
- def initialize(adaptor:, message:)
9
+ def initialize(adaptor:, message:, response:nil)
9
10
  @adaptor = adaptor
11
+ @response = response
10
12
  super(message: message)
11
13
  end
12
14
 
13
15
  class MissingAdaptorError < self
14
16
  def initialize(message:)
15
- super(message: message, adaptor: nil)
17
+ super(
18
+ message: message,
19
+ adaptor: nil,
20
+ )
16
21
  end
17
22
  end
18
23
 
@@ -22,20 +27,27 @@ module LedgerSync
22
27
  def initialize(message:, adaptor:, attribute:, validation:)
23
28
  @attribute = attribute
24
29
  @validation = validation
25
- super(message: message, adaptor: adaptor)
30
+ super(
31
+ message: message,
32
+ adaptor: adaptor,
33
+ )
26
34
  end
27
35
  end
28
36
 
29
37
  class ThrottleError < self
30
38
  attr_reader :rate_limiting_wait_in_seconds
31
39
 
32
- def initialize(adaptor:, message: nil)
40
+ def initialize(adaptor:, message: nil, response:nil)
33
41
  message ||= 'Your request has been throttled.'
34
42
  @rate_limiting_wait_in_seconds = LedgerSync.adaptors.config_from_klass(
35
43
  klass: adaptor.class
36
44
  ).rate_limiting_wait_in_seconds
37
45
 
38
- super(adaptor: adaptor, message: message)
46
+ super(
47
+ adaptor: adaptor,
48
+ message: message,
49
+ response: response
50
+ )
39
51
  end
40
52
  end
41
53
 
@@ -4,9 +4,11 @@ module LedgerSync
4
4
  class Error
5
5
  class OperationError < Error
6
6
  attr_reader :operation
7
+ attr_reader :response
7
8
 
8
- def initialize(message:, operation:)
9
+ def initialize(message:, operation:, response:nil)
9
10
  @operation = operation
11
+ @response = response
10
12
  super(message: message)
11
13
  end
12
14
 
@@ -15,10 +17,14 @@ module LedgerSync
15
17
  class LedgerValidationError < self; end
16
18
 
17
19
  class PerformedOperationError < self
18
- def initialize(message: nil, operation:)
20
+ def initialize(message: nil, operation:, response:nil)
19
21
  message ||= 'Operation has already been performed. Please check the result.'
20
22
 
21
- super(message: message, operation: operation)
23
+ super(
24
+ message: message,
25
+ operation: operation,
26
+ response: response
27
+ )
22
28
  end
23
29
  end
24
30
 
@@ -26,13 +32,14 @@ module LedgerSync
26
32
  attr_reader :attribute,
27
33
  :validation
28
34
 
29
- def initialize(message:, attribute:, operation:, validation:)
35
+ def initialize(message:, attribute:, operation:, validation:, response:nil)
30
36
  @attribute = attribute
31
37
  @validation = validation
32
38
 
33
39
  super(
34
40
  message: message,
35
- operation: operation
41
+ operation: operation,
42
+ response: response
36
43
  )
37
44
  end
38
45
  end
@@ -16,7 +16,11 @@ module LedgerSync
16
16
 
17
17
  resource_class = resource.class
18
18
 
19
- message = "Attribute #{attribute.name} for #{resource_class.name} should be a class supported by #{attribute.type.class.name}. Given: #{value.class}"
19
+ message = attribute.type.error_message(
20
+ attribute: attribute,
21
+ resource: resource,
22
+ value: value
23
+ )
20
24
 
21
25
  super(message: message, resource: nil)
22
26
  end
@@ -14,6 +14,7 @@ module LedgerSync
14
14
  include ResourceAttribute::Reference::Many::Mixin
15
15
 
16
16
  PRIMITIVES = [
17
+ ActiveModel::Type,
17
18
  Date,
18
19
  DateTime,
19
20
  FalseClass,
@@ -27,24 +28,50 @@ module LedgerSync
27
28
 
28
29
  serialize except: %i[resource_attributes references]
29
30
 
30
- dirty_attribute :external_id, :ledger_id, :sync_token
31
+ def assign_attribute(name, value)
32
+ public_send("#{name}=", value)
33
+ end
34
+
35
+ def assign_attributes(**keywords)
36
+ keywords.each { |k, v| assign_attribute(k, v) }
37
+ end
31
38
 
32
- def initialize(external_id: nil, ledger_id: nil, sync_token: nil, **data)
33
- @external_id = external_id.try(:to_sym)
34
- @ledger_id = ledger_id
35
- @sync_token = sync_token
39
+ def changed?
40
+ super || resource_attributes.references_many.select(&:changed?).any?
41
+ end
36
42
 
37
- super(data)
43
+ def changes
44
+ super.merge(Hash[resource_attributes.references_many.map { |ref| [ref.name, ref.changes['value']] if ref.changed? }.compact])
45
+ end
46
+
47
+ def dup
48
+ Marshal.load(Marshal.dump(self))
38
49
  end
39
50
 
40
51
  def klass_from_resource_type(obj)
41
52
  LedgerSync.const_get(LedgerSync::Util::StringHelpers.camelcase(obj))
42
53
  end
43
54
 
55
+ def to_h
56
+ resource_attributes.to_h.merge(dirty_attributes_to_h)
57
+ end
58
+
59
+ def self.inherited(subclass)
60
+ subclass.attribute :external_id, type: Type::ID
61
+ subclass.attribute :ledger_id, type: Type::ID
62
+ end
63
+
44
64
  def self.resource_type
45
65
  @resource_type ||= LedgerSync::Util::StringHelpers.underscore(name.split('::').last).to_sym
46
66
  end
47
67
 
68
+ def self.serialize_attribute?(sattr)
69
+ sattr = sattr.to_sym
70
+ return true if resource_attributes.key?(sattr)
71
+
72
+ false
73
+ end
74
+
48
75
  def ==(other)
49
76
  other.fingerprint == fingerprint
50
77
  end
@@ -43,6 +43,10 @@ module LedgerSync
43
43
  is_a?(Reference)
44
44
  end
45
45
 
46
+ def references_many?
47
+ is_a?(Reference::Many)
48
+ end
49
+
46
50
  def valid_with?(value:)
47
51
  type.valid_without_casting?(value: value)
48
52
  end
@@ -32,6 +32,21 @@ module LedgerSync
32
32
  end
33
33
  end
34
34
 
35
+ # Change the dirty change set of {"name" => ["Bill", "Bob"]}
36
+ # to current values of attributes that have changed: {"name" => "Bob"}
37
+ def changes_to_h
38
+ Hash[changes.map { |k, v| [k, v.last] }]
39
+ end
40
+
41
+ def dirty_attributes_to_h
42
+ Hash[self.class.dirty_attributes.keys.map do |k|
43
+ [
44
+ k,
45
+ public_send(k)
46
+ ]
47
+ end]
48
+ end
49
+
35
50
  # Normally you would just call `changes_applied`, but because we
36
51
  # define an `@attributes` instance variable, the `ActiveModel::Dirty`
37
52
  # mixin assumes it is a list of attributes in their format (spoiler: it
@@ -43,6 +58,7 @@ module LedgerSync
43
58
  @mutations_before_last_save = mutations_from_database
44
59
  # forget_attribute_assignments # skipped as our attributes do not implement this method
45
60
  @mutations_from_database = ActiveModel::ForcedMutationTracker.new(self) # Manually set to expected value
61
+ resource_attributes.references_many.map(&:save)
46
62
  end
47
63
  end
48
64
  end
@@ -21,19 +21,21 @@ module LedgerSync
21
21
  resource_attributes[name].value
22
22
  end
23
23
 
24
- define_method "#{name}=" do |val|
25
- public_send("#{name}_will_change!") unless val == resource_attributes[name] # For Dirty
26
-
27
- attribute = resource_attributes[name]
28
-
29
- unless attribute.valid_with?(value: val)
24
+ define_method "_#{name}_valid_with_value?" do |val|
25
+ unless resource_attributes[name].valid_with?(value: val)
30
26
  raise ResourceError::AttributeTypeError.new(
31
- attribute: attribute,
27
+ attribute: resource_attributes[name],
32
28
  resource: self,
33
29
  value: val
34
30
  )
35
31
  end
32
+ end
36
33
 
34
+ define_method "#{name}=" do |val|
35
+ attribute = resource_attributes[name]
36
+ public_send("_#{name}_valid_with_value?", val)
37
+ val = attribute.type.cast(val) if attribute.type.cast?
38
+ public_send("#{name}_will_change!") unless val == resource_attributes[name] # For Dirty
37
39
  attribute.value = val
38
40
  end
39
41
 
@@ -47,10 +49,15 @@ module LedgerSync
47
49
  _define_attribute_methods(name)
48
50
 
49
51
  resource_attributes.add(resource_attribute)
52
+ references_many_resource_attributes << resource_attribute if resource_attribute.type.is_a?(Reference::Many)
50
53
 
51
54
  resource_attribute
52
55
  end
53
56
 
57
+ def references_many_resource_attributes
58
+ @references_many_resource_attributes ||= []
59
+ end
60
+
54
61
  def resource_attributes
55
62
  @resource_attributes ||= ResourceAttributeSet.new(resource: self)
56
63
  end
@@ -82,6 +89,11 @@ module LedgerSync
82
89
  @resource_attributes ||= Marshal.load(Marshal.dump(self.class.resource_attributes))
83
90
  end
84
91
 
92
+ def save
93
+ resoure_attributes.map(&:save)
94
+ super
95
+ end
96
+
85
97
  def serialize_attributes
86
98
  Hash[resource_attributes.map { |k, v| [k, v.value] }]
87
99
  end
@@ -7,6 +7,48 @@ module LedgerSync
7
7
  class ResourceAttribute
8
8
  class Reference
9
9
  class Many < Reference
10
+ class ManyArray
11
+ include ActiveModel::Dirty
12
+
13
+ ARRAY_METHODS_TO_OVERRIDE_WITH_DIRTY = %w[<< | []= + -].freeze
14
+
15
+ delegate :count,
16
+ :each,
17
+ :include?,
18
+ :map,
19
+ to: :value
20
+
21
+ attr_accessor :value
22
+
23
+ define_attribute_methods :value
24
+
25
+ def initialize
26
+ @value = []
27
+ end
28
+
29
+ ARRAY_METHODS_TO_OVERRIDE_WITH_DIRTY.each do |array_method|
30
+ define_method(array_method) do |val|
31
+ value_will_change!
32
+ @value = @value.send(array_method, val)
33
+ end
34
+ end
35
+
36
+ def ==(other)
37
+ return false unless other.is_a?(ManyArray)
38
+ return false unless other.sorted_fingerprints == sorted_fingerprints
39
+
40
+ true
41
+ end
42
+
43
+ def save
44
+ changes_applied
45
+ end
46
+
47
+ def sorted_fingerprints
48
+ value.map(&:fingerprint).sort
49
+ end
50
+ end
51
+
10
52
  module Mixin
11
53
  def self.included(base)
12
54
  base.extend(ClassMethods)
@@ -25,9 +67,21 @@ module LedgerSync
25
67
  super(
26
68
  name: name,
27
69
  type: Type::ReferenceMany.new(resource_class: to),
28
- value: []
70
+ value: ManyArray.new
29
71
  )
30
72
  end
73
+
74
+ def changed?
75
+ value.changed?
76
+ end
77
+
78
+ def changes
79
+ value.changes
80
+ end
81
+
82
+ def save
83
+ value.save
84
+ end
31
85
  end
32
86
  end
33
87
  end