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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 93c94796743b8e64ce62fca81e4b6cbb00d10c5e
4
- data.tar.gz: 8c9097cc09c2691f13564318245dd9b09b7a580f
3
+ metadata.gz: ce226b279191db83c5d94fdcd974f9ff078f9aed
4
+ data.tar.gz: 209d901098c7203cd6c08194fe5565a432885c71
5
5
  SHA512:
6
- metadata.gz: cb70d45374e3edc354755f2a39e4e7d2ece560ceb2739123f37a3ba6e54aea1931bc26585d13e3472ea389e6f0d246d5ad9ba230b7740f2d3f2472267ada8271
7
- data.tar.gz: 4e87fb81c8fa67831e195f8f41b79e676c97c47f6dae5545b0f8ad129f672a76f662819d5727f4af8bda4fd70fa12831c003a96ca8b687e9fad47b337aa17e30
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 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.
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::Container module, which is used for Array and Hash classes.
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
 
@@ -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
- # create new proxy class for klass, with copy on write
6
- # in other case CowProxy will wrap objects with class without copy on write
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
- # module CowProxy
9
- # module MyModule
10
- # class MyClass < WrapClass(::MyModule::MyClass)
23
+ # module CowProxy
24
+ # module MyModule
25
+ # class MyClass < WrapClass(::MyModule::MyClass)
26
+ # end
11
27
  # end
12
28
  # end
13
- # end
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
- # register proxy to be used when another proxy returns a value of klass,
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
- puts "register proxy for #{klass} with #{proxy_klass} < #{proxy_klass.superclass}" if ENV['DEBUG'] && !@@wrapper_classes[klass]
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
- # 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
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
- # returns proxy wrapper class for obj, registered proxy or default proxy
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
- 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
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
- Kernel.puts "create new proxy class for #{klass}#{" from #{proxy_superclass}" if proxy_superclass}" if ENV['DEBUG']
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 -= [:_copy_on_write, :===, :frozen?]
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, CowProxy.send(:wrapping_block, method, cow)
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/container.rb'
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'
@@ -1,6 +1,6 @@
1
1
  module CowProxy
2
2
  class Array < WrapClass(::Array)
3
- include Container
3
+ include Indexable
4
4
  end
5
5
 
6
6
  end
@@ -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
- def _copy_on_write(parent = true)
19
- Kernel.puts "copy on write on #{__getobj__.class.name}" if ENV['DEBUG']
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._copy_on_write(false)
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__ # :nodoc:
80
+ def __getobj__
34
81
  @delegate_dc_obj
35
82
  end
36
83
 
37
- def wrap(value, inst_var = nil)
84
+ def __wrap__(value, inst_var = nil)
38
85
  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)
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
@@ -1,6 +1,6 @@
1
1
  module CowProxy
2
2
  class Hash < WrapClass(::Hash)
3
- include Container
3
+ include Indexable
4
4
  end
5
5
 
6
6
  end
@@ -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
@@ -2,7 +2,7 @@ module CowProxy
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 1
5
- PATCH = 0
5
+ PATCH = 1
6
6
 
7
7
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
8
  end
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.0
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-16 00:00:00.000000000 Z
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.6.10
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
@@ -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