Class: ReactOnRailsPro::Cache

Inherits:
Object
  • Object
show all
Defined in:
lib/react_on_rails_pro/cache.rb,
lib/react_on_rails_pro/cache/tag_index.rb,
lib/react_on_rails_pro/cache/revalidates.rb,
sig/react_on_rails_pro/cache.rbs

Defined Under Namespace

Modules: Revalidates Classes: TagIndex

Constant Summary collapse

ACTIVE_SUPPORT_EXPIRES_AT_VERSION =
Gem::Version.new("7.0.0")
EXPIRED_CACHE_WRITE_TTL =

seconds; minimum positive TTL for race-expired writes

1
RSC_BUNDLE_MISSING_CACHE_KEY =
"rsc-bundle-missing"

Class Method Summary collapse

Class Method Details

.base_cache_key(type, prerender: nil) ⇒ Array[String]

Cache keys by React on Rails Pro should build upon this base Provide prerender: true in order to include bundle hashes in the list of keys. Bundle hashes are necessary so that any changes to rendered output fault the cache.

Parameters:

  • type (String)
  • prerender: (Boolean, nil) (defaults to: nil)

Returns:

  • (Array[String])


187
188
189
190
191
192
193
194
195
196
197
# File 'lib/react_on_rails_pro/cache.rb', line 187

def base_cache_key(type, prerender: nil)
  keys = [type, ReactOnRails::VERSION, ReactOnRailsPro::VERSION]

  # We only care about bundle hashes if prerendering because non-prerendered
  # output is not generated by the server or RSC bundle.
  if prerender
    keys.push(ReactOnRailsPro::Utils.bundle_hash)
    keys.push(rsc_bundle_cache_key) if ReactOnRailsPro.configuration.enable_rsc_support
  end
  keys
end

.cache_supports_expires_at?Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/react_on_rails_pro/cache.rb', line 100

def cache_supports_expires_at?
  ActiveSupport.gem_version >= ACTIVE_SUPPORT_EXPIRES_AT_VERSION
end

.cache_write_expired?(cache_options) ⇒ Boolean

Parameters:

  • cache_options (Hash[Symbol, untyped], nil)

Returns:

  • (Boolean)


91
92
93
94
95
96
97
98
# File 'lib/react_on_rails_pro/cache.rb', line 91

def cache_write_expired?(cache_options)
  return false unless cache_options&.key?(:expires_at)

  expires_at = cache_options[:expires_at]
  return false if expires_at && unsupported_expires_at_with_explicit_expires_in?(cache_options)

  expires_at && expires_at.to_time.to_f <= Time.now.to_f
end

.cache_write_options(cache_options) ⇒ Hash[Symbol, untyped]?

Parameters:

  • cache_options (Hash[Symbol, untyped], nil)

Returns:

  • (Hash[Symbol, untyped], nil)


73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/react_on_rails_pro/cache.rb', line 73

def cache_write_options(cache_options)
  return cache_options unless cache_options&.key?(:expires_at)

  expires_at = cache_options[:expires_at]
  return cache_options unless expires_at

  return cache_options.except(:expires_at) if unsupported_expires_at_with_explicit_expires_in?(cache_options)

  expires_in = expires_at.to_time.to_f - Time.now.to_f
  return cache_options.merge(expires_in: EXPIRED_CACHE_WRITE_TTL).except(:expires_at) if expires_in <= 0

  return supported_expires_at_write_options(cache_options) if cache_supports_expires_at?

  return cache_options.except(:expires_at) unless cache_options[:expires_in].nil?

  cache_options.merge(expires_in:).except(:expires_at)
end

.dependencies_cache_keyString?

Returns:

  • (String, nil)


199
200
201
202
203
204
205
206
207
208
209
# File 'lib/react_on_rails_pro/cache.rb', line 199

def dependencies_cache_key
  # https://github.com/shakacode/react_on_rails_pro/issues/32
  # https://github.com/shakacode/react_on_rails/issues/39#issuecomment-143472325
  return @dependency_checksum if @dependency_checksum.present? && !Rails.env.development?
  return nil unless ReactOnRailsPro.configuration.dependency_globs.present?

  @dependency_checksum =
    ReactOnRailsPro::Utils.digest_of_globs(
      ReactOnRailsPro.configuration.dependency_globs
    ).hexdigest
end

.fetch_react_component(component_name, options = nil, callback_options = nil) ⇒ Object

options can include :compress, :expires_in, :race_condition_ttl and other options



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/react_on_rails_pro/cache.rb', line 28

def fetch_react_component(component_name, options = nil, callback_options = nil)
  options ||= {}
  on_cache_hit = cache_hit_callback(callback_options)

  return yield unless use_cache?(options)

  cache_key = react_component_cache_key(component_name, options)
  Rails.logger.debug { "React on Rails Pro cache_key is #{cache_key.inspect}" }
  cache_options = cache_write_options(options[:cache_options])
  return (yield, cache_key, false) if cache_write_expired?(options[:cache_options])

  cache_hit = true
  normalized_cache_tags = []
  result = Rails.cache.fetch(cache_key, cache_options) do
    cache_hit = false
    normalized_cache_tags = normalize_tags(options[:cache_tags])
    yield
  end
  register_normalized_tags(normalized_cache_tags, cache_key, cache_options) unless cache_hit
  on_cache_hit&.call(component_name, options) if cache_hit

  (result, cache_key, cache_hit)
end

.normalize_tags(tags) ⇒ Array[String]

Parameters:

  • tags (Object)

Returns:

  • (Array[String])


67
68
69
70
71
# File 'lib/react_on_rails_pro/cache.rb', line 67

def normalize_tags(tags)
  return [] if tags.nil? || (tags.is_a?(Array) && tags.empty?)

  TagIndex.normalize_tags(tags)
end

.react_component_cache_key(component_name, options) ⇒ Array[untyped]

Parameters:

  • component_name (String)
  • options (Hash[Symbol, untyped])

Returns:

  • (Array[untyped])


211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/react_on_rails_pro/cache.rb', line 211

def react_component_cache_key(component_name, options)
  cache_key_option = options[:cache_key]
  cache_key_value = if cache_key_option.respond_to?(:call)
                      cache_key_option.call
                    else
                      cache_key_option
                    end

  # NOTE: Rails seems to do this automatically: ActiveSupport::Cache.expand_cache_key(keys)
  [
    *base_cache_key("ror_component", prerender: options[:prerender]),
    dependencies_cache_key,
    component_name,
    cache_key_value
  ].compact
end

.register_normalized_tags(normalized_tags, cache_key, cache_options) ⇒ void

This method returns an undefined value.

Parameters:

  • normalized_tags (Array[String])
  • cache_key (Object)
  • cache_options (Hash[Symbol, untyped], nil)


61
62
63
64
65
# File 'lib/react_on_rails_pro/cache.rb', line 61

def register_normalized_tags(normalized_tags, cache_key, cache_options)
  return if normalized_tags.blank?

  TagIndex.register_normalized(normalized_tags, cache_key, cache_options || {})
end

.register_tags(tags, cache_key, cache_options) ⇒ void

This method returns an undefined value.

Registers cache tags for an already-written cache entry so a later ReactOnRailsPro.revalidate_tag can delete it. Call after a successful cache write (never on a cache hit). No-op when tags are nil or []. See ReactOnRailsPro::Cache::TagIndex for the v1 index semantics (best-effort, lossy-OK; correctness bounded by :expires_in).

Parameters:

  • tags (Object)
  • cache_key (Object)
  • cache_options (Hash[Symbol, untyped], nil)


57
58
59
# File 'lib/react_on_rails_pro/cache.rb', line 57

def register_tags(tags, cache_key, cache_options)
  register_normalized_tags(normalize_tags(tags), cache_key, cache_options)
end

.revalidate_tags(*tags) ⇒ Integer

Deletes every cached component entry registered under the given tags and clears the tag index entries. Tags accept the same forms as the cache_tags: helper option. Blank tags (nil/empty/whitespace) are silently ignored at the revalidation boundary (unlike registration, which raises on blank tags). Missing/evicted index entries are a no-op. Returns the number of cache entries deleted.

Parameters:

  • tags (Object)

Returns:

  • (Integer)


120
121
122
123
124
125
# File 'lib/react_on_rails_pro/cache.rb', line 120

def revalidate_tags(*tags)
  meaningful_tags = meaningful_revalidation_tags(tags)
  return 0 if meaningful_tags.empty?

  TagIndex.revalidate(*meaningful_tags)
end

.supported_expires_at_write_options(cache_options) ⇒ Object



104
105
106
107
108
# File 'lib/react_on_rails_pro/cache.rb', line 104

def supported_expires_at_write_options(cache_options)
  return cache_options.except(:expires_in) if cache_options.key?(:expires_in)

  cache_options
end

.unsupported_expires_at_with_explicit_expires_in?(cache_options) ⇒ Boolean

Returns:

  • (Boolean)


110
111
112
# File 'lib/react_on_rails_pro/cache.rb', line 110

def unsupported_expires_at_with_explicit_expires_in?(cache_options)
  !cache_supports_expires_at? && cache_options.key?(:expires_in) && !cache_options[:expires_in].nil?
end

.use_cache?(options) ⇒ Boolean

Parameters:

  • options (Hash[Symbol, untyped])

Returns:

  • (Boolean)


174
175
176
177
178
179
180
181
182
# File 'lib/react_on_rails_pro/cache.rb', line 174

def use_cache?(options)
  if options.key?(:if)
    options[:if]
  elsif options.key?(:unless)
    !options[:unless]
  else
    true
  end
end