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

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

Class Method Summary collapse

Class Method Details

.base_cache_key(type, prerender: nil) ⇒ Object

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



181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/react_on_rails_pro/cache.rb', line 181

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

  # TODO: Move comment over to test
  # We only care about the bundle hash if prerendering because we're not caching anything
  # that would be generated by the bundle.
  keys.push(ReactOnRailsPro::Utils.bundle_hash) if prerender
  keys
end

.cache_supports_expires_at?Boolean

Returns:

  • (Boolean)


108
109
110
# File 'lib/react_on_rails_pro/cache.rb', line 108

def cache_supports_expires_at?
  ActiveSupport.gem_version >= ACTIVE_SUPPORT_EXPIRES_AT_VERSION
end

.cache_write_expired?(cache_options) ⇒ Boolean

Returns:

  • (Boolean)


99
100
101
102
103
104
105
106
# File 'lib/react_on_rails_pro/cache.rb', line 99

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) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/react_on_rails_pro/cache.rb', line 81

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_keyObject



195
196
197
198
199
200
201
202
203
204
205
# File 'lib/react_on_rails_pro/cache.rb', line 195

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) ⇒ Object

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



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/react_on_rails_pro/cache.rb', line 27

def fetch_react_component(component_name, options)
  if 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])
    if cache_write_expired?(options[:cache_options])
      result = yield
      if result.is_a?(Hash)
        result[:RORP_CACHE_KEY] = cache_key
        result[:RORP_CACHE_HIT] = false
      end
      return result
    end

    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
    # Pass back the cache key in the results only if the result is a Hash
    if result.is_a?(Hash)
      result[:RORP_CACHE_KEY] = cache_key
      result[:RORP_CACHE_HIT] = cache_hit
    end
    result
  else
    yield
  end
end

.normalize_tags(tags) ⇒ Object



75
76
77
78
79
# File 'lib/react_on_rails_pro/cache.rb', line 75

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) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/react_on_rails_pro/cache.rb', line 207

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) ⇒ Object



69
70
71
72
73
# File 'lib/react_on_rails_pro/cache.rb', line 69

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) ⇒ Object

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



65
66
67
# File 'lib/react_on_rails_pro/cache.rb', line 65

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

.revalidate_tags(*tags) ⇒ Object

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.



128
129
130
131
132
133
# File 'lib/react_on_rails_pro/cache.rb', line 128

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



112
113
114
115
116
# File 'lib/react_on_rails_pro/cache.rb', line 112

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)


118
119
120
# File 'lib/react_on_rails_pro/cache.rb', line 118

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

Returns:

  • (Boolean)


168
169
170
171
172
173
174
175
176
# File 'lib/react_on_rails_pro/cache.rb', line 168

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