cow_proxy 0.2.2 → 0.3.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 +5 -5
- data/lib/cow_proxy.rb +43 -30
- data/lib/cow_proxy/base.rb +44 -34
- data/lib/cow_proxy/enumerable.rb +1 -0
- data/lib/cow_proxy/hash.rb +7 -0
- data/lib/cow_proxy/indexable.rb +11 -1
- data/lib/cow_proxy/set.rb +0 -1
- data/lib/cow_proxy/struct.rb +13 -0
- data/lib/cow_proxy/version.rb +2 -2
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cfbd6c5980cadda3b7c607c1ce3abd0a29d7d59094a3a8c58e6c3d0ba1d2f887
|
4
|
+
data.tar.gz: 2b64b690e0a63f964a75efbc8a9f7aac30ed151747ec756802d6d7c059214148
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71b0c69acbad373ce1b7344474423bfa15e4c008a2b8c73ef256af9aa0b6893dd99e0efef4aa491c01be39e71846015f8486c8ed595f849edfef1b5a588ea7eb
|
7
|
+
data.tar.gz: da7b070003fb7602dd7032d59d7278b18607088f68d0f8432908d6c40c68aa2a40e31f9c693e876182af21ef9e1316ef25bb92a10295b56fe1053db65888f8c2
|
data/README.md
CHANGED
@@ -27,7 +27,7 @@ CowProxy.wrap(obj)
|
|
27
27
|
|
28
28
|
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.
|
29
29
|
|
30
|
-
To create a CowProxy class for custom class, create a new class which inherits from CowProxy::WrapClass(CustomClass)
|
30
|
+
To create a CowProxy class for custom class, create a new class which inherits from `CowProxy::WrapClass(CustomClass)`:
|
31
31
|
|
32
32
|
```ruby
|
33
33
|
module YourModule
|
@@ -40,7 +40,7 @@ obj.freeze
|
|
40
40
|
proxy = CowProxy.wrap(obj)
|
41
41
|
```
|
42
42
|
|
43
|
-
You can create proxy in CowProxy module too:
|
43
|
+
You can create proxy in `CowProxy` module too:
|
44
44
|
|
45
45
|
```ruby
|
46
46
|
module CowProxy
|
@@ -49,9 +49,9 @@ module CowProxy
|
|
49
49
|
end
|
50
50
|
```
|
51
51
|
|
52
|
-
If your custom class has some getters with arguments, such as [] method of Array or Hash
|
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.
|
53
53
|
|
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. But you must inherit from WrapClass(CustomClass) so your new proxy class is registered:
|
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. But you must inherit from `WrapClass(CustomClass)` so your new proxy class is registered:
|
55
55
|
|
56
56
|
```ruby
|
57
57
|
module CowProxy
|
@@ -75,7 +75,7 @@ module CowProxy
|
|
75
75
|
@custom_var = {}
|
76
76
|
end
|
77
77
|
|
78
|
-
def
|
78
|
+
def __copy_on_write__(*)
|
79
79
|
super.tap do
|
80
80
|
if @custom_var
|
81
81
|
@custom_var.each do |k, v|
|
data/lib/cow_proxy.rb
CHANGED
@@ -10,14 +10,10 @@
|
|
10
10
|
# obj = CustomClass.new
|
11
11
|
# obj.freeze
|
12
12
|
# proxy = CowProxy.wrap(obj)
|
13
|
-
|
14
13
|
module CowProxy
|
15
14
|
autoload :Enumerable, 'cow_proxy/enumerable.rb'
|
16
15
|
autoload :Indexable, 'cow_proxy/indexable.rb'
|
17
16
|
class << self
|
18
|
-
# @!visibility private
|
19
|
-
@@wrapper_classes = {}
|
20
|
-
|
21
17
|
# Create new proxy class for klass, with copy on write enabled.
|
22
18
|
#
|
23
19
|
# In other case CowProxy will wrap objects of klass without copy on write
|
@@ -30,8 +26,8 @@ module CowProxy
|
|
30
26
|
# end
|
31
27
|
#
|
32
28
|
# @return new proxy class, so it can be used to create a class which inherits from it
|
33
|
-
def WrapClass(klass)
|
34
|
-
|
29
|
+
def WrapClass(klass) # rubocop:disable Naming/MethodName
|
30
|
+
_wrap_class(klass)
|
35
31
|
end
|
36
32
|
|
37
33
|
# Register proxy to be used when wrapping an object of klass.
|
@@ -43,8 +39,10 @@ module CowProxy
|
|
43
39
|
#
|
44
40
|
# @return proxy_klass
|
45
41
|
def register_proxy(klass, proxy_klass)
|
46
|
-
|
47
|
-
|
42
|
+
return if @wrapper_classes&.dig(klass)
|
43
|
+
debug { "register proxy for #{klass} with #{proxy_klass}#{" < #{proxy_klass.superclass}" if proxy_klass}" }
|
44
|
+
@wrapper_classes ||= {}
|
45
|
+
@wrapper_classes[klass] = proxy_klass
|
48
46
|
end
|
49
47
|
|
50
48
|
# Returns a proxy wrapping obj, using registered class for obj's class.
|
@@ -67,10 +65,10 @@ module CowProxy
|
|
67
65
|
# if none is registered
|
68
66
|
def wrapper_class(obj)
|
69
67
|
# only classes with defined wrapper and Structs has COW enabled by default
|
70
|
-
if
|
71
|
-
|
68
|
+
if @wrapper_classes&.has_key?(obj.class)
|
69
|
+
@wrapper_classes[obj.class]
|
72
70
|
else
|
73
|
-
|
71
|
+
_wrap_class(obj.class, obj.class < ::Struct, true)
|
74
72
|
end
|
75
73
|
end
|
76
74
|
|
@@ -87,38 +85,56 @@ module CowProxy
|
|
87
85
|
end
|
88
86
|
|
89
87
|
private
|
90
|
-
|
91
|
-
|
92
|
-
|
88
|
+
|
89
|
+
def _wrap_class(klass, cow = true, register = false)
|
90
|
+
proxy_superclass = get_proxy_klass_for(klass.superclass)
|
91
|
+
debug { "create new proxy class for #{klass} from #{proxy_superclass} with#{'out' unless cow} cow" }
|
93
92
|
proxy_klass = Class.new(proxy_superclass) do |k|
|
94
93
|
k.wrapped_class = klass
|
95
94
|
end
|
96
95
|
register_proxy klass, proxy_klass if register
|
97
|
-
|
98
|
-
methods -= [:__copy_on_write__, :__wrap__, :__wrapped_value__, :__wrapped_method__, :__getobj__, :enum_for, :send, :===, :frozen?]
|
99
|
-
methods -= proxy_superclass.wrapped_class.instance_methods if proxy_superclass.wrapped_class
|
100
|
-
methods -= [:inspect] if ENV['DEBUG']
|
96
|
+
define_case_equality klass
|
101
97
|
|
98
|
+
methods = methods_to_wrap(klass, proxy_superclass)
|
102
99
|
proxy_klass.module_eval do
|
103
100
|
methods.each do |method|
|
104
101
|
define_method method, proxy_klass.wrapping_block(method, cow)
|
105
102
|
end
|
106
103
|
end
|
107
|
-
proxy_klass.define_singleton_method :public_instance_methods do |all=true|
|
104
|
+
proxy_klass.define_singleton_method :public_instance_methods do |all = true|
|
108
105
|
super(all) - klass.protected_instance_methods
|
109
106
|
end
|
110
|
-
proxy_klass.define_singleton_method :protected_instance_methods do |all=true|
|
107
|
+
proxy_klass.define_singleton_method :protected_instance_methods do |all = true|
|
111
108
|
super(all) | klass.protected_instance_methods
|
112
109
|
end
|
113
110
|
proxy_klass
|
114
111
|
end
|
115
112
|
|
113
|
+
# fix case equality for wrapped objects, kind_of?(klass) works, but klass === was failing
|
114
|
+
def define_case_equality(klass)
|
115
|
+
class << klass
|
116
|
+
def ===(other)
|
117
|
+
CowProxy::Base === other ? other.kind_of?(self) : super(other) # rubocop:disable Style/CaseEquality,Style/ClassCheck
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def methods_to_wrap(klass, proxy_superclass)
|
123
|
+
methods = klass.instance_methods
|
124
|
+
methods -= %i[__copy_on_write__ __wrap__ __wrapped_value__ __wrapped_method__ __getobj__
|
125
|
+
__copy_parent__ enum_for send === frozen?]
|
126
|
+
methods -= proxy_superclass.wrapped_class.instance_methods if proxy_superclass.wrapped_class
|
127
|
+
methods -= [:inspect] if ENV['DEBUG']
|
128
|
+
methods
|
129
|
+
end
|
130
|
+
|
116
131
|
def get_proxy_klass_for(klass)
|
117
|
-
|
132
|
+
proxy_klass = nil
|
118
133
|
klass.ancestors.each do |ancestor|
|
119
|
-
|
134
|
+
proxy_klass = @wrapper_classes&.dig ancestor
|
135
|
+
break if proxy_klass
|
120
136
|
end
|
121
|
-
|
137
|
+
proxy_klass || Base
|
122
138
|
end
|
123
139
|
end
|
124
140
|
end
|
@@ -128,19 +144,16 @@ end
|
|
128
144
|
CowProxy.register_proxy klass, nil
|
129
145
|
end
|
130
146
|
|
131
|
-
if 1.
|
147
|
+
if 1.instance_of? Integer
|
132
148
|
CowProxy.register_proxy Integer, nil
|
133
149
|
else
|
134
|
-
if defined? Fixnum
|
135
|
-
|
136
|
-
end
|
137
|
-
if defined? Bignum
|
138
|
-
CowProxy.register_proxy Bignum, nil
|
139
|
-
end
|
150
|
+
CowProxy.register_proxy Fixnum, nil if defined? Fixnum
|
151
|
+
CowProxy.register_proxy Bignum, nil if defined? Bignum
|
140
152
|
end
|
141
153
|
|
142
154
|
require 'cow_proxy/base.rb'
|
143
155
|
require 'cow_proxy/array.rb'
|
144
156
|
require 'cow_proxy/hash.rb'
|
145
157
|
require 'cow_proxy/string.rb'
|
158
|
+
require 'cow_proxy/struct.rb'
|
146
159
|
require 'cow_proxy/set.rb'
|
data/lib/cow_proxy/base.rb
CHANGED
@@ -17,6 +17,7 @@ module CowProxy
|
|
17
17
|
end
|
18
18
|
|
19
19
|
protected
|
20
|
+
|
20
21
|
# Return block with proxy implementation.
|
21
22
|
#
|
22
23
|
# Block calls a method in wrapped object
|
@@ -28,11 +29,12 @@ module CowProxy
|
|
28
29
|
# @return [Proc] Block with proxy implementation.
|
29
30
|
def wrapping_block(method, cow_enabled)
|
30
31
|
lambda do |*args, &block|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
if method.to_s =~ /^\w+$/
|
33
|
+
inst_var = "@#{method}"
|
34
|
+
return _instance_variable_get(inst_var) if _instance_variable_defined?(inst_var)
|
35
|
+
elsif method.to_s =~ /^(\w+)=$/ && _instance_variable_defined?("@#{Regexp.last_match(1)}")
|
36
|
+
CowProxy.debug { "remove #{Regexp.last_match(1)}" }
|
37
|
+
_remove_instance_variable "@#{Regexp.last_match(1)}"
|
36
38
|
end
|
37
39
|
__wrapped_method__(inst_var, cow_enabled, method, *args, &block)
|
38
40
|
end
|
@@ -53,6 +55,7 @@ module CowProxy
|
|
53
55
|
end
|
54
56
|
|
55
57
|
protected
|
58
|
+
|
56
59
|
# Replace wrapped object with a copy, so object can
|
57
60
|
# be modified.
|
58
61
|
#
|
@@ -63,56 +66,63 @@ module CowProxy
|
|
63
66
|
def __copy_on_write__(parent = true)
|
64
67
|
CowProxy.debug { "copy on write on #{__getobj__.class.name}" }
|
65
68
|
return @delegate_dc_obj if @dc_obj_duplicated
|
66
|
-
@delegate_dc_obj = @delegate_dc_obj.dup
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
if @parent_var
|
71
|
-
parent_dc = @parent_proxy._instance_variable_get(:@delegate_dc_obj)
|
72
|
-
method = @parent_var[1..-1] + '='
|
73
|
-
parent_dc.send(method, new_target)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
69
|
+
@delegate_dc_obj = @delegate_dc_obj.dup
|
70
|
+
@dc_obj_duplicated = true
|
71
|
+
__copy_parent__ if parent && @parent_proxy
|
72
|
+
@delegate_dc_obj
|
77
73
|
end
|
78
74
|
|
79
75
|
private
|
76
|
+
|
80
77
|
def __getobj__
|
81
78
|
@delegate_dc_obj
|
82
79
|
end
|
83
80
|
|
81
|
+
def __copy_parent__
|
82
|
+
@parent_proxy.send :__copy_on_write__, false
|
83
|
+
return unless @parent_var
|
84
|
+
parent_dc = @parent_proxy._instance_variable_get(:@delegate_dc_obj)
|
85
|
+
method = @parent_var[1..-1] + '='
|
86
|
+
parent_dc.send(method, @delegate_dc_obj)
|
87
|
+
end
|
88
|
+
|
84
89
|
def __wrap__(value, inst_var = nil)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
90
|
+
return unless value.frozen?
|
91
|
+
CowProxy.debug { "wrap #{value.class.name} with parent #{__getobj__.class.name}" }
|
92
|
+
wrap_klass = CowProxy.wrapper_class(value)
|
93
|
+
wrap_value = wrap_klass&.new(value, self, inst_var)
|
94
|
+
_instance_variable_set(inst_var, wrap_value) if inst_var && wrap_value
|
95
|
+
wrap_value
|
92
96
|
end
|
93
97
|
|
94
98
|
def __wrapped_value__(inst_var, method, *args, &block)
|
95
|
-
CowProxy.debug
|
99
|
+
CowProxy.debug do
|
100
|
+
"run on #{__getobj__.class.name} (#{__getobj__.object_id}) "\
|
101
|
+
"#{method} #{args.inspect unless args.empty?}"
|
102
|
+
end
|
96
103
|
value = __getobj__.__send__(method, *args, &block)
|
97
|
-
wrap_value = __wrap__(value, inst_var) if inst_var && args.empty? && block
|
104
|
+
wrap_value = __wrap__(value, inst_var) if inst_var && args.empty? && !block
|
98
105
|
wrap_value || value
|
99
106
|
end
|
100
107
|
|
101
108
|
def __wrapped_method__(inst_var, cow, method, *args, &block)
|
102
109
|
__wrapped_value__(inst_var, method, *args, &block)
|
103
|
-
rescue => e
|
104
|
-
CowProxy.debug
|
110
|
+
rescue StandardError => e
|
111
|
+
CowProxy.debug do
|
112
|
+
"error #{e.message} on #{__getobj__.class.name} (#{__getobj__.object_id}) #{method} "\
|
113
|
+
"#{args.inspect unless args.empty?} with#{'out' unless cow} cow"
|
114
|
+
end
|
105
115
|
raise unless cow && e.message =~ /^can't modify frozen/
|
106
|
-
CowProxy.debug { "copy on write to run #{method}
|
116
|
+
CowProxy.debug { "copy on write to run #{method}" }
|
107
117
|
__copy_on_write__
|
108
118
|
CowProxy.debug { "new target #{__getobj__.class.name} (#{__getobj__.object_id})" }
|
109
119
|
__wrapped_value__(inst_var, method, *args, &block)
|
110
120
|
end
|
111
121
|
|
112
|
-
alias
|
113
|
-
alias
|
114
|
-
alias
|
115
|
-
alias
|
116
|
-
alias
|
122
|
+
alias _instance_variable_get instance_variable_get
|
123
|
+
alias _instance_variable_set instance_variable_set
|
124
|
+
alias _remove_instance_variable remove_instance_variable
|
125
|
+
alias _instance_variable_defined? instance_variable_defined?
|
126
|
+
alias _instance_variables instance_variables
|
117
127
|
end
|
118
|
-
end
|
128
|
+
end
|
data/lib/cow_proxy/enumerable.rb
CHANGED
data/lib/cow_proxy/hash.rb
CHANGED
@@ -60,6 +60,13 @@ module CowProxy
|
|
60
60
|
map(&:last)
|
61
61
|
end
|
62
62
|
|
63
|
+
# Returns true if the given key is present in hash.
|
64
|
+
#
|
65
|
+
# @return [Array] Wrapped values from hash
|
66
|
+
def include?(key)
|
67
|
+
key?(key)
|
68
|
+
end
|
69
|
+
|
63
70
|
# Used for merging into another Hash
|
64
71
|
# needs to return unwrapped Hash
|
65
72
|
#
|
data/lib/cow_proxy/indexable.rb
CHANGED
@@ -9,7 +9,7 @@ module CowProxy
|
|
9
9
|
#
|
10
10
|
# @return CowProxy wrapped value from wrapped object
|
11
11
|
def [](index)
|
12
|
-
return @hash[index] if @hash
|
12
|
+
return @hash[index] if @hash&.has_key?(index)
|
13
13
|
|
14
14
|
begin
|
15
15
|
value = __getobj__[index]
|
@@ -20,6 +20,15 @@ module CowProxy
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
# Extracts the nested value specified by the sequence of idx objects by calling dig
|
24
|
+
# at each step, returning nil if any intermediate step is nil.
|
25
|
+
#
|
26
|
+
# @return CowProxy wrapped value from wrapped object
|
27
|
+
def dig(key, *args)
|
28
|
+
value = self[key]
|
29
|
+
args.empty? ? value : value&.dig(*args)
|
30
|
+
end
|
31
|
+
|
23
32
|
# Extends {CowProxy::Base#initialize}
|
24
33
|
def initialize(*)
|
25
34
|
super
|
@@ -27,6 +36,7 @@ module CowProxy
|
|
27
36
|
end
|
28
37
|
|
29
38
|
protected
|
39
|
+
|
30
40
|
# Copy wrapped values to duplicated wrapped object
|
31
41
|
# @see CowProxy::Base#__copy_on_write__
|
32
42
|
# @return duplicated wrapped object
|
data/lib/cow_proxy/set.rb
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
module CowProxy
|
2
|
+
# Wrapper class for Struct
|
3
|
+
class Struct < WrapClass(::Struct)
|
4
|
+
# Extracts the nested value specified by the sequence of idx objects by
|
5
|
+
# calling dig at each step, returning nil if any intermediate step is nil.
|
6
|
+
#
|
7
|
+
# @return CowProxy wrapped value from wrapped object
|
8
|
+
def dig(key, *args)
|
9
|
+
value = send(key)
|
10
|
+
args.empty? ? value : value&.dig(*args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
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.
|
4
|
+
version: 0.3.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: 2019-03-
|
11
|
+
date: 2019-03-29 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
|
@@ -29,6 +29,7 @@ files:
|
|
29
29
|
- lib/cow_proxy/indexable.rb
|
30
30
|
- lib/cow_proxy/set.rb
|
31
31
|
- lib/cow_proxy/string.rb
|
32
|
+
- lib/cow_proxy/struct.rb
|
32
33
|
- lib/cow_proxy/version.rb
|
33
34
|
homepage: http://github.com/Programatica/cow_proxy
|
34
35
|
licenses:
|
@@ -42,7 +43,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
43
|
requirements:
|
43
44
|
- - ">="
|
44
45
|
- !ruby/object:Gem::Version
|
45
|
-
version:
|
46
|
+
version: 2.3.0
|
46
47
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
48
|
requirements:
|
48
49
|
- - ">="
|