Skip to content

Instantly share code, notes, and snippets.

@Inversion-des
Created July 27, 2018 09:05
Show Gist options
  • Save Inversion-des/8f920066434c8389be81dae8ed9c54fd to your computer and use it in GitHub Desktop.
Save Inversion-des/8f920066434c8389be81dae8ed9c54fd to your computer and use it in GitHub Desktop.
Ruby Memoizable decorator experiment (from power-of-trust.net)
require 'concurrent'
require 'method_decorators'
# usage in class:
# include Decorators
module Decorators
# when module included (hook)
def self.included(base)
# some class-methods needed for decorators to work
base.extend MethodDecorators
end
# *doesn't work for methods with block_given (will raise exception)
# *works for methods and class-methods
# usage in def:
# +Memoizable
# def method
# usage in calls:
# Test.action(2){:real} | Test.action(2){:flush} — flush cache only for this arg
# Test.action{:flush} — flush all cache
# Test.action(n:1, b:2) — works correctly for arguments like hash and array
# Test.action — works also if no params
# pass block to memoizable method 'members_uids'
# def members_count(&block)
# members_uids(count:true, &block)
# end
# so we can now call: g.members_count{:real}
class Memoizable < MethodDecorators::Decorator
@@known_flags = {real:1, flush:1}
attr :cache_map_by_obj_args
# on destroy of some object — remove its using as a key in cache_map_by_obj_args from all existing Memoizable decorators
# in 'def destroy'
# Memoizable.unref self
def Memoizable.unref(destroyed_o)
ObjectSpace.each_object(Memoizable) do |decorator|
decorator.cache_map_by_obj_args.delete destroyed_o
end
end
# helps to check if some garbage left after :flush
# Decorators::Memoizable.stats
def Memoizable.stats
ObjectSpace.each_object(Memoizable) {|o| p o }
end
# Decorators::Memoizable.total
def Memoizable.total
ObjectSpace.each_object(Memoizable).count
end
def initialize
@cache_map_by_obj_args = {}
end
#! with calling .destroy there can be problem if some other links to this item still are in use…
def call(wrapped, this, *args, &blk)
# (<!) detect flag from block
if block_given?
flag = blk.()
raise NotImplementedError, 'Decorators::Memoizable doesn\'t work for methods with block_given' if !@@known_flags[flag]
end
# thread safe Concurrent::Map used here instead of Hash to overcome an error:
# `[]=': can't add a new key into hash during iteration (RuntimeError)
# *Concurrent::Map has better performance than Concurrent::Hash
#cache_map = @cache_map_by_obj_args[this] ||= {}
cache_map = @cache_map_by_obj_args[this] ||= Concurrent::Map.new
# (<!) on :flush flag — clear all cache
if flag == :flush && args.empty?
for item in cache_map.values
item.destroy if item.respond_to? :destroy
end
# *deletion not needed here, deletion works in Memoizable.unref
# @cache_map_by_obj_args.delete this
cache_map.clear
return
end
signature = args
# if cached
if cache_map.key?(signature)
item = cache_map[signature]
# on :real|:flush flag — clear this cache
if flag
item.destroy if item.respond_to? :destroy
cache_map.delete signature
else
# (<!) return cached
return item
end
end
# cache and return
cache_map[signature] = wrapped.(*args)
end
end
end
class Test
include Decorators
+Memoizable
def Test.get_group(n)
puts "------ get_group #{n} working..."
{rg:n}
end
+Memoizable
def Test.get_group2(n:nil, **others)
puts "------ get_group2 #{n} working..."
{rg:n}
end
end
#puts Test.new.method_added # — should raise undefined method
# puts Test.method_added # — should raise wrong number of arguments
puts Test.get_group 1
puts Test.get_group 1
puts Test.get_group(1){:real}
puts Test.get_group 1
puts Test.get_group2(n:1)
puts Test.get_group2(n:1)
puts Test.get_group2{:flush}
puts Test.get_group2(n:1)
class Group
include Decorators
+Memoizable
def members_uids(p:'1', count:false)
puts 'working'
"p:"+p
end
def members_count(&block)
members_uids(count:true, &block)
end
end
g = Group.new
puts g.members_count
puts g.members_count
puts g.members_count{:real}
puts g.members_count
@Inversion-des
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment