cow_proxy 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +81 -0
- data/lib/cow_proxy.rb +118 -0
- data/lib/cow_proxy/array.rb +6 -0
- data/lib/cow_proxy/base.rb +50 -0
- data/lib/cow_proxy/container.rb +33 -0
- data/lib/cow_proxy/hash.rb +6 -0
- data/lib/cow_proxy/set.rb +7 -0
- data/lib/cow_proxy/string.rb +4 -0
- data/lib/cow_proxy/version.rb +9 -0
- metadata +56 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 93c94796743b8e64ce62fca81e4b6cbb00d10c5e
|
4
|
+
data.tar.gz: 8c9097cc09c2691f13564318245dd9b09b7a580f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cb70d45374e3edc354755f2a39e4e7d2ece560ceb2739123f37a3ba6e54aea1931bc26585d13e3472ea389e6f0d246d5ad9ba230b7740f2d3f2472267ada8271
|
7
|
+
data.tar.gz: 4e87fb81c8fa67831e195f8f41b79e676c97c47f6dae5545b0f8ad129f672a76f662819d5727f4af8bda4fd70fa12831c003a96ca8b687e9fad47b337aa17e30
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2016-2017 Sergio Cambra
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
Overview
|
2
|
+
========
|
3
|
+
|
4
|
+
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
|
+
|
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.
|
7
|
+
|
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 had 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
|
+
|
10
|
+
Usage
|
11
|
+
-----
|
12
|
+
|
13
|
+
Call CowProxy.wrap with object to be proxied:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
CowProxy.wrap(obj)
|
17
|
+
```
|
18
|
+
|
19
|
+
It doesn't need to be a frozen object, it can be frozen later or only have references to frozen objects, but no object will be duplicated until some change is requested on frozen object.
|
20
|
+
|
21
|
+
To create a CowProxy class for custom class, create a new class which inherits from CowProxy::WrapClass(CustomClass):
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
module YourModule
|
25
|
+
class CustomProxy < CowProxy::WrapClass(CustomClass)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
obj = CustomClass.new(...)
|
30
|
+
obj.freeze
|
31
|
+
proxy = CowProxy.wrap(obj)
|
32
|
+
```
|
33
|
+
|
34
|
+
You can create proxy in CowProxy module too:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
module CowProxy
|
38
|
+
class CustomClass < WrapClass(::CustomClass)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
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::Container module, which is used for Array and Hash classes.
|
44
|
+
|
45
|
+
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
|
+
|
47
|
+
```ruby
|
48
|
+
module CowProxy
|
49
|
+
class CustomClass < Array
|
50
|
+
def custom_get(arg)
|
51
|
+
return @custom_var[index] if @custom_var && @custom_var.has_key?(arg)
|
52
|
+
|
53
|
+
begin
|
54
|
+
value = __getobj__.custom_get(arg)
|
55
|
+
return value if @custom_var.nil?
|
56
|
+
wrap_value = wrap(value)
|
57
|
+
@custom_var[index] = wrap_value if wrap_value
|
58
|
+
wrap_value || value
|
59
|
+
ensure
|
60
|
+
$@.delete_if {|t| /\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:/o =~ t} if $@
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize(obj, *)
|
65
|
+
super
|
66
|
+
@custom_var = {}
|
67
|
+
end
|
68
|
+
|
69
|
+
def _copy_on_write(*)
|
70
|
+
super.tap do
|
71
|
+
if @custom_var
|
72
|
+
@custom_var.each do |k, v|
|
73
|
+
__getobj__.custom_set(k, v)
|
74
|
+
end
|
75
|
+
@custom_var = nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
data/lib/cow_proxy.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
module CowProxy
|
2
|
+
class << self
|
3
|
+
@@wrapper_classes = {}
|
4
|
+
|
5
|
+
# create new proxy class for klass, with copy on write
|
6
|
+
# in other case CowProxy will wrap objects with class without copy on write
|
7
|
+
#
|
8
|
+
# module CowProxy
|
9
|
+
# module MyModule
|
10
|
+
# class MyClass < WrapClass(::MyModule::MyClass)
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
def WrapClass(klass)
|
15
|
+
_WrapClass(klass)
|
16
|
+
end
|
17
|
+
|
18
|
+
# register proxy to be used when another proxy returns a value of klass,
|
19
|
+
# so it wraps with proxy_klass.
|
20
|
+
#
|
21
|
+
# called automatically when inheriting from class returned by WrapClass
|
22
|
+
def register_proxy(klass, proxy_klass)
|
23
|
+
puts "register proxy for #{klass} with #{proxy_klass} < #{proxy_klass.superclass}" if ENV['DEBUG'] && !@@wrapper_classes[klass]
|
24
|
+
@@wrapper_classes[klass] ||= proxy_klass
|
25
|
+
end
|
26
|
+
|
27
|
+
# return a proxy wrapping obj, using registered class for obj's class
|
28
|
+
# if no class is registered for obj, it uses default proxy without
|
29
|
+
# copy on write
|
30
|
+
def wrap(obj)
|
31
|
+
wrapper_class(obj).new(obj)
|
32
|
+
end
|
33
|
+
|
34
|
+
# returns proxy wrapper class for obj, registered proxy or default proxy
|
35
|
+
# without copy on write
|
36
|
+
def wrapper_class(obj)
|
37
|
+
# only classes with defined wrapper and Structs has COW enabled by default
|
38
|
+
@@wrapper_classes[obj.class] || _WrapClass(obj.class, obj.class < Struct, true)
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def wrapping_block(mid, cow_allowed)
|
43
|
+
lambda do |*args, &block|
|
44
|
+
target = __getobj__
|
45
|
+
inst_var = "@#{mid}" if mid.to_s =~ /^\w+$/
|
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
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def _WrapClass(klass, cow = true, register = false)
|
78
|
+
proxy_superclass = get_proxy_klass_for(klass.superclass) || Base
|
79
|
+
Kernel.puts "create new proxy class for #{klass}#{" from #{proxy_superclass}" if proxy_superclass}" if ENV['DEBUG']
|
80
|
+
proxy_klass = Class.new(proxy_superclass) do |k|
|
81
|
+
k.wrapped_class = klass
|
82
|
+
end
|
83
|
+
register_proxy klass, proxy_klass if register
|
84
|
+
methods = klass.instance_methods
|
85
|
+
methods -= [:_copy_on_write, :===, :frozen?]
|
86
|
+
methods -= proxy_superclass.wrapped_class.instance_methods if proxy_superclass.wrapped_class
|
87
|
+
methods -= [:inspect] if ENV['DEBUG']
|
88
|
+
|
89
|
+
proxy_klass.module_eval do
|
90
|
+
methods.each do |method|
|
91
|
+
define_method method, CowProxy.send(:wrapping_block, method, cow)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
proxy_klass.define_singleton_method :public_instance_methods do |all=true|
|
95
|
+
super(all) - klass.protected_instance_methods
|
96
|
+
end
|
97
|
+
proxy_klass.define_singleton_method :protected_instance_methods do |all=true|
|
98
|
+
super(all) | klass.protected_instance_methods
|
99
|
+
end
|
100
|
+
proxy_klass
|
101
|
+
end
|
102
|
+
|
103
|
+
def get_proxy_klass_for(klass)
|
104
|
+
wrapper = nil
|
105
|
+
klass.ancestors.each do |ancestor|
|
106
|
+
wrapper = @@wrapper_classes[ancestor] and break
|
107
|
+
end
|
108
|
+
wrapper
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
require 'cow_proxy/base.rb'
|
114
|
+
require 'cow_proxy/container.rb'
|
115
|
+
require 'cow_proxy/array.rb'
|
116
|
+
require 'cow_proxy/hash.rb'
|
117
|
+
require 'cow_proxy/string.rb'
|
118
|
+
require 'cow_proxy/set.rb'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module CowProxy
|
2
|
+
class Base
|
3
|
+
class << self
|
4
|
+
attr_accessor :wrapped_class
|
5
|
+
|
6
|
+
def inherited(subclass)
|
7
|
+
subclass.wrapped_class = wrapped_class
|
8
|
+
CowProxy.register_proxy wrapped_class, subclass if wrapped_class
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(obj, parent = nil, parent_var = nil)
|
13
|
+
@delegate_dc_obj = obj
|
14
|
+
@parent_proxy = parent
|
15
|
+
@parent_var = parent_var
|
16
|
+
end
|
17
|
+
|
18
|
+
def _copy_on_write(parent = true)
|
19
|
+
Kernel.puts "copy on write on #{__getobj__.class.name}" if ENV['DEBUG']
|
20
|
+
@delegate_dc_obj = @delegate_dc_obj.dup.tap do |new_target|
|
21
|
+
if parent && @parent_proxy
|
22
|
+
@parent_proxy._copy_on_write(false)
|
23
|
+
if @parent_var
|
24
|
+
parent_dc = @parent_proxy._instance_variable_get(:@delegate_dc_obj)
|
25
|
+
method = @parent_var[1..-1] + '='
|
26
|
+
parent_dc.send(method, new_target)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def __getobj__ # :nodoc:
|
34
|
+
@delegate_dc_obj
|
35
|
+
end
|
36
|
+
|
37
|
+
def wrap(value, inst_var = nil)
|
38
|
+
if value.frozen?
|
39
|
+
Kernel.puts "wrap #{value.class.name} with parent #{self.class.name}" if ENV['DEBUG']
|
40
|
+
CowProxy.wrapper_class(value).new(value, self, inst_var)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
alias :_instance_variable_get :instance_variable_get
|
45
|
+
alias :_instance_variable_set :instance_variable_set
|
46
|
+
alias :_remove_instance_variable :remove_instance_variable
|
47
|
+
alias :_instance_variable_defined? :instance_variable_defined?
|
48
|
+
alias :_instance_variables? :instance_variable_defined?
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,33 @@
|
|
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
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cow_proxy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sergio Cambra
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-16 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Make a COW proxy for a frozen object (or deep frozen), it will delegate
|
14
|
+
every read method to proxied object, wrap value in COW proxy if frozen. Trying to
|
15
|
+
modify object will result in data stored in proxy.
|
16
|
+
email: sergio@programatica.es
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files:
|
20
|
+
- README.md
|
21
|
+
files:
|
22
|
+
- LICENSE
|
23
|
+
- README.md
|
24
|
+
- lib/cow_proxy.rb
|
25
|
+
- lib/cow_proxy/array.rb
|
26
|
+
- lib/cow_proxy/base.rb
|
27
|
+
- lib/cow_proxy/container.rb
|
28
|
+
- lib/cow_proxy/hash.rb
|
29
|
+
- lib/cow_proxy/set.rb
|
30
|
+
- lib/cow_proxy/string.rb
|
31
|
+
- lib/cow_proxy/version.rb
|
32
|
+
homepage: http://github.com/Programatica/cow_proxy
|
33
|
+
licenses:
|
34
|
+
- MIT
|
35
|
+
metadata: {}
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 1.9.3
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 2.6.10
|
53
|
+
signing_key:
|
54
|
+
specification_version: 4
|
55
|
+
summary: Copy-on-write proxy class, to use with frozen objects
|
56
|
+
test_files: []
|