Module: ViewComponent::ExperimentallyCacheable

Extended by:
ActiveSupport::Concern
Defined in:
lib/view_component/experimentally_cacheable.rb

Overview

Experimental caching support for ViewComponents.

This API is experimental. It may change or be removed in a non-major release. Please share feedback in https://github.com/ViewComponent/view_component/issues/234.

Including this module does two things:

  1. Registers the component with Rails' template digest tree, so a <% cache %> block wrapping the component in a view is invalidated when the component's template, Ruby class, sidecar files, or child components change.
  2. Enables the cache_on macro, which caches the component's own rendered output.
class MessageComponent < ViewComponent::Base
  include ViewComponent::ExperimentallyCacheable

  cache_on :message

  def initialize(message:)
    @message = message
  end
end

Constant Summary collapse

NIL_CACHE_VALUE =

Stands in for nil in the cache key. Without it, expand_cache_key renders nil and "" identically, so two components differing only in that respect would share an entry.

:__vc_nil

Instance Method Summary collapse

Instance Method Details

#cache_key(view_context = nil) ⇒ String

The cache key for this rendering of the component.

Combines the component's identity, its source digest, the requested format and variant, the current locale, and the values declared with cache_on. Override for full control.

Parameters:

  • view_context (ActionView::Base) (defaults to: nil)

Returns:

  • (String)


204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/view_component/experimentally_cacheable.rb', line 204

def cache_key(view_context = nil)
  lookup_context = view_context&.lookup_context
  format = __vc_cache_format(lookup_context)

  parts = [
    "view_component",
    self.class.virtual_path,
    self.class.cache_digest(finder: lookup_context, format: format),
    # Included in its own right, not just as a digest input: components that
    # render every format from one template have the same digest for each.
    format,
    __vc_cache_variant(lookup_context),
    I18n.locale,
    *__vc_cache_on_values
  ]

  # Positions are significant, so nils are substituted rather than removed.
  # Compacting the array would let a nil in one position collapse into a nil
  # in another.
  ActiveSupport::Cache.expand_cache_key(
    parts.map { |part| part.nil? ? NIL_CACHE_VALUE : part }
  )
end

#render_in(view_context, &block) ⇒ Object

Renders the component, reading from and writing to the Rails cache when cache_on has been declared.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/view_component/experimentally_cacheable.rb', line 169

def render_in(view_context, **, &block)
  return super unless self.class.__vc_caches_output?

  # Content provided by the caller isn't part of the cache key, so caching
  # it would serve one caller's content to another. Raised whether or not
  # caching is currently enabled, so the conflict surfaces in development
  # and test rather than only in production.
  if block || __vc_content_set_by_with_content_defined? || __vc_slots_set_by_caller?
    raise ContentPassedToCachedComponentError.new(self.class.name)
  end

  return super unless __vc_cache_enabled?(view_context)

  store = Rails.cache
  key = cache_key(view_context)

  if (cached = store.read(key))
    # Safe to mark as HTML-safe: the cached string was produced by this same
    # rendering pipeline, which escapes output before it's written.
    return cached.html_safe # rubocop:disable Rails/OutputSafety
  end

  super.tap do |output|
    store.write(key, output.to_s)
  end
end