This post will be a brief introduction to setting up the Rails cache and seeing it in action with Redis in development.
We will look at the configuration required from Rails but it will expect that you know a little about Redis and have it already installed and setup on your local machine.
We will use Rails to initialize the project demo-rails-cache:
# Create a new rails project
$ rails new demo-rails-with-react-frontend
$ cd demo-rails-with-react-frontend
# Create a controller to demo the cache
$ bin/rails g controllers hello index
At this stage, we are ready to adjust some files and configuration for the demo.
Adjusting the application configuration
We are turning off the default forgery protection so that we can demonstrate the cache on the command-line.
require_relative 'boot'
require 'rails/all'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module DemoRailsCache
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 7.0
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
config.action_controller.default_protect_from_forgery = false if ENV['RAILS_ENV'] == 'development'
end
end
Adjusting the development configuration
Next is to update our development environment configuration to use Redis instead of the default memory store.
I am omitting a lot of the configuration that is already there, so ensure that you only adjust the following:
require 'active_support/core_ext/integer/time'
Rails.application.configure do
# ... omitted
if Rails.root.join('tmp/caching-dev.txt').exist?
config.action_controller.perform_caching = true
config.action_controller.enable_fragment_cache_logging = true
# CHANGE HERE
# config.cache_store = :memory_store
config.cache_store = :redis_cache_store, { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/1') }
config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{2.days.to_i}"
}
else
config.action_controller.perform_caching = false
config.cache_store = :null_store
end
# ... omitted
end
Setting up our hello controller
We will have a basic controller action to demonstrate the cache. It will return some JSON.
class HelloController < ApplicationController
def index
res = Rails.cache.fetch(:cached_result) do
# Only executed if the cache does not already have a value for this key
# We will sleep for 3 seconds to simulate an expensive operation
sleep 3
# Return the array ["Hello", "World"]
messages = %w[Hello World]
end
render json: { message: res }
end
end
We use Rails.cache.fetch to look for the key cached_result in the cache. If it is not found, we will execute the block and store the result in the cache.
Our block has a sleep 3 line to simulate an expensive operation. The idea is that if we miss the cache, we will need to wait 3 seconds for a response.
With this done, we are now ready to ensure the route is available.
Update our routes
Set config/routes.rb to the following:
Rails.application.routes.draw do
resources :hello, only: [:index]
end
We will now be able to access the GET request for /hello.
Running the server and testing our cache
We are now ready to run our server and test the cache setup. We need to ensure that caching is turned on for this.
# Toggle on the dev cache
$ bin/rails dev:cache
# Start the server
$ bin/rails s
In one terminal will be using HTTPie to demonstrate the requests so we can visually see the cache in action.
In another terminal, I will run redis-cli monitor to monitor anything that is entered into the cache and check that the configuration is correct.
Missing the cache
In my first call, I will be missing the cache. The logs look as follows:
You will note here that it took 3.012563 seconds to get the response.
Note: It is outside the scope of this demo, but it might be worth adding another heading X-Cache to indicate that the cache was missed or hit. It is not a standard HTTP header field, but useful for debugging.
The Rails server console tells us the following:
Started GET "/hello" for ::1 at 2022-03-03 11:43:49 +1000
Processing by HelloController#index as */*
Completed 200 OK in 3006ms (Views: 0.3ms | ActiveRecord: 0.0ms | Allocations: 279)
This similarly tells us that it took 3 seconds to get the response.
Hitting the cache
At this point, our cache value will have been set in Redis during the first request.
If we run the request for a a second time, we get the following:
Note here that now the request took 0.010306 seconds.
The Rails server logs tell us the following:
Started GET "/hello" for ::1 at 2022-03-03 11:42:35 +1000
Processing by HelloController#index as */*
Completed 200 OK in 2ms (Views: 0.3ms | ActiveRecord: 0.0ms | Allocations: 207)
Awesome! We got the response from the cache.
If we were running redis-cli monitor that entire time, then the monitor logs tell us the following: