# frozen_string_literal: true require_relative 'ledger_serializer_type/value_type' Gem.find_files('ledger_sync/adaptors/ledger_serializer_type/**/*.rb').each { |path| require path } module LedgerSync module Adaptors class LedgerSerializerAttribute attr_reader :block, :deserialize, :id, :ledger_attribute, :resource_attribute, :resource_class, :serialize, :serializer, :type def initialize( block: nil, deserialize: true, id: false, ledger_attribute:, resource_attribute: nil, resource_class: nil, serialize: true, serializer: nil, type: LedgerSerializerType::ValueType ) raise 'block and resource_attribute cannot both be present' unless block.nil? || resource_attribute.nil? @block = block @deserialize = deserialize @id = id @ledger_attribute = ledger_attribute.to_s @resource_attribute = resource_attribute.to_s @resource_class = resource_class @serialize = serialize @serializer = serializer @type = type.new end def block_value_for(resource:) block.call(resource) end def deserialize? deserialize end # Make nested/dot calls (e.g. `vendor.ledger_id`) def dot_value_for(resource:) resource_attribute_dot_parts.inject(resource) { |r, dot_method| r&.send(dot_method) } end def ledger_attribute_dot_parts @ledger_attribute_dot_parts ||= ledger_attribute.split('.') end def ledger_attribute_hash_for(resource:) ledger_attribute_hash_with(value: value_from_local(resource: resource)) end # Create nested hash for ledger # # when ledger_attribute = 'Foo.Bar.Baz', # ledger_attribute_hash_with(value: 123) # Result: # { # 'Foo' => { # 'Bar' => { # 'Baz' => 123 # } # } # } def ledger_attribute_hash_with(value:) ledger_attribute_dot_parts.reverse.inject(value) { |a, n| { n => a } } end def reference? references_many? || references_one? end def references_many? type.is_a?(LedgerSerializerType::ReferencesManyType) end def references_one? type.is_a?(LedgerSerializerType::ReferencesOneType) end def resource_attribute? resource_attribute.present? end def resource_attribute_dot_parts @resource_attribute_dot_parts ||= resource_attribute.split('.') end def serialize? serialize end def value_from_local(resource:) value = if block.nil? dot_value_for(resource: resource) else block_value_for(resource: resource) end if reference? type.convert_from_local( serializer: serializer, value: value ) else type.convert_from_local( value: value ) end end def value_from_ledger(hash:, resource:) value = hash.dig(*ledger_attribute_dot_parts) value = if reference? type.convert_from_ledger( resource_class: resource_class, serializer: serializer, value: value ) else type.convert_from_ledger( value: value ) end return value if resource_attribute_dot_parts.count <= 1 nested_resource = resource.send(resource_attribute_dot_parts.first) nested_resource ||= resource_attribute_class(resource: resource).new build_resource_value_from_nested_attributes( nested_resource, value, resource_attribute_dot_parts[1..-1] ) end private def build_resource_value_from_nested_attributes(resource, value, attribute_parts) resource = resource.dup first_attribute, *remaining_attributes = attribute_parts if remaining_attributes.count.zero? resource.public_send("#{first_attribute}=", value) else resource.public_send( "#{first_attribute}=", build_resource_value_from_nested_attributes( resource.public_send(first_attribute) || resource.class.resource_attributes[first_attribute.to_sym].type.resource_class.new, value, remaining_attributes ) ) end resource end def resource_attribute_class(resource:) @resource_attribute_type ||= {} @resource_attribute_type[resource] ||= resource.class.resource_attributes[resource_attribute_dot_parts.first.to_sym].type.resource_class end end end end