module CowProxy
This module include public api for CowProxy
usage
@example Create a CowProxy
class and register to be used by wrap
module CowProxy class CustomClass < WrapClass(::CustomClass) end end
@example Call CowProxy.wrap
with object to be proxied
obj = CustomClass.new obj.freeze proxy = CowProxy.wrap(obj)
Public Class Methods
Create new proxy class for klass, with copy on write enabled.
In other case CowProxy
will wrap objects of klass without copy on write
module CowProxy module MyModule class MyClass < WrapClass(::MyModule::MyClass) end end end
@return new proxy class, so it can be used to create a class which inherits from it
# File lib/cow_proxy.rb, line 29 def WrapClass(klass) # rubocop:disable Naming/MethodName _wrap_class(klass) end
Print debug line if debug is enabled (ENV true) Accepts a block instead of line, so interpolation is skipped when debug is disabled
@param [String] line debug line to print @return nil
# File lib/cow_proxy.rb, line 81 def debug(line = nil) return unless ENV['DEBUG'] line ||= yield if block_given? Kernel.puts line end
Register proxy to be used when wrapping an object of klass.
It's called automatically when inheriting from class returned by WrapClass
Can be called with nil proxy_klass to disable wrapping objects of klass, for example Integer is registered with nil because there is no point in wrapping immutable classes.
@return proxy_klass
# File lib/cow_proxy.rb, line 41 def register_proxy(klass, proxy_klass) return if @wrapper_classes&.dig(klass) debug { "register proxy for #{klass} with #{proxy_klass}#{" < #{proxy_klass.superclass}" if proxy_klass}" } @wrapper_classes ||= {} @wrapper_classes[klass] = proxy_klass end
Returns a proxy wrapping obj, using registered class for obj's class. If no class is registered for obj's class, it uses default proxy, without copy on write.
If class is registered with nil Proxy, return obj.
@return wrapped obj with CowProxy
class
# File lib/cow_proxy.rb, line 55 def wrap(obj) klass = wrapper_class(obj) klass ? klass.new(obj) : obj end
Returns proxy wrapper class for obj. It will return registered proxy or default proxy without copy on write if none is registered.
@return registered proxy or default proxy without copy on write
if none is registered
# File lib/cow_proxy.rb, line 66 def wrapper_class(obj) # only classes with defined wrapper and Structs has COW enabled by default if @wrapper_classes&.has_key?(obj.class) @wrapper_classes[obj.class] else _wrap_class(obj.class, obj.class < ::Struct, true) end end
Private Class Methods
# File lib/cow_proxy.rb, line 89 def _wrap_class(klass, cow = true, register = false) proxy_superclass = get_proxy_klass_for(klass.superclass) debug { "create new proxy class for #{klass} from #{proxy_superclass} with#{'out' unless cow} cow" } proxy_klass = Class.new(proxy_superclass) do |k| k.wrapped_class = klass end register_proxy klass, proxy_klass if register define_case_equality klass methods = methods_to_wrap(klass, proxy_superclass) proxy_klass.module_eval do methods.each do |method| define_method method, proxy_klass.wrapping_block(method, cow) end end proxy_klass.define_singleton_method :public_instance_methods do |all = true| super(all) - klass.protected_instance_methods end proxy_klass.define_singleton_method :protected_instance_methods do |all = true| super(all) | klass.protected_instance_methods end proxy_klass end
fix case equality for wrapped objects, kind_of?(klass) works, but klass === was failing
# File lib/cow_proxy.rb, line 114 def define_case_equality(klass) class << klass def ===(other) CowProxy::Base === other ? other.kind_of?(self) : super(other) # rubocop:disable Style/CaseEquality,Style/ClassCheck end end end
# File lib/cow_proxy.rb, line 131 def get_proxy_klass_for(klass) proxy_klass = nil klass.ancestors.each do |ancestor| proxy_klass = @wrapper_classes&.dig ancestor break if proxy_klass end proxy_klass || Base end
# File lib/cow_proxy.rb, line 122 def methods_to_wrap(klass, proxy_superclass) methods = klass.instance_methods methods -= %i[__copy_on_write__ __wrap__ __wrapped_value__ __wrapped_method__ __getobj__ __copy_parent__ enum_for send === frozen?] methods -= proxy_superclass.wrapped_class.instance_methods if proxy_superclass.wrapped_class methods -= [:inspect] if ENV['DEBUG'] methods end