memoized_on_frozen 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7b9b5a0421f853e1b9756a4803bc49c70465d202f0f4a6ba3d9af8132be8e83f
4
+ data.tar.gz: dc0a3ed91fae98a822084385a8f8a944e9661baff749cf13866200cc62ddcd3f
5
+ SHA512:
6
+ metadata.gz: d7810466303db44765c16141c0a91d902b4dd4cc4430f8f3fc4d698cf81c2f2f1990a3615773178b0db02b3fd8c3aa732f6f3d84dbfe739b6fc8d93bd5ae6236
7
+ data.tar.gz: c5f78499029c9ccc8f982f1b582b61af55d8608e65d1e4e852216d94119fa1a0f06f7d38d00f75c824dfd880da0a10f4e86613dd0ec344b9533a4be843a8f161
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
@@ -0,0 +1,13 @@
1
+ Lint/UselessAssignment:
2
+ Exclude:
3
+ - 'spec/memoized_on_frozen_spec.rb'
4
+
5
+ Naming/UncommunicativeMethodParamName:
6
+ Exclude:
7
+ - 'spec/support/rectangle.rb'
8
+
9
+ AllCops:
10
+ TargetRubyVersion: 2.5
11
+ Exclude:
12
+ - 'example.rb'
13
+ - 'vendor/**/*'
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.0
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in memoized_on_frozen.gemspec
8
+ gemspec
9
+
10
+ gem 'rubocop'
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ memoized_on_frozen (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.0)
10
+ diff-lcs (1.3)
11
+ parallel (1.12.1)
12
+ parser (2.5.0.5)
13
+ ast (~> 2.4.0)
14
+ powerpack (0.1.1)
15
+ rainbow (3.0.0)
16
+ rake (10.5.0)
17
+ rspec (3.7.0)
18
+ rspec-core (~> 3.7.0)
19
+ rspec-expectations (~> 3.7.0)
20
+ rspec-mocks (~> 3.7.0)
21
+ rspec-core (3.7.1)
22
+ rspec-support (~> 3.7.0)
23
+ rspec-expectations (3.7.0)
24
+ diff-lcs (>= 1.2.0, < 2.0)
25
+ rspec-support (~> 3.7.0)
26
+ rspec-mocks (3.7.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.7.0)
29
+ rspec-support (3.7.1)
30
+ rubocop (0.54.0)
31
+ parallel (~> 1.10)
32
+ parser (>= 2.5)
33
+ powerpack (~> 0.1)
34
+ rainbow (>= 2.2.2, < 4.0)
35
+ ruby-progressbar (~> 1.7)
36
+ unicode-display_width (~> 1.0, >= 1.0.1)
37
+ ruby-progressbar (1.9.0)
38
+ unicode-display_width (1.3.0)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ bundler (~> 1.16)
45
+ memoized_on_frozen!
46
+ rake (~> 10.0)
47
+ rspec (~> 3.0)
48
+ rubocop
49
+
50
+ BUNDLED WITH
51
+ 1.16.1
@@ -0,0 +1,119 @@
1
+ # MemoizedOnFrozen
2
+
3
+ [![Build Status](https://travis-ci.org/iliabylich/memoized_on_frozen.svg?branch=master)](https://travis-ci.org/iliabylich/memoized_on_frozen)
4
+
5
+ Immutable objects are a good thing, but they can't use memoization:
6
+
7
+ ``` ruby
8
+ class Rectangle
9
+ def initialize(x, y)
10
+ @x = x
11
+ @y = y
12
+
13
+ freeze
14
+ end
15
+
16
+ def area
17
+ @area ||= @x * @y
18
+ end
19
+ end
20
+
21
+ Rectangle.new(2, 3).area
22
+ # => FrozenError (can't modify frozen Rectangle)
23
+ ```
24
+
25
+ This gem offers a very simple solution - it performs a memoization on a "global" registry object.
26
+
27
+ Like this:
28
+ ``` ruby
29
+ def area
30
+ $registry[self][:area] ||= @x * @y
31
+ end
32
+ ```
33
+
34
+ But then we face another problem: memory leaking. Every object that is memoized creates a garbage
35
+ in the "global" variable that is never GC-ed. This can be solved using `WeakRef` Ruby class.
36
+
37
+ `MemoizedOnFrozen` uses a `Hash` as a registry under the hood, but this hash uses `WeakRef`s as keys.
38
+
39
+ ## Example
40
+
41
+ Declare a class:
42
+ ``` ruby
43
+ require 'memoized_on_frozen'
44
+
45
+ class Rectangle
46
+ include MemoizedOnFrozen[:area]
47
+
48
+ def initialize(x, y)
49
+ @x = x
50
+ @y = y
51
+
52
+ freeze
53
+ end
54
+
55
+ def area
56
+ puts 'calculating area'
57
+ @x * @y
58
+ end
59
+ end
60
+ ```
61
+
62
+ And see how the library handles it:
63
+
64
+ ``` ruby
65
+ rectangle1 = Rectangle.new(1, 2)
66
+ rectangle2 = Rectangle.new(3, 4)
67
+
68
+ rectangle1.area
69
+ # calculating area
70
+ # => 2
71
+ rectangle1.area
72
+ # => 2
73
+
74
+ rectangle2.area
75
+ # calculating area
76
+ # => 12
77
+ rectangle2.area
78
+ # => 12
79
+
80
+ MemoizedOnFrozen::WEAK_STORE.keys
81
+ # => [rectangle1, rectangle2]
82
+
83
+ rectangle1 = rectangle2 = nil
84
+ GC.start
85
+ MemoizedOnFrozen::WEAK_STORE.keys
86
+ # => []
87
+ ```
88
+
89
+ You can find a working example in `example.rb`
90
+
91
+ ## Installation
92
+
93
+ Add this line to your application's Gemfile:
94
+
95
+ ```ruby
96
+ gem 'memoized_on_frozen'
97
+ ```
98
+
99
+ Or if you'd like to use an alias `Memoized` instead of `MemoizedOnFrozen`
100
+ (and you are 100% sure that there are no conflicts with existing classes):
101
+
102
+ ``` ruby
103
+ gem 'memoized_on_frozen', require: 'memoized_on_frozen/as_memoized'
104
+ ```
105
+
106
+ The file `lib/memoized_on_frozen/as_memoized.rb` literally does `Memoized = MemoizedOnFrozen`
107
+
108
+ And then execute:
109
+
110
+ $ bundle
111
+
112
+ Or install it yourself as:
113
+
114
+ $ gem install memoized_on_frozen
115
+
116
+
117
+ ## Contributing
118
+
119
+ Bug reports and pull requests are welcome on GitHub at https://github.com/iliabylich/memoized_on_frozen.
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:rubocop)
8
+ RuboCop::RakeTask.new(:spec)
9
+
10
+ task default: %i[spec rubocop]
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'memoized_on_frozen'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,36 @@
1
+ # bundle exec ruby example.rb
2
+
3
+ require 'memoized_on_frozen'
4
+
5
+ class Rectangle
6
+ include MemoizedOnFrozen[:area]
7
+
8
+ def initialize(x, y)
9
+ @x = x
10
+ @y = y
11
+
12
+ freeze
13
+ end
14
+
15
+ def area
16
+ puts 'calculating area'
17
+ @x * @y
18
+ end
19
+ end
20
+
21
+ rectangle1 = Rectangle.new(1, 2)
22
+ rectangle2 = Rectangle.new(3, 4)
23
+
24
+ puts rectangle1.area
25
+ puts rectangle1.area
26
+
27
+ puts rectangle2.area
28
+ puts rectangle2.area
29
+
30
+ p MemoizedOnFrozen::WEAK_STORE.keys
31
+ # => [rectangle1, rectangle2]
32
+
33
+ rectangle1 = rectangle2 = nil
34
+ GC.start
35
+ p MemoizedOnFrozen::WEAK_STORE.keys
36
+ # => []
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'memoized_on_frozen/version'
4
+ require 'weakref'
5
+
6
+ # A builder module that allows you to do memoization on
7
+ # frozen objects
8
+ #
9
+ # @example
10
+ # class YourClass
11
+ # include MemoizedOnFrozen[:m]
12
+ #
13
+ # def initialize
14
+ # freeze
15
+ # end
16
+ #
17
+ # def m
18
+ # some_heavy_computation
19
+ # end
20
+ # end
21
+ #
22
+ module MemoizedOnFrozen
23
+ # :nodoc:
24
+ class WeakStore < Hash
25
+ (Hash.instance_methods(false) - [:select!]).each do |method_name|
26
+ define_method(method_name) do |*args, &blk|
27
+ cleanup if potentially_stale?
28
+ super(*args, &blk)
29
+ end
30
+ end
31
+
32
+ def initialize(*)
33
+ super
34
+ @last_tracked_gc_run = GC.count
35
+ end
36
+
37
+ def cleanup
38
+ select! { |key, _| key.weakref_alive? }
39
+ @last_tracked_gc_run = GC.count
40
+ end
41
+
42
+ def potentially_stale?
43
+ @last_tracked_gc_run != GC.count
44
+ end
45
+ end
46
+
47
+ WEAK_STORE = WeakStore.new
48
+
49
+ # :nodoc:
50
+ module SetWeakRef
51
+ def initialize(*args, &block)
52
+ @_ref = WeakRef.new(self)
53
+ WEAK_STORE[@_ref] = {}
54
+ super(*args, &block)
55
+ end
56
+ end
57
+
58
+ class << self
59
+ def _module_for(method_names)
60
+ Module.new do
61
+ method_names.each do |method_name|
62
+ define_method(method_name) do
63
+ WEAK_STORE[@_ref][method_name] ||= super()
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ def [](*method_names)
70
+ Module.new do
71
+ define_singleton_method(:included) do |base|
72
+ base.prepend(SetWeakRef, MemoizedOnFrozen._module_for(method_names))
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'memoized_on_frozen'
4
+ Memoized = MemoizedOnFrozen
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MemoizedOnFrozen
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,28 @@
1
+
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path('lib', __dir__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require 'memoized_on_frozen/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'memoized_on_frozen'
10
+ spec.version = MemoizedOnFrozen::VERSION
11
+ spec.authors = ['Ilya Bylich']
12
+ spec.email = ['ibylich@gmail.com']
13
+
14
+ spec.summary = 'A tiny gem for memoization on frozen objects'
15
+ spec.description = 'Because memoization creates an ivar'
16
+ spec.homepage = 'https://github.com/iliabylich/memoized_on_frozen'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.16'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'rspec', '~> 3.0'
28
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: memoized_on_frozen
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ilya Bylich
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-04-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: Because memoization creates an ivar
56
+ email:
57
+ - ibylich@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rubocop.yml"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - Gemfile.lock
67
+ - README.md
68
+ - Rakefile
69
+ - bin/console
70
+ - bin/setup
71
+ - example.rb
72
+ - lib/memoized_on_frozen.rb
73
+ - lib/memoized_on_frozen/as_memoized.rb
74
+ - lib/memoized_on_frozen/version.rb
75
+ - memoized_on_frozen.gemspec
76
+ homepage: https://github.com/iliabylich/memoized_on_frozen
77
+ licenses: []
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.7.3
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: A tiny gem for memoization on frozen objects
99
+ test_files: []