Module: IdentityCache::WithPrimaryIndex::ClassMethods

Defined in:
lib/identity_cache/with_primary_index.rb

Instance Method Summary collapse

Instance Method Details

#cache_index(*fields, unique: false) ⇒ Object

Declares a new index in the cache for the class where IdentityCache was included.

IdentityCache will add a fetch_by_field1_and_field2_and_…field and fetch_multi_by_field1_and_field2_and_…field for every index.

Example:

class Product
  include IdentityCache
  cache_index :name, :vendor
end

Will add:

Product.fetch_by_name_and_vendor
Product.fetch_multi_by_name_and_vendor

Parameters

fields Array of symbols or strings representing the fields in the index

Options

  • unique: if the index would only have unique values. Default is false



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/identity_cache/with_primary_index.rb', line 60

def cache_index(*fields, unique: false)
  attribute_proc = -> { primary_key }
  cache_attribute_by_alias(attribute_proc, alias_name: :id, by: fields, unique: unique)

  field_list = fields.join("_and_")
  arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(",")

  if unique
    instance_eval(<<-CODE, __FILE__, __LINE__ + 1)
      def fetch_by_#{field_list}(#{arg_list}, includes: nil)
        id = fetch_id_by_#{field_list}(#{arg_list})
        id && fetch_by_id(id, includes: includes)
      end

      # exception throwing variant
      def fetch_by_#{field_list}!(#{arg_list}, includes: nil)
        fetch_by_#{field_list}(#{arg_list}, includes: includes) or raise IdentityCache::RecordNotFound
      end
    CODE
  else
    instance_eval(<<-CODE, __FILE__, __LINE__ + 1)
      def fetch_by_#{field_list}(#{arg_list}, includes: nil)
        ids = fetch_id_by_#{field_list}(#{arg_list})
        ids.empty? ? ids : fetch_multi(ids, includes: includes)
      end
    CODE
  end

  instance_eval(<<-CODE, __FILE__, __LINE__ + 1)
    def fetch_multi_by_#{field_list}(index_values, includes: nil)
      ids = fetch_multi_id_by_#{field_list}(index_values).values.flatten(1)
      return ids if ids.empty?
      fetch_multi(ids, includes: includes)
    end
  CODE
end

#cached_primary_indexObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



27
28
29
# File 'lib/identity_cache/with_primary_index.rb', line 27

def cached_primary_index
  @cached_primary_index ||= Cached::PrimaryIndex.new(self)
end

#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)


99
100
101
# File 'lib/identity_cache/with_primary_index.rb', line 99

def exists_with_identity_cache?(id)
  !!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.



162
163
164
# File 'lib/identity_cache/with_primary_index.rb', line 162

def expire_primary_key_cache_index(id)
  cached_primary_index.expire(id)
end

#fetch(id, **options) ⇒ self

Fetch the record by its primary key from the cache or read from the database and fill the cache on a cache miss. This behaves like ‘readonly.find(id)` being called on the model.

Parameters:

  • id

    Primary key value for the record to fetch.

  • includes (Hash|Array|Symbol)

    Cached associations to prefetch from the cache on the returned record

  • fill_lock_duration (Float)

    If provided, take a fill lock around cache fills and wait for this duration for cache to be filled when reading a lock provided by another client. Defaults to not setting the fill lock and trying to fill the cache from the database regardless of the presence of another client’s fill lock. Set this to just above the typical amount of time it takes to do a cache fill.

  • lock_wait_tries (Integer)

    Only applicable if fill_lock_duration is provided, in which case it specifies the number of times to do a lock wait. After the first lock wait it will try to take the lock, so will only do following lock waits due to another client taking the lock first. If another lock wait would be needed after reaching the limit, then a ‘LockWaitTimeout` exception is raised. Default is 2. Use this to control the maximum total lock wait duration (`lock_wait_tries * fill_lock_duration`).

Returns:

  • (self)

    An instance of this model for the record with the specified id

Raises:

  • (ActiveRecord::RecordNotFound)

    if the record isn’t found

  • (LockWaitTimeout)

    Timeout after waiting ‘lock_wait_tries * fill_lock_duration` seconds for `lock_wait_tries` other clients to fill the cache.



142
143
144
145
146
# File 'lib/identity_cache/with_primary_index.rb', line 142

def fetch(id, **options)
  fetch_by_id(id, **options) || raise(
    IdentityCache::RecordNotFound, "Couldn't find #{name} with ID=#{id}"
  )
end

#fetch_by_id(id, includes: nil, **cache_fetcher_options) ⇒ self|nil

Fetch the record by its primary key from the cache or read from the database and fill the cache on a cache miss. This behaves like ‘where(id: id).readonly.first` being called on the model.

Parameters:

  • id

    Primary key value for the record to fetch.

  • includes (Hash|Array|Symbol) (defaults to: nil)

    Cached associations to prefetch from the cache on the returned record

  • fill_lock_duration (Float)

    If provided, take a fill lock around cache fills and wait for this duration for cache to be filled when reading a lock provided by another client. Defaults to not setting the fill lock and trying to fill the cache from the database regardless of the presence of another client’s fill lock. Set this to just above the typical amount of time it takes to do a cache fill.

  • lock_wait_tries (Integer)

    Only applicable if fill_lock_duration is provided, in which case it specifies the number of times to do a lock wait. After the first lock wait it will try to take the lock, so will only do following lock waits due to another client taking the lock first. If another lock wait would be needed after reaching the limit, then a ‘LockWaitTimeout` exception is raised. Default is 2. Use this to control the maximum total lock wait duration (`lock_wait_tries * fill_lock_duration`).

Returns:

  • (self|nil)

    An instance of this model for the record with the specified id or ‘nil` if no record with this `id` exists in the database

Raises:

  • (LockWaitTimeout)

    Timeout after waiting ‘lock_wait_tries * fill_lock_duration` seconds for `lock_wait_tries` other clients to fill the cache.



126
127
128
129
130
131
132
# File 'lib/identity_cache/with_primary_index.rb', line 126

def fetch_by_id(id, includes: nil, **cache_fetcher_options)
  ensure_base_model
  raise_if_scoped
  record = cached_primary_index.fetch(id, cache_fetcher_options)
  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



150
151
152
153
154
155
156
157
158
159
# File 'lib/identity_cache/with_primary_index.rb', line 150

def fetch_multi(*ids, includes: nil)
  ensure_base_model
  raise_if_scoped
  ids.flatten!(1)
  return [] if ids.none?

  records = cached_primary_index.fetch_multi(ids)
  prefetch_associations(includes, records) if includes
  records
end

#primary_cache_index_enabledObject



31
32
33
# File 'lib/identity_cache/with_primary_index.rb', line 31

def primary_cache_index_enabled
  true
end