Low-level caching a database query [Ruby/Rails]

Viewed 54

I have a question about getting low level caching to work. I currently need this feature for my web app

  • Make it so that the first non-filtered page would only make a single database request per day (24 hours). - not done

So low level caching is probably the way forward for this? Ive tried to implement it based on the documentation, but im struggling a little with the set up. This is my code in my homecontroller currently

class HomeController < ApplicationController
  before_action :set_freelancer, only: %i[ show edit update destroy ]


  def cache
    freelancers_scope = Rails.cache.fetch("freelancers", expires_in: 24.hours) do
      Freelancer.order(featured: :desc)
    end
    index(freelancers_scope)
  end

  def index 
    if(freelancers_scope == nil)
      cache
    else
      freelancers_scope = freelancers_scope.where("cost >= ?", params[:cost_lower_than]) if params[:cost_greater_than].present?
      freelancers_scope = freelancers_scope.where("cost <= ?", params[:cost_greater_than]) if params[:cost_lower_than].present?
      @pagy, @freelancers = pagy(freelancers_scope)
    end
  end

  private

  def set_freelancer
    @freelancer = Freelancer.find(params[:id])
  end
end

And this is it without the caching feature being added

lass HomeController < ApplicationController
  before_action :set_freelancer, only: %i[ show edit update destroy ]



  def index
    freelancers_scope = Freelancer.order(featured: :desc)
    freelancers_scope = freelancers_scope.where("cost >= ?", params[:cost_lower_than]) if params[:cost_greater_than].present?
    freelancers_scope = freelancers_scope.where("cost <= ?", params[:cost_greater_than]) if params[:cost_lower_than].present?

    @pagy, @freelancers = pagy(freelancers_scope)
  end

  private

  def set_freelancer
    @freelancer = Freelancer.find(params[:id])
  end
end

How do i cache the result of Freelancer.order(featured: :desc) for 24 hours, and then when its been deleted from the cache, run the query again? Im just getting errors this way currently and its driving me a little mad :)

UPDATE:

class HomeController < ApplicationController
  before_action :set_freelancer, only: %i[ show edit update destroy ]



  def index
    freelancers = Rails.cache.fetch('freelancers', expires_in: 24.hours) do
      Freelancer.order(featured: :desc).map(&:attributes)
    end
  
    freelancers = freelancers.select do |freelancer|
      freelancer['cost'].in?((params[:cost_lower_than].to_i || 0)..params[:cost_greater_than].presence)
    end
  
    @pagy, @freelancers = pagy_array(freelancers)
  end
  

  private

  def set_freelancer
    @freelancer = Freelancer.find(params[:id])
  end
end

https://ibb.co/mzFXWfd This is the error i get now?

1 Answers

First of all I have to warn you that this is very bad practice to store all your ActiveRecord records in cache because if you will have hundreds of thousands records you will always cache them all and then fetch and after that you will need to go through them to find record that meet the conditions.

But here is an example:

Since Rails recommend not to cache instances of Active Record objects you need to simplify your objects. Also you need to cache all freelancers without applying any filters. You can apply your filters after you fetch your records from cache like shown below. And since you have array of freelancers not ActiveRecord_Relation you need to tell your pagy gem how to work with array.

Also keep in mind that you will have to change your code in view where you list your @freelancers.

From this:

@freelancers.each do |freelancer|
  freelancer.cost
end

To this:

@freelancers.each do |freelancer|
  freelancer['cost']
end
def index
  freelancers = Rails.cache.fetch('freelancers', expires_in: 24.hours) do
    Freelancer.order(featured: :desc).map(&:attributes)
  end

  if params[:cost_lower_than].present? || params[:cost_greater_than].present?
    from = params[:cost_lower_than].to_i
    to = params[:cost_greater_than].to_i.zero? ? nil : params[:cost_greater_than].to_i
    freelancers = freelancers.select do |freelancer|
      freelancer['cost'].to_i.in?(from..to) 
    end
  end

  @pagy, @freelancers = pagy_array(freelancers)
end
# config/initializers/pagy.rb

require 'pagy/extras/array'

Note: You can reset cache with this command Rails.cache.clear in code or from your console rake tmp:cache:clear

Related