cow_proxy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,6 @@
1
+ module CowProxy
2
+ class Array < WrapClass(::Array)
3
+ include Container
4
+ end
5
+
6
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ module CowProxy
2
+ class Hash < WrapClass(::Hash)
3
+ include Container
4
+ end
5
+
6
+ end
@@ -0,0 +1,7 @@
1
+ require 'set'
2
+
3
+ module CowProxy
4
+ class Set < WrapClass(::Set)
5
+ end
6
+
7
+ end
@@ -0,0 +1,4 @@
1
+ module CowProxy
2
+ class String < WrapClass(::String)
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module CowProxy
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ PATCH = 0
6
+
7
+ STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
+ end
9
+ 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: []