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

WrapClass(klass) click to toggle source

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
debug(line = nil) { || ... } click to toggle source

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(klass, proxy_klass) click to toggle source

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
wrap(obj) click to toggle source

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
wrapper_class(obj) click to toggle source

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

_wrap_class(klass, cow = true, register = false) click to toggle source
Calls superclass method
# 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
define_case_equality(klass) click to toggle source

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
get_proxy_klass_for(klass) click to toggle source
# 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
methods_to_wrap(klass, proxy_superclass) click to toggle source
# 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