cow_proxy 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -3
- data/lib/cow_proxy.rb +47 -51
- data/lib/cow_proxy/array.rb +1 -1
- data/lib/cow_proxy/base.rb +73 -7
- data/lib/cow_proxy/hash.rb +1 -1
- data/lib/cow_proxy/indexable.rb +44 -0
- data/lib/cow_proxy/version.rb +1 -1
- metadata +4 -4
- data/lib/cow_proxy/container.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ce226b279191db83c5d94fdcd974f9ff078f9aed
|
4
|
+
data.tar.gz: 209d901098c7203cd6c08194fe5565a432885c71
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 02695f8b475ab7c813d169a4eb44ae91c2a6b00ec7a0ad41f690667a2bca29b3cedb228c72e1d572a917bbfafd54ddf8080badb0770e229a956e37bb00e52789
|
7
|
+
data.tar.gz: 64156f469d1b130068bfdaeced1988c8daad0528ce28ced26ebc132dcb74785602faa36457632ba79bc00d7bfb6c3fef2c08007543e5909d5a31115970b88a75
|
data/README.md
CHANGED
@@ -1,11 +1,20 @@
|
|
1
|
+
[![Gem
|
2
|
+
Version](https://badge.fury.io/rb/cow_proxy.svg)](http://badge.fury.io/rb/cow_proxy)
|
3
|
+
[![Build Status](https://secure.travis-ci.org/Programatica/cow_proxy.png?branch=master)](http://travis-ci.org/Programatica/cow_proxy)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/Programatica/cow_proxy/badges/gpa.svg)](https://codeclimate.com/github/Programatica/cow_proxy)
|
5
|
+
[![Dependency Status](https://gemnasium.com/Programatica/cow_proxy.png?branch=master)](https://gemnasium.com/Programatica/cow_proxy)
|
6
|
+
[![Test Coverage](https://codeclimate.com/github/Programatica/cow_proxy/badges/coverage.svg)](https://codeclimate.com/github/Programatica/cow_proxy/coverage)
|
7
|
+
[![Inline docs](https://inch-ci.org/github/Programatica/cow_proxy.svg?branch=master)](https://inch-ci.org/github/Programatica/cow_proxy)
|
8
|
+
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
|
9
|
+
|
1
10
|
Overview
|
2
11
|
========
|
3
12
|
|
4
13
|
This is a copy-on-write proxy for frozen Ruby objects, so duplicating frozen object is delayed until a method tries to change frozen object.
|
5
14
|
|
6
|
-
CowProxy classes for array, hash, string and struct are provided. Objects from other classes will be proxied without copy-on-write, you have to create a CowProxy class for them. Also you have to create CowProxy class for each object which have any getter method with arguments, because default CowProxy won't wrap returned value. Immutable classes such as Integer or TrueClass doesn't need copy-on-write proxy because they can't be changed.
|
15
|
+
CowProxy classes for array, hash, string and struct are provided. Objects from other classes will be proxied without copy-on-write, you have to create a CowProxy class for them to enable copy-on-write. Also you have to create CowProxy class for each object which have any getter method with arguments, because default CowProxy won't wrap returned value. Immutable classes such as Integer or TrueClass doesn't need copy-on-write proxy because they can't be changed.
|
7
16
|
|
8
|
-
You can wrap every object in a proxy. Proxy will always send method calls to wrapped object, and wrap returned value with CowProxy if method
|
17
|
+
You can wrap every object in a proxy. Proxy will always send method calls to wrapped object, and wrap returned value with a CowProxy class if method has no argument, so a proxy will always return proxy objects for getters without arguments. When a method tries to change a frozen object, if proxy has copy-on-write enabled, it will duplicate frozen object and will send next method calls to duplicated object, in other case an exception is raised.
|
9
18
|
|
10
19
|
Usage
|
11
20
|
-----
|
@@ -40,7 +49,7 @@ module CowProxy
|
|
40
49
|
end
|
41
50
|
```
|
42
51
|
|
43
|
-
If your custom class has some getters with arguments, such as [] method of Array or Hash, you will have to define it in your Proxy so it wraps returned values and memoizes them, and override _copy_on_write to set memoized proxies to duplicated object. Wrapped object can be accessed from proxy with \__getobj\__ method. You can see an example in CowProxy::
|
52
|
+
If your custom class has some getters with arguments, such as [] method of Array or Hash, you will have to define it in your Proxy so it wraps returned values and memoizes them, and override _copy_on_write to set memoized proxies to duplicated object. Wrapped object can be accessed from proxy with \__getobj\__ method. You can see an example in CowProxy::Indexable module, which is used for Array and Hash classes.
|
44
53
|
|
45
54
|
If your custom class inherits from a class with CowProxy class, you don't need to create an own class, unless you need to override some method:
|
46
55
|
|
data/lib/cow_proxy.rb
CHANGED
@@ -1,94 +1,90 @@
|
|
1
|
+
# This module include public api for CowProxy usage
|
2
|
+
#
|
3
|
+
# @example Create a CowProxy class and register to be used by wrap
|
4
|
+
# module CowProxy
|
5
|
+
# class CustomClass < WrapClass(::CustomClass)
|
6
|
+
# end
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# @example Call CowProxy.wrap with object to be proxied
|
10
|
+
# obj = CustomClass.new
|
11
|
+
# obj.freeze
|
12
|
+
# proxy = CowProxy.wrap(obj)
|
13
|
+
|
1
14
|
module CowProxy
|
2
15
|
class << self
|
16
|
+
# @!visibility private
|
3
17
|
@@wrapper_classes = {}
|
4
18
|
|
5
|
-
#
|
6
|
-
#
|
19
|
+
# Create new proxy class for klass, with copy on write enabled.
|
20
|
+
#
|
21
|
+
# In other case CowProxy will wrap objects of klass without copy on write
|
7
22
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
23
|
+
# module CowProxy
|
24
|
+
# module MyModule
|
25
|
+
# class MyClass < WrapClass(::MyModule::MyClass)
|
26
|
+
# end
|
11
27
|
# end
|
12
28
|
# end
|
13
|
-
#
|
29
|
+
#
|
30
|
+
# @return new proxy class, so it can be used to create a class which inherits from it
|
14
31
|
def WrapClass(klass)
|
15
32
|
_WrapClass(klass)
|
16
33
|
end
|
17
34
|
|
18
|
-
#
|
19
|
-
# so it wraps with proxy_klass.
|
35
|
+
# Register proxy to be used when wrapping an object of klass.
|
20
36
|
#
|
21
|
-
# called automatically when inheriting from class returned by WrapClass
|
37
|
+
# It's called automatically when inheriting from class returned by WrapClass
|
38
|
+
#
|
39
|
+
# @return proxy_klass
|
22
40
|
def register_proxy(klass, proxy_klass)
|
23
|
-
|
41
|
+
debug "register proxy for #{klass} with #{proxy_klass} < #{proxy_klass.superclass}" unless @@wrapper_classes[klass]
|
24
42
|
@@wrapper_classes[klass] ||= proxy_klass
|
25
43
|
end
|
26
44
|
|
27
|
-
#
|
28
|
-
#
|
45
|
+
# Returns a proxy wrapping obj, using registered class for obj's class.
|
46
|
+
# If no class is registered for obj's class, it uses default proxy, without
|
29
47
|
# copy on write
|
48
|
+
#
|
49
|
+
# @return wrapped obj with CowProxy class
|
30
50
|
def wrap(obj)
|
31
51
|
wrapper_class(obj).new(obj)
|
32
52
|
end
|
33
53
|
|
34
|
-
#
|
35
|
-
# without copy on write
|
54
|
+
# Returns proxy wrapper class for obj.
|
55
|
+
# It will return registered proxy or default proxy without copy on write
|
56
|
+
# if none is registered.
|
57
|
+
#
|
58
|
+
# @return registered proxy or default proxy without copy on write
|
59
|
+
# if none is registered
|
36
60
|
def wrapper_class(obj)
|
37
61
|
# only classes with defined wrapper and Structs has COW enabled by default
|
38
62
|
@@wrapper_classes[obj.class] || _WrapClass(obj.class, obj.class < Struct, true)
|
39
63
|
end
|
40
64
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
return _instance_variable_get(inst_var) if inst_var && _instance_variable_defined?(inst_var)
|
47
|
-
if mid.to_s =~ /^(\w+)=$/ && _instance_variable_defined?("@#{$1}")
|
48
|
-
Kernel.puts "remove #{$1}" if ENV['DEBUG']
|
49
|
-
_remove_instance_variable "@#{$1}"
|
50
|
-
end
|
51
|
-
|
52
|
-
cow = cow_allowed
|
53
|
-
begin
|
54
|
-
Kernel.puts "run on #{target.class.name} (#{target.object_id}) #{mid} #{args.inspect unless args.empty?}" if ENV['DEBUG']
|
55
|
-
value = target.__send__(mid, *args, &block)
|
56
|
-
if inst_var && args.empty? && block.nil?
|
57
|
-
wrap_value = wrap(value, inst_var)
|
58
|
-
_instance_variable_set(inst_var, wrap_value) if wrap_value
|
59
|
-
end
|
60
|
-
wrap_value || value
|
61
|
-
rescue => e
|
62
|
-
raise unless cow && e.message =~ /^can't modify frozen/
|
63
|
-
Kernel.puts "copy on write to run #{mid} #{args.inspect unless args.empty?} (#{e.message})" if ENV['DEBUG']
|
64
|
-
target = _copy_on_write
|
65
|
-
Kernel.puts "new target #{target.class.name} (#{target.object_id})" if ENV['DEBUG']
|
66
|
-
cow = false
|
67
|
-
retry
|
68
|
-
ensure
|
69
|
-
Kernel.puts '-----' if ENV['DEBUG']
|
70
|
-
# cleanup exception from cow proxy files
|
71
|
-
$@.delete_if {|t| /\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:/o =~ t} if $@
|
72
|
-
end
|
73
|
-
end
|
65
|
+
# Print debug line if debug is enabled (ENV['DEBUG'] true)
|
66
|
+
# @param [String] line debug line to print
|
67
|
+
# @return nil
|
68
|
+
def debug(line)
|
69
|
+
Kernel.puts line if ENV['DEBUG']
|
74
70
|
end
|
75
71
|
|
76
72
|
private
|
77
73
|
def _WrapClass(klass, cow = true, register = false)
|
78
74
|
proxy_superclass = get_proxy_klass_for(klass.superclass) || Base
|
79
|
-
|
75
|
+
debug "create new proxy class for #{klass}#{" from #{proxy_superclass}" if proxy_superclass}"
|
80
76
|
proxy_klass = Class.new(proxy_superclass) do |k|
|
81
77
|
k.wrapped_class = klass
|
82
78
|
end
|
83
79
|
register_proxy klass, proxy_klass if register
|
84
80
|
methods = klass.instance_methods
|
85
|
-
methods -= [:
|
81
|
+
methods -= [:__copy_on_write__, :__wrap__, :__wrapped_value__, :__wrapped_method__, :__getobj__, :send, :===, :frozen?]
|
86
82
|
methods -= proxy_superclass.wrapped_class.instance_methods if proxy_superclass.wrapped_class
|
87
83
|
methods -= [:inspect] if ENV['DEBUG']
|
88
84
|
|
89
85
|
proxy_klass.module_eval do
|
90
86
|
methods.each do |method|
|
91
|
-
define_method method,
|
87
|
+
define_method method, proxy_klass.wrapping_block(method, cow)
|
92
88
|
end
|
93
89
|
end
|
94
90
|
proxy_klass.define_singleton_method :public_instance_methods do |all=true|
|
@@ -111,7 +107,7 @@ module CowProxy
|
|
111
107
|
end
|
112
108
|
|
113
109
|
require 'cow_proxy/base.rb'
|
114
|
-
require 'cow_proxy/
|
110
|
+
require 'cow_proxy/indexable.rb'
|
115
111
|
require 'cow_proxy/array.rb'
|
116
112
|
require 'cow_proxy/hash.rb'
|
117
113
|
require 'cow_proxy/string.rb'
|
data/lib/cow_proxy/array.rb
CHANGED
data/lib/cow_proxy/base.rb
CHANGED
@@ -1,25 +1,72 @@
|
|
1
1
|
module CowProxy
|
2
|
+
# Base class to create CowProxy classes
|
3
|
+
#
|
4
|
+
# Also, it's used as default CowProxy class for non-registered classes
|
5
|
+
# with copy-on-write disabled, so returned values are wrapped but
|
6
|
+
# methods trying to change object still raise exception.
|
2
7
|
class Base
|
3
8
|
class << self
|
9
|
+
# Class which will be wrapped with this CowProxy class
|
4
10
|
attr_accessor :wrapped_class
|
5
11
|
|
12
|
+
# Setup wrapped_class and register itself into CowProxy
|
13
|
+
# with {CowProxy.register_proxy CowProxy.register_proxy}
|
6
14
|
def inherited(subclass)
|
7
15
|
subclass.wrapped_class = wrapped_class
|
8
16
|
CowProxy.register_proxy wrapped_class, subclass if wrapped_class
|
9
17
|
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
# Return block with proxy implementation.
|
21
|
+
#
|
22
|
+
# Block calls a method in wrapped object
|
23
|
+
#
|
24
|
+
# @param [Symbol] method Method name to call in wrapped object
|
25
|
+
# @param [Boolean] cow_enabled True if copy-on-write is enabled
|
26
|
+
# for proxy class, it will _copy_on_write when method will
|
27
|
+
# modify wrapped object.
|
28
|
+
# @return [Proc] Block with proxy implementation.
|
29
|
+
def wrapping_block(method, cow_enabled)
|
30
|
+
lambda do |*args, &block|
|
31
|
+
inst_var = "@#{method}" if method.to_s =~ /^\w+$/
|
32
|
+
return _instance_variable_get(inst_var) if inst_var && _instance_variable_defined?(inst_var)
|
33
|
+
if method.to_s =~ /^(\w+)=$/ && _instance_variable_defined?("@#{$1}")
|
34
|
+
CowProxy.debug "remove #{$1}"
|
35
|
+
_remove_instance_variable "@#{$1}"
|
36
|
+
end
|
37
|
+
__wrapped_method__(inst_var, cow_enabled, method, *args, &block)
|
38
|
+
end
|
39
|
+
end
|
10
40
|
end
|
11
41
|
|
42
|
+
# Creates a CowProxy object wrapping obj
|
43
|
+
#
|
44
|
+
# @param obj An object to wrap with CowProxy class
|
45
|
+
# @param parent CowProxy object wrapping obj
|
46
|
+
# @param parent_var instance variable name in parent
|
47
|
+
# which keeps this CowProxy object
|
12
48
|
def initialize(obj, parent = nil, parent_var = nil)
|
13
49
|
@delegate_dc_obj = obj
|
14
50
|
@parent_proxy = parent
|
15
51
|
@parent_var = parent_var
|
52
|
+
@dc_obj_duplicated = false
|
16
53
|
end
|
17
54
|
|
18
|
-
|
19
|
-
|
55
|
+
protected
|
56
|
+
# Replace wrapped object with a copy, so object can
|
57
|
+
# be modified.
|
58
|
+
#
|
59
|
+
# @param [Boolean] parent Replace proxy object in parent with
|
60
|
+
# duplicated wrapped object, if this proxy was created from
|
61
|
+
# another CowProxy.
|
62
|
+
# @return duplicated wrapped object
|
63
|
+
def __copy_on_write__(parent = true)
|
64
|
+
CowProxy.debug "copy on write on #{__getobj__.class.name}"
|
65
|
+
return @delegate_dc_obj if @dc_obj_duplicated
|
20
66
|
@delegate_dc_obj = @delegate_dc_obj.dup.tap do |new_target|
|
67
|
+
@dc_obj_duplicated = true
|
21
68
|
if parent && @parent_proxy
|
22
|
-
@parent_proxy.
|
69
|
+
@parent_proxy.send :__copy_on_write__, false
|
23
70
|
if @parent_var
|
24
71
|
parent_dc = @parent_proxy._instance_variable_get(:@delegate_dc_obj)
|
25
72
|
method = @parent_var[1..-1] + '='
|
@@ -30,17 +77,36 @@ module CowProxy
|
|
30
77
|
end
|
31
78
|
|
32
79
|
private
|
33
|
-
def __getobj__
|
80
|
+
def __getobj__
|
34
81
|
@delegate_dc_obj
|
35
82
|
end
|
36
83
|
|
37
|
-
def
|
84
|
+
def __wrap__(value, inst_var = nil)
|
38
85
|
if value.frozen?
|
39
|
-
|
40
|
-
CowProxy.wrapper_class(value).new(value, self, inst_var)
|
86
|
+
CowProxy.debug "wrap #{value.class.name} with parent #{self.class.name}"
|
87
|
+
wrap_value = CowProxy.wrapper_class(value).new(value, self, inst_var)
|
88
|
+
_instance_variable_set(inst_var, wrap_value) if inst_var
|
89
|
+
wrap_value
|
41
90
|
end
|
42
91
|
end
|
43
92
|
|
93
|
+
def __wrapped_value__(inst_var, method, *args, &block)
|
94
|
+
CowProxy.debug "run on #{__getobj__.class.name} (#{__getobj__.object_id}) #{method} #{args.inspect unless args.empty?}"
|
95
|
+
value = __getobj__.__send__(method, *args, &block)
|
96
|
+
wrap_value = __wrap__(value, inst_var) if inst_var && args.empty? && block.nil?
|
97
|
+
wrap_value || value
|
98
|
+
end
|
99
|
+
|
100
|
+
def __wrapped_method__(inst_var, cow, method, *args, &block)
|
101
|
+
__wrapped_value__(inst_var, method, *args, &block)
|
102
|
+
rescue => e
|
103
|
+
raise unless cow && e.message =~ /^can't modify frozen/
|
104
|
+
CowProxy.debug "copy on write to run #{method} #{args.inspect unless args.empty?} (#{e.message})"
|
105
|
+
__copy_on_write__
|
106
|
+
CowProxy.debug "new target #{__getobj__.class.name} (#{__getobj__.object_id})"
|
107
|
+
__wrapped_value__(inst_var, method, *args, &block)
|
108
|
+
end
|
109
|
+
|
44
110
|
alias :_instance_variable_get :instance_variable_get
|
45
111
|
alias :_instance_variable_set :instance_variable_set
|
46
112
|
alias :_remove_instance_variable :remove_instance_variable
|
data/lib/cow_proxy/hash.rb
CHANGED
@@ -0,0 +1,44 @@
|
|
1
|
+
module CowProxy
|
2
|
+
# A mixin to add wrapper getter and copy-on-write for
|
3
|
+
# indexable classes, such as Array and Hash, i.e. classes
|
4
|
+
# with [] method
|
5
|
+
module Indexable
|
6
|
+
# Calls [](index) in wrapped object and keep wrapped
|
7
|
+
# value, so same wrapped value is return on following
|
8
|
+
# calls with same index.
|
9
|
+
#
|
10
|
+
# @return CowProxy wrapped value from wrapped object
|
11
|
+
def [](index)
|
12
|
+
return @hash[index] if @hash && @hash.has_key?(index)
|
13
|
+
|
14
|
+
begin
|
15
|
+
value = __getobj__[index]
|
16
|
+
return value if @hash.nil?
|
17
|
+
wrap_value = __wrap__(value)
|
18
|
+
@hash[index] = wrap_value if wrap_value
|
19
|
+
wrap_value || value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Extends {CowProxy::Base#initialize}
|
24
|
+
def initialize(*)
|
25
|
+
super
|
26
|
+
@hash = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
# Copy wrapped values to duplicated wrapped object
|
31
|
+
# @see CowProxy::Base#__copy_on_write__
|
32
|
+
# @return duplicated wrapped object
|
33
|
+
def __copy_on_write__(*)
|
34
|
+
super.tap do
|
35
|
+
if @hash
|
36
|
+
@hash.each do |k, v|
|
37
|
+
__getobj__[k] = v
|
38
|
+
end
|
39
|
+
@hash = nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/cow_proxy/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cow_proxy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergio Cambra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-21 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Make a COW proxy for a frozen object (or deep frozen), it will delegate
|
14
14
|
every read method to proxied object, wrap value in COW proxy if frozen. Trying to
|
@@ -24,8 +24,8 @@ files:
|
|
24
24
|
- lib/cow_proxy.rb
|
25
25
|
- lib/cow_proxy/array.rb
|
26
26
|
- lib/cow_proxy/base.rb
|
27
|
-
- lib/cow_proxy/container.rb
|
28
27
|
- lib/cow_proxy/hash.rb
|
28
|
+
- lib/cow_proxy/indexable.rb
|
29
29
|
- lib/cow_proxy/set.rb
|
30
30
|
- lib/cow_proxy/string.rb
|
31
31
|
- lib/cow_proxy/version.rb
|
@@ -49,7 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
49
|
version: '0'
|
50
50
|
requirements: []
|
51
51
|
rubyforge_project:
|
52
|
-
rubygems_version: 2.
|
52
|
+
rubygems_version: 2.5.1
|
53
53
|
signing_key:
|
54
54
|
specification_version: 4
|
55
55
|
summary: Copy-on-write proxy class, to use with frozen objects
|
data/lib/cow_proxy/container.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
module CowProxy
|
2
|
-
module Container
|
3
|
-
def [](index)
|
4
|
-
return @hash[index] if @hash && @hash.has_key?(index)
|
5
|
-
|
6
|
-
begin
|
7
|
-
value = __getobj__[index]
|
8
|
-
return value if @hash.nil?
|
9
|
-
wrap_value = wrap(value)
|
10
|
-
@hash[index] = wrap_value if wrap_value
|
11
|
-
wrap_value || value
|
12
|
-
ensure
|
13
|
-
$@.delete_if {|t| /\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:/o =~ t} if $@
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def initialize(obj, *)
|
18
|
-
super
|
19
|
-
@hash = {}
|
20
|
-
end
|
21
|
-
|
22
|
-
def _copy_on_write(*)
|
23
|
-
super.tap do
|
24
|
-
if @hash
|
25
|
-
@hash.each do |k, v|
|
26
|
-
__getobj__[k] = v
|
27
|
-
end
|
28
|
-
@hash = nil
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|