# frozen_string_literal: true module ActiveModelCachers module ActiveRecord class Cacher @defined_map = {} class << self def define_cacher_method(attr, primary_key, service_klasses) cacher_klass = get_cacher_klass(attr.klass) method = attr.column return cacher_klass.define_find_by(attr, primary_key, service_klasses) if method == nil cacher_klass.attributes << method cacher_klass.send(:define_method, method){ exec_by(attr, primary_key, service_klasses, :get) } cacher_klass.send(:define_method, "peek_#{method}"){ exec_by(attr, primary_key, service_klasses, :peek) } cacher_klass.send(:define_method, "clean_#{method}"){ exec_by(attr, primary_key, service_klasses, :clean_cache) } end def get_cacher_klass(klass) @defined_map[klass] ||= create_cacher_klass_at(klass) end def define_find_by(attr, primary_key, service_klasses) if @find_by_mapping == nil @find_by_mapping = {} attributes << :find_by define_method(:find_by){|args| exec_find_by(args, :get) } define_method(:peek_by){|args| exec_find_by(args, :peek) } define_method(:clean_by){|args| exec_find_by(args, :clean_cache) } end @find_by_mapping[primary_key] = [attr, service_klasses] end def get_data_from_find_by_mapping(primary_key) return if @find_by_mapping == nil return @find_by_mapping[primary_key] end private def create_cacher_klass_at(target) cacher_klass = Class.new(self) cacher_klass.instance_variable_set(:@find_by_mapping, nil) # to remove warning: instance variable @find_by_mapping not initialized cacher_klass.define_singleton_method(:attributes){ @attributes ||= [] } cacher_klass.send(:define_method, 'peek'){|column| send("peek_#{column}") } cacher_klass.send(:define_method, 'clean'){|column| send("clean_#{column}") } target.define_singleton_method(:cacher_at){|id| cacher_klass.new(id: id) } target.define_singleton_method(:cacher){ cacher_klass.new } target.send(:define_method, :cacher){ cacher_klass.new(model: self) } return cacher_klass end end def initialize(id: nil, model: nil) @id = id @model = model end private def exec_find_by(args, method) # e.g. args = {course_id: xx} primary_key = args.keys.sort.first # Support only one key now. attr, service_klasses = self.class.get_data_from_find_by_mapping(primary_key) return if service_klasses == nil return exec_by(attr, primary_key, service_klasses, method, data: args[primary_key]) end def exec_by(attr, primary_key, service_klasses, method, data: nil) bindings = [@model] if @model and attr.association? if attr.belongs_to? and method != :clean_cache # no need to load binding when just cleaning cache association = @model.association(attr.column) bindings << association.load_target if association.loaded? end end data ||= (@model ? @model.send(primary_key) : nil) || @id service_klasses.each_with_index do |service_klass, index| data = service_klass.instance(data).send(method, binding: bindings[index]) return if data == nil end return data end end end end