lib/active_model_cachers/active_record/cacher.rb
# frozen_string_literal: true
module ActiveModelCachers
module ActiveRecord
class Cacher
@defined_map = {}
class << self
def get_cacher_klass(klass)
@defined_map[klass] ||= create_cacher_klass_at(klass)
end
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.send(:define_methods, method, {
method => ->{ exec_by(attr, primary_key, service_klasses, :get) },
"peek_#{method}" => ->{ exec_by(attr, primary_key, service_klasses, :peek) },
"clean_#{method}" => ->{ exec_by(attr, primary_key, service_klasses, :clean_cache) },
})
end
def define_find_by(attr, primary_key, service_klasses)
if @find_by_mapping == nil
@find_by_mapping = {}
define_methods(:find_by, {
:find_by => ->(args){ exec_find_by(args, :get) },
:peek_by => ->(args){ exec_find_by(args, :peek) },
:clean_by => ->(args){ exec_find_by(args, :clean_cache) },
})
end
@find_by_mapping[primary_key] = [attr, service_klasses]
end
private
def define_methods(attribute, methods_mapping)
if attributes.include?(attribute)
methods_mapping.keys.each{|s| undef_method(s) }
else
attributes << attribute
end
methods_mapping.each{|method, block| define_method(method, &block) }
end
def get_data_from_find_by_mapping(primary_key)
return if @find_by_mapping == nil
return @find_by_mapping[primary_key]
end
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.send(: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]
reflects = (attr.belongs_to? ? [] : [attr.reflect])
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], reflect: reflects[index])
return if data == nil
end
return data
end
end
end
end