hooopo/second_level_cache

View on GitHub
lib/second_level_cache/active_record/preloader/legacy.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

module SecondLevelCache
  module ActiveRecord
    module Associations
      module Preloader
        module Association
          # For < Rails 7
          module Legacy
            RAILS6 = ::ActiveRecord.version >= ::Gem::Version.new("6")

            def records_for(ids, &block)
              return super unless klass.second_level_cache_enabled?
              return super unless reflection.is_a?(::ActiveRecord::Reflection::BelongsToReflection)
              return super if klass.default_scopes.present? || reflection.scope
              return super if association_key_name.to_s != klass.primary_key

              map_cache_keys = ids.map { |id| klass.second_level_cache_key(id) }
              records_from_cache = ::SecondLevelCache.cache_store.read_multi(*map_cache_keys)

              record_marshals = if RAILS6
                RecordMarshal.load_multi(records_from_cache.values) do |record|
                  # This block is copy from:
                  # https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/associations/preloader/association.rb#L101
                  owner = owners_by_key[convert_key(record[association_key_name])].first
                  association = owner.association(reflection.name)
                  association.set_inverse_instance(record)
                end
              else
                RecordMarshal.load_multi(records_from_cache.values, &block)
              end

              # NOTICE
              # Rails.cache.read_multi return hash that has keys only hitted.
              # eg. Rails.cache.read_multi(1,2,3) => {2 => hit_value, 3 => hit_value}
              hitted_ids = record_marshals.map { |record| record.read_attribute(association_key_name).to_s }
              missed_ids = ids.map(&:to_s) - hitted_ids
              ActiveSupport::Notifications.instrument("preload.second_level_cache", key: association_key_name, hit: hitted_ids, miss: missed_ids)
              return SecondLevelCache::RecordRelation.new(record_marshals) if missed_ids.empty?

              records_from_db = super(missed_ids, &block)
              records_from_db.map { |r| write_cache(r) }

              SecondLevelCache::RecordRelation.new(records_from_db + record_marshals)
            end

            private

            def write_cache(record)
              record.write_second_level_cache
            end
          end
        end
      end
    end
  end
end