Class: ViewModel::ActiveRecord::Cache

Inherits:
Object
  • Object
show all
Defined in:
lib/view_model/active_record/cache.rb

Overview

Cache for ViewModels that wrap ActiveRecord models.

Defined Under Namespace

Modules: CacheableView Classes: CacheWorker, UncacheableViewModelError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(viewmodel_class, cache_group: nil) ⇒ Cache

If cache_group: is specified, it must be a group of a single key: ‘:id`



15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/view_model/active_record/cache.rb', line 15

def initialize(viewmodel_class, cache_group: nil)
  @viewmodel_class = viewmodel_class

  @cache_group = cache_group || create_default_cache_group
  @migrated_cache_group = @cache_group.register_child_group(:migrated, :version)

  # /viewname/:id/viewname-currentversion
  @cache = @cache_group.register_cache(cache_name)

  # /viewname/:id/migrated/:oldversion/viewname-currentversion
  @migrated_cache = @migrated_cache_group.register_cache(cache_name)
end

Instance Attribute Details

#viewmodel_classObject (readonly)

Returns the value of attribute viewmodel_class.



12
13
14
# File 'lib/view_model/active_record/cache.rb', line 12

def viewmodel_class
  @viewmodel_class
end

Instance Method Details

#cache_for(migration_version) ⇒ Object



280
281
282
283
284
285
286
# File 'lib/view_model/active_record/cache.rb', line 280

def cache_for(migration_version)
  if migration_version
    @migrated_cache
  else
    @cache
  end
end

#cache_versionObject



312
313
314
315
316
317
318
# File 'lib/view_model/active_record/cache.rb', line 312

def cache_version
  @cache_version ||=
    begin
      versions = @viewmodel_class.deep_schema_version(include_referenced: false)
      ViewModel.schema_hash(versions)
    end
end

#clearObject



34
35
36
# File 'lib/view_model/active_record/cache.rb', line 34

def clear
  @cache_group.invalidate_cache_group
end

#delete(*ids) ⇒ Object



28
29
30
31
32
# File 'lib/view_model/active_record/cache.rb', line 28

def delete(*ids)
  ids.each do |id|
    @cache_group.delete_all(@cache.key.new(id))
  end
end

#fetch(ids, initial_viewmodels: nil, migration_versions: {}, locked: false, serialize_context: @viewmodel_class.new_serialize_context) ⇒ Object



43
44
45
46
47
48
49
50
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/view_model/active_record/cache.rb', line 43

def fetch(ids, initial_viewmodels: nil, migration_versions: {}, locked: false, serialize_context: @viewmodel_class.new_serialize_context)
  data_serializations = Array.new(ids.size)
  worker = CacheWorker.new(migration_versions: migration_versions, serialize_context: serialize_context)

  # If initial root viewmodels were provided, visit them to ensure that they
  # are visible. Other than this, no traversal callbacks are performed, as a
  # view may be resolved from the cache without ever loading its viewmodel.
  # Note that if unlocked, these views will be reloaded as part of obtaining a
  # share lock. If the visibility of this viewmodel can change due to edits,
  # it is necessary to obtain a lock before calling `fetch`.
  initial_viewmodels&.each do |v|
    serialize_context.run_callback(ViewModel::Callbacks::Hook::BeforeVisit, v)
    serialize_context.run_callback(ViewModel::Callbacks::Hook::AfterVisit, v)
  end

  # Collect input array positions for each id, allowing duplicates
  positions = ids.each_with_index.with_object({}) do |(id, i), h|
    (h[id] ||= []) << i
  end

  # Fetch duplicates only once
  ids = positions.keys

  # Load existing serializations from the cache
  cached_serializations = worker.load_from_cache(self, ids)
  cached_serializations.each do |id, data|
    positions[id].each do |idx|
      data_serializations[idx] = data
    end
  end

  # Resolve and serialize missing views
  missing_ids = ids.to_set.subtract(cached_serializations.keys)

  # If initial viewmodels have been locked, we can serialize them for cache
  #  misses.
  available_viewmodels =
    if locked
      initial_viewmodels&.each_with_object({}) do |vm, h|
        h[vm.id] = vm if missing_ids.include?(vm.id)
      end
    end

  @viewmodel_class.transaction do
    # Load remaining views and serialize
    viewmodels = worker.find_and_preload_viewmodels(@viewmodel_class, missing_ids.to_a,
                                                    available_viewmodels: available_viewmodels)

    loaded_serializations = worker.serialize_and_cache(viewmodels)
    loaded_serializations.each do |id, data|
      positions[id].each do |idx|
        data_serializations[idx] = data
      end
    end

    # Resolve references
    worker.resolve_references!

    return data_serializations, worker.resolved_references
  end
end

#fetch_by_viewmodel(viewmodels, migration_versions: {}, locked: false, serialize_context: @viewmodel_class.new_serialize_context) ⇒ Object



38
39
40
41
# File 'lib/view_model/active_record/cache.rb', line 38

def fetch_by_viewmodel(viewmodels, migration_versions: {}, locked: false, serialize_context: @viewmodel_class.new_serialize_context)
  ids = viewmodels.map(&:id)
  fetch(ids, initial_viewmodels: viewmodels, migration_versions: migration_versions, locked: locked, serialize_context: serialize_context)
end

#id_for(key) ⇒ Object



296
297
298
# File 'lib/view_model/active_record/cache.rb', line 296

def id_for(key)
  key[:id]
end

#key_for(id, migration_version) ⇒ Object



288
289
290
291
292
293
294
# File 'lib/view_model/active_record/cache.rb', line 288

def key_for(id, migration_version)
  if migration_version
    @migrated_cache.key.new(id, migration_version)
  else
    @cache.key.new(id)
  end
end

#load(ids, migration_version, serialize_context:) ⇒ Object



306
307
308
309
310
# File 'lib/view_model/active_record/cache.rb', line 306

def load(ids, migration_version, serialize_context:)
  keys = ids.map { |id| key_for(id, migration_version) }
  results = cache_for(migration_version).read_multi(keys)
  results.transform_keys! { |key| id_for(key) }
end

#migrated_cache_version(migration_versions) ⇒ Object



320
321
322
323
324
325
326
327
328
329
330
# File 'lib/view_model/active_record/cache.rb', line 320

def migrated_cache_version(migration_versions)
  versions = ViewModel::Migrator.migrated_deep_schema_version(viewmodel_class, migration_versions, include_referenced: false)
  version_hash = ViewModel.schema_hash(versions)

  if version_hash == cache_version
    # no migrations affect this view
    nil
  else
    version_hash
  end
end

#store(id, migration_version, data_serialization, ref_cache, serialize_context:) ⇒ Object

Save the provided serialization and reference data in the cache



301
302
303
304
# File 'lib/view_model/active_record/cache.rb', line 301

def store(id, migration_version, data_serialization, ref_cache, serialize_context:)
  key = key_for(id, migration_version)
  cache_for(migration_version).write(key, { data: data_serialization, ref_cache: ref_cache })
end