Created
July 27, 2018 09:05
-
-
Save Inversion-des/8f920066434c8389be81dae8ed9c54fd to your computer and use it in GitHub Desktop.
Ruby Memoizable decorator experiment (from power-of-trust.net)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Author
Inversion-des
commented
Jul 27, 2018
- Презентація Декоратори методів in Ruby
- Більше про проект платформи Сила Довіри — https://power-of-trust.net
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment