ledger_sync 1.1.1 → 1.1.2

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