Module: ResponseCacheConcern

Overview

typed: ignore frozen_string_literal: true

Instance Method Summary collapse

Instance Method Details

#my_response_cache(ck, format, options = {}, &block) ⇒ Object

this method is inspired by github.com/Shopify/response_bank gem but with more functionality, we can cache the response in redis and also in CDN (e.g. CloudFlare)

if caching is enabled and :public argument is not set, then the cache will be private and stored in redis but if :public is set to true, then the cache will be public and stored in CDN (e.g. CloudFlare) or browser

# How to use:

## Example 1: cache the response in redis with JSON format

cache_key = ['menus', action_name, current_user.id, params[:locale]
my_response_cache(cache_key, :json) do
  { success: true, message: '', data: restaurant.restaurant_packages }
end

## Example 2: cache the response in redis with HTML format

cache_key = ['menus', action_name, current_user.id, params[:locale]
my_response_cache(cache_key, :html) do
  render_to_string
end

## Example 3: cache the response in CDN with JSON format

cache_key = ['menus', action_name, params[:locale]
my_response_cache(cache_key, :json, public: true) do
  { success: true, message: '', data: restaurant.restaurant_packages }
end

## Example 4: cache the response in CDN with HTML format

cache_key = ['menus', action_name, params[:locale]
my_response_cache(cache_key, :html, public: true) do
  render_to_string
end

How to clear cache (example):

key = '*api_v5_cache:*'
$redis_lru.with do |conn|
  # find any key that match "store_page_list"
  conn.keys(key).each do |k|
    if k.include? "store_page_list"
      conn.del(k)
      puts "Deleted key: #{k}"
    end
  end
end


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
104
105
106
107
108
109
110
# File 'app/controllers/concerns/response_cache_concern.rb', line 64

def my_response_cache(ck, format, options = {}, &block)
  default_option = !(params[:__cache] == 'false')
  is_cache_enabled = options.fetch(:enabled, default_option)
  expires_in = options.fetch(:expires_in, 1.day)
  cache_by_public = options.fetch(:public, false)
  public_expires_in = options.fetch(:public_expires_in, DEFAULT_HTTP_PUBLIC_CACHE_EXPIRATION)
  force_write = options.fetch(:force_write, false)
  debug_mode = true
  debug_params = options.fetch(:debug_params) do
    debug_mode = false
    {}
  end

  class_name = self.class.to_s
  cache_key = ck.is_a?(Array) ? "response_cache:#{ck.join(':')}" : "response_cache:#{ck}"

  if is_cache_enabled && !force_write && !stale?(etag: cache_key, template: false)
    Rails.logger.debug("[cacheable][public: #{cache_by_public}] render hit cached CLIENT #{cache_key}")
    return head(:not_modified)
  end

  body = if is_cache_enabled
           # in the Cloudflare cache rule, we only cache the response if there is `locale` in the query string and method should GET
           if request.method == 'GET' && ENV['CLOUDFLARE_CACHE'].to_s == 'true' && cache_by_public && request.query_parameters['locale'].present?
             Rails.logger.debug("[cacheable] cache by public #{cache_key}")
             headers['Cache-Control'] = "max-age=#{public_expires_in.to_i}, public"

             # We can't depend on Cloudflare cache, because we can't clear the cache in Cloudflare perfectly
             # Whenever there is a new data in our side, hh-server touch the cache key correctly in Redis,
             # but not in Cloudflare, so we need to wait until the cache in Cloudflare is expired
             # That's why we use 5 minutes cache in Cloudflare, so the cache in Cloudflare will be expired in 5 minutes
             # FYI we use Cloudflare cache to reduce the server load only
             # The current implementation is not perfect, because we can't clear the cache in Cloudflare
             # We use Cloudflare premium plan, to Enterprise plan, to have the ability to clear the cache
             # using custom keys, such as wildcard, etc.
             cache_in_redis(cache_key, expires_in, format, force_write, debug_mode, debug_params, &block)
           else
             cache_in_redis(cache_key, expires_in, format, force_write, debug_mode, debug_params, &block)
           end
         else
           Rails.logger.debug("[cacheable] skip cache #{class_name}:#{action_name} #{ck.inspect}")
           my_render_to_string(format, &block)
         end

  content_type = format == :json ? Mime[:json].to_s : Mime[:html].to_s
  render body: body, content_type: content_type
end