Module: Controllers::CloudflareCaching

Extended by:
ActiveSupport::Concern
Included in:
ApplicationController
Defined in:
app/concerns/controllers/cloudflare_caching.rb

Overview

Cloudflare CDN caching utilities for edge caching

Usage:

1. Declare which actions are edge-cached (prevents ghost guest creation):

edge_cached # all actions
edge_cached only: %i[index show] # specific actions

2. In the controller action, set cache params:

set_cloudflare_cache(time_in_secs: 1.week.to_i, tags: ['product-123'])

To bust cache on error:

reset_cloudflare_cache

Why edge_cached matters:
Edge-cached pages skip the session cookie (Set-Cookie header) so Cloudflare
can cache them. Without edge_cached, init_current_user creates a guest user
in the database, then skip_session discards the session -- orphaning the guest.
This causes "ghost guest" proliferation.

See: https://blog.cloudflare.com/cdn-cache-control/

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.edge_cached(only: nil) ⇒ Object

Declare actions as edge-cacheable. This automatically skips init_current_user
to prevent ghost guest creation on pages served from Cloudflare's CDN.

Examples:

Cache all actions in the controller

edge_cached

Cache specific actions only

edge_cached only: %i[index show]


44
45
46
47
48
49
50
51
52
# File 'app/concerns/controllers/cloudflare_caching.rb', line 44

def edge_cached(only: nil)
  if only
    skip_before_action :init_current_user, only: only
    self._edge_cached_actions = Array(only).map(&:to_s).to_set.freeze
  else
    skip_before_action :init_current_user
    self._edge_cached_actions = :all
  end
end

Instance Method Details

#edge_cached_action?Boolean

Returns true if the current action was declared via edge_cached

Returns:

  • (Boolean)


56
57
58
59
60
61
62
# File 'app/concerns/controllers/cloudflare_caching.rb', line 56

def edge_cached_action?
  config = self.class._edge_cached_actions
  return false if config.nil?
  return true if config == :all

  config.include?(action_name)
end

#reset_cloudflare_cacheObject



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'app/concerns/controllers/cloudflare_caching.rb', line 84

def reset_cloudflare_cache
  return unless response

  # Remove all Cloudflare-specific cache headers
  response.delete_header('Cloudflare-CDN-Cache-Control')
  response.delete_header('Cache-Tag')

  # Explicitly tell Cloudflare to NOT cache this response
  # This prevents edge caching of error pages even if other headers suggest caching
  response.set_header('Cloudflare-CDN-Cache-Control', 'no-store')

  # Also prevent browser caching
  expires_now

  @edge_cached = false
end

#set_cloudflare_cache(time_in_secs: 3.days.to_i, stale_time_in_secs: 1.day.to_i, public_cache_expires_in: 3.days, tags: nil) ⇒ Object

Applies headers for caching specifically for cloudflare's edge

Parameters:

  • time_in_secs (Integer) (defaults to: 3.days.to_i)

    Normal time to keep a public response in cache

  • stale_time_in_secs (Integer) (defaults to: 1.day.to_i)

    Time to keep in cache in case of server error

  • public_cache_expires_in (ActiveSupport::Duration) (defaults to: 3.days)

    Browser cache expiry

  • tags (Array<String>) (defaults to: nil)

    Cache tags for selective purging



69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'app/concerns/controllers/cloudflare_caching.rb', line 69

def set_cloudflare_cache(time_in_secs: 3.days.to_i, stale_time_in_secs: 1.day.to_i, public_cache_expires_in: 3.days, tags: nil)
  return unless response && response.ok?

  warn_if_not_edge_cached

  expires_in public_cache_expires_in, public: true
  skip_session # Necessary otherwise you will not get the benefit of this method
  cdn_cache_controls = []
  cdn_cache_controls << "max-age=#{time_in_secs}"
  cdn_cache_controls << "stale-if-error=#{stale_time_in_secs}"
  response.set_header('Cloudflare-CDN-Cache-Control', cdn_cache_controls.join(','))
  response.set_header('Cache-Tag', tags.join(',')) if tags.present?
  @edge_cached = true
end

#skip_sessionObject

For some static content, we don't need to track the session
This helps with picky CDNs throwing off the cache if a cookie is present



103
104
105
# File 'app/concerns/controllers/cloudflare_caching.rb', line 103

def skip_session
  request.session_options[:skip] = true
end