Aniket Tiwari
Aniket Tiwari

Reputation: 3998

Cache query on multiple request

I am working on an application where I have a User model which has a default scope

default_scope { preload(:preference, :permission, :user_detail, :log) }

Every time we call current_user it fires 4 queries

And for current_user I have overridden the devise

module DeviseOverrides
  def self.included(base)
    base.class_eval {
      alias_method :devise_current_user, :current_user
      helper_method :current_user, :user_signed_in?
    }

    base.send(:include, InstanceMethods)
  end

  module InstanceMethods
    def current_user
      @current_user ||= warden.try(:user) || anonymous_user
    end
 
    def user_signed_in?
      warden.user.present?
    end

  end
end

Let suppose if we have 5 modules on a page. And loading each module we fire ajax call. Basically we will fire 5 requests. Now for every 5 request @current_user will be nil for the first time and it will fire a query. Again when we call current_user in the same request it will return from the cache.

What I want is for the first request it should fire the query and store in @current_user. Later for all the other request in the page, it should return from the cache instead of firing a query

@current_user ||= warden.try(:user) || anonymous_user

warden.user.present?

Can anyone guide me what I need to do

class ApplicationController < ActionController::Base
  around_action :set_current_user

  def set_current_user
    User.current = current_user
    yield
  ensure
    User.current = nil
  end
end


class User < ApplicationRecord
  default_scope { preload(:preference, :permission, :user_detail, :log) 

  def self.current=(user)
    Thread.current[:current_user] = user
  end

  def self.current
    Thread.current[:current_user]
  end

end

As suggested by @kgilpin I updated my application_controller method

def set_current_user
    User.current = warden.try(:user) || anonymous_user
    yield
  ensure
    User.current = nil
  end

Upvotes: 2

Views: 246

Answers (2)

kgilpin
kgilpin

Reputation: 2226

You can initialize and store the current user in a thread-local variable and then access it from anywhere in the process.

So for example, your ApplicationController would do something like this:

  around_action :with_current_user
 
  protected
 
  def with_current_user
    Thread.current[:current_user] = warden.try(:user) || anonymous_user
    begin
      yield
    ensure
      Thread.current[:current_user] = nil
    end
  end
end

Thread.current[:current_user] is now the current user from anywhere in same thread/fiber of the Ruby process.

Note, Ruby thread-local storage syntax [], []= is also safe to use with fibers. See https://docs.ruby-lang.org/en/2.6.0/Thread.html#method-i-5B-5D-3D.

Upvotes: 1

cesartalves
cesartalves

Reputation: 1585

What you're trying to do is impossible with include, because every instance of the included module will have it's own @current_user.

You need to setup the @current_user variable in a scope where all objects can access it, using Dependency Injection or a dynamic dependency resolving mechanism.

One example:

class CurrentUserHolder
 include Singleton

 attr_accessor :current_user

end

module InstanceMethods
    def current_user
      CurrentUserHolder.instance.current_user ||= warden.try(:user) || anonymous_user
    end

end

Upvotes: 0

Related Questions