# frozen_string_literal: true module IdentityCache module Encoder DEHYDRATE_EVENT = "dehydration.identity_cache" HYDRATE_EVENT = "hydration.identity_cache" class << self def encode(record) return unless record ActiveSupport::Notifications.instrument(DEHYDRATE_EVENT, class: record.class.name) do coder_from_record(record, record.class) end end def decode(coder, klass) return unless coder ActiveSupport::Notifications.instrument(HYDRATE_EVENT, class: klass.name) do record_from_coder(coder, klass) end end private def coder_from_record(record, klass) return unless record coder = {} coder[:attributes] = record.attributes_before_type_cast.dup recursively_embedded_associations = klass.send(:recursively_embedded_associations) id_embedded_has_manys = klass.cached_has_manys.select { |_, association| association.embedded_by_reference? } id_embedded_has_ones = klass.cached_has_ones.select { |_, association| association.embedded_by_reference? } if recursively_embedded_associations.present? coder[:associations] = recursively_embedded_associations.each_with_object({}) do |(name, association), hash| hash[name] = IdentityCache.map_cached_nil_for(embedded_coder(record, name, association)) end end if id_embedded_has_manys.present? coder[:association_ids] = id_embedded_has_manys.each_with_object({}) do |(name, association), hash| hash[name] = record.instance_variable_get(association.ids_variable_name) end end if id_embedded_has_ones.present? coder[:association_id] = id_embedded_has_ones.each_with_object({}) do |(name, association), hash| hash[name] = record.instance_variable_get(association.id_variable_name) end end coder end def embedded_coder(record, _association, cached_association) embedded_record_or_records = record.public_send(cached_association.cached_accessor_name) if embedded_record_or_records.respond_to?(:to_ary) embedded_record_or_records.map do |embedded_record| coder_from_record(embedded_record, embedded_record.class) end else coder_from_record(embedded_record_or_records, embedded_record_or_records.class) end end def record_from_coder(coder, klass) # :nodoc: record = klass.instantiate(coder[:attributes].dup) if coder.key?(:associations) coder[:associations].each do |name, value| record.instance_variable_set(klass.cached_association(name).dehydrated_variable_name, value) end end if coder.key?(:association_ids) coder[:association_ids].each do |name, ids| record.instance_variable_set(klass.cached_has_manys.fetch(name).ids_variable_name, ids) end end if coder.key?(:association_id) coder[:association_id].each do |name, id| record.instance_variable_set(klass.cached_has_ones.fetch(name).id_variable_name, id) end end record.readonly! if IdentityCache.fetch_read_only_records record end end end private_constant :Encoder end