Module: IdentityCache::QueryAPI::ClassMethods

Defined in:
lib/identity_cache/query_api.rb

Instance Method Summary collapse

Instance Method Details

#exists_with_identity_cache?(id) ⇒ Boolean

Similar to ActiveRecord::Base#exists? will return true if the id can be found in the cache or in the DB.

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)

12
13
14
15
# File 'lib/identity_cache/query_api.rb', line 12

def exists_with_identity_cache?(id)
  raise NotImplementedError, "exists_with_identity_cache? needs the primary index enabled" unless primary_cache_index_enabled
  !!fetch_by_id(id)
end

#expire_primary_key_cache_index(id) ⇒ Object

Invalidates the primary cache index for the associated record. Will not invalidate cached attributes.


117
118
119
120
121
# File 'lib/identity_cache/query_api.rb', line 117

def expire_primary_key_cache_index(id)
  return unless primary_cache_index_enabled
  id = type_for_attribute(primary_key).cast(id)
  IdentityCache.cache.delete(rails_cache_key(id))
end

#fetch(id, includes: nil) ⇒ Object

Default fetcher added to the model on inclusion, it behaves like ActiveRecord::Base.find, will raise ActiveRecord::RecordNotFound exception if id is not in the cache or the db.


45
46
47
# File 'lib/identity_cache/query_api.rb', line 45

def fetch(id, includes: nil)
  fetch_by_id(id, includes: includes) or raise(ActiveRecord::RecordNotFound, "Couldn't find #{self.name} with ID=#{id}")
end

#fetch_by_id(id, includes: nil) ⇒ Object

Default fetcher added to the model on inclusion, it behaves like ActiveRecord::Base.where(id: id).first

Raises:

  • (NotImplementedError)

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/identity_cache/query_api.rb', line 19

def fetch_by_id(id, includes: nil)
  ensure_base_model
  raise_if_scoped
  raise NotImplementedError, "fetching needs the primary index enabled" unless primary_cache_index_enabled
  id = type_for_attribute(primary_key).cast(id)
  return unless id
  record = if should_use_cache?
    require_if_necessary do
      object = nil
      coder = IdentityCache.fetch(rails_cache_key(id)){ instrumented_coder_from_record(object = resolve_cache_miss(id)) }
      object ||= instrumented_record_from_coder(coder)
      if object && object.id != id
        IdentityCache.logger.error "[IDC id mismatch] fetch_by_id_requested=#{id} fetch_by_id_got=#{object.id} for #{object.inspect[(0..100)]}"
      end
      object
    end
  else
    resolve_cache_miss(id)
  end
  prefetch_associations(includes, [record]) if record && includes
  record
end

#fetch_multi(*ids, includes: nil) ⇒ Object

Default fetcher added to the model on inclusion, if behaves like ActiveRecord::Base.find_all_by_id

Raises:

  • (NotImplementedError)

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/identity_cache/query_api.rb', line 51

def fetch_multi(*ids, includes: nil)
  ensure_base_model
  raise_if_scoped
  raise NotImplementedError, "fetching needs the primary index enabled" unless primary_cache_index_enabled
  ids.flatten!(1)
  id_type = type_for_attribute(primary_key)
  ids.map! { |id| id_type.cast(id) }.compact!
  records = if should_use_cache?
    require_if_necessary do
      cache_keys = ids.map {|id| rails_cache_key(id) }
      key_to_id_map = Hash[ cache_keys.zip(ids) ]
      key_to_record_map = {}

      coders_by_key = IdentityCache.fetch_multi(cache_keys) do |unresolved_keys|
        ids = unresolved_keys.map {|key| key_to_id_map[key] }
        records = find_batch(ids)
        key_to_record_map = records.compact.index_by{ |record| rails_cache_key(record.id) }
        records.map {|record| instrumented_coder_from_record(record) }
      end

      cache_keys.map{ |key| key_to_record_map[key] || instrumented_record_from_coder(coders_by_key[key]) }
    end
  else
    find_batch(ids)
  end
  records.compact!
  prefetch_associations(includes, records) if includes
  records
end

#instrumented_record_from_coder(coder) ⇒ Object

:nodoc:


109
110
111
112
113
114
# File 'lib/identity_cache/query_api.rb', line 109

def instrumented_record_from_coder(coder) #:nodoc:
  return nil unless coder
  ActiveSupport::Notifications.instrument('hydration.identity_cache', class: coder[:class]) do
    record_from_coder(coder)
  end
end

#prefetch_associations(associations, records) ⇒ Object


81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/identity_cache/query_api.rb', line 81

def prefetch_associations(associations, records)
  records = records.to_a
  return if records.empty?

  case associations
  when nil
    # do nothing
  when Symbol
    instrumented_prefetch_one_association(associations, records)
  when Array
    associations.each do |association|
      prefetch_associations(association, records)
    end
  when Hash
    associations.each do |association, sub_associations|
      next_level_records = instrumented_prefetch_one_association(association, records)

      if sub_associations.present?
        associated_class = reflect_on_association(association).klass
        associated_class.prefetch_associations(sub_associations, next_level_records)
      end
    end
  else
    raise TypeError, "Invalid associations class #{associations.class}"
  end
  nil
end