ruby_multiton 0.0.1.pre → 0.0.2.pre
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.
- checksums.yaml +4 -4
- data/lib/multiton/instance_box.rb +60 -0
- data/lib/multiton/mixin.rb +38 -0
- data/lib/multiton/utils.rb +30 -0
- data/lib/multiton/version.rb +5 -0
- data/lib/multiton.rb +166 -0
- data/lib/ruby_multiton.rb +1 -0
- data/ruby_multiton.gemspec +1 -1
- metadata +7 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: afff779cb9cfe9e4064c5ae34eb3b586eb4d9fd8
|
4
|
+
data.tar.gz: a56bd90775c2936ac005e71096d2e18da0dc423d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2fe09106e68a4a800ca2b14a6fcb8bca4c81a66d8ae97c77987a7b909f8a23223d6d3f29d9187821dbfafd5e00243954b21d84672d37dccf60baa60d54cdda1a
|
7
|
+
data.tar.gz: e065a49b34a460e8b7982becb9d5f39fc9752059f89b00c68088a774bc9f7edb0b95373a67828b5324d8ca2cb1bfb0eca7382f7028471eaf033fb3004672cf78
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "sync".freeze
|
2
|
+
require "multiton/utils".freeze
|
3
|
+
|
4
|
+
module Multiton
|
5
|
+
##
|
6
|
+
# InstanceBox is a thread safe container for storing and retrieving multiton instances.
|
7
|
+
class InstanceBox
|
8
|
+
##
|
9
|
+
# call-seq:
|
10
|
+
# new => new_instance
|
11
|
+
#
|
12
|
+
# Returns a new InstanceBox instance.
|
13
|
+
def initialize
|
14
|
+
self.hash = {}
|
15
|
+
self.sync = Sync.new
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# call-seq:
|
21
|
+
# get(key) => instance
|
22
|
+
#
|
23
|
+
# Returns the instance associated with +key+. If +key+ does not exist it returns +nil+.
|
24
|
+
def get(key)
|
25
|
+
sync.synchronize(:SH) { hash[key] }
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# call-seq:
|
30
|
+
# key(instance) => key
|
31
|
+
#
|
32
|
+
# Returns the multiton key associated with +instance+. If +instance+ does not exist it returns +nil+.
|
33
|
+
def key(instance)
|
34
|
+
sync.synchronize(:SH) { Utils.hash_key(hash, instance) }
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# call-seq:
|
39
|
+
# store(key, instance) => instance
|
40
|
+
#
|
41
|
+
# Stores +instance+ indexed by +key+.
|
42
|
+
#
|
43
|
+
# Returns +instance+.
|
44
|
+
def store(key, instance)
|
45
|
+
sync.synchronize(:EX) { hash[key] ||= instance }
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
##
|
51
|
+
# A hash that contains the created instances indexed by their multiton key.
|
52
|
+
attr_accessor :hash
|
53
|
+
|
54
|
+
##
|
55
|
+
# An instance of Sync[https://ruby-doc.org/stdlib-2.4.1/libdoc/sync/rdoc/Sync.html] to provide thread safety (via
|
56
|
+
# a {shared-exclusive lock}[https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock]) when reading and writing
|
57
|
+
# multiton instances to #hash.
|
58
|
+
attr_accessor :sync
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Multiton
|
2
|
+
##
|
3
|
+
# Mixin adds appropriate behavior to multiton instances.
|
4
|
+
module Mixin
|
5
|
+
##
|
6
|
+
# call-seq:
|
7
|
+
# _dump(level) => string
|
8
|
+
#
|
9
|
+
# Serializes the instance as a string that can be reconstituted at a later point.
|
10
|
+
#
|
11
|
+
# Returns string.
|
12
|
+
def _dump(_level)
|
13
|
+
self.class.instance_variable_get(:@__multiton_instances).key self
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# call-seq:
|
18
|
+
# clone
|
19
|
+
#
|
20
|
+
# Raises a TypeError[https://ruby-doc.org/core-2.4.1/TypeError.html] since multiton instances can not be cloned.
|
21
|
+
#
|
22
|
+
# Never returns.
|
23
|
+
def clone
|
24
|
+
raise TypeError, "can't clone instance of multiton `#{self.class.name}`"
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# call-seq:
|
29
|
+
# dup
|
30
|
+
#
|
31
|
+
# Raises a TypeError[https://ruby-doc.org/core-2.4.1/TypeError.html] since multiton instances can not be duplicated.
|
32
|
+
#
|
33
|
+
# Never returns.
|
34
|
+
def dup
|
35
|
+
raise TypeError, "can't dup instance of multiton `#{self.class.name}`"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Multiton
|
2
|
+
##
|
3
|
+
# Adds compatibility methods to support different Ruby versions.
|
4
|
+
module Utils
|
5
|
+
class << self
|
6
|
+
if RUBY_VERSION >= "1.9".freeze
|
7
|
+
##
|
8
|
+
# call-seq:
|
9
|
+
# hash_key(hash, value) => key
|
10
|
+
#
|
11
|
+
# Returns the key associated with +value+ on +hash+. If +value+ does not exist returns +nil+.
|
12
|
+
def hash_key(hash, value)
|
13
|
+
hash.key value
|
14
|
+
end
|
15
|
+
else
|
16
|
+
# :nocov:
|
17
|
+
|
18
|
+
##
|
19
|
+
# call-seq:
|
20
|
+
# hash_key(hash, value) => key
|
21
|
+
#
|
22
|
+
# Returns the key associated with +value+ on +hash+. If +value+ does not exist returns +nil+.
|
23
|
+
def hash_key(hash, value)
|
24
|
+
hash.index value
|
25
|
+
end
|
26
|
+
# :nocov:
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/multiton.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
require "extensible".freeze
|
2
|
+
require "multiton/instance_box".freeze
|
3
|
+
require "multiton/mixin".freeze
|
4
|
+
require "multiton/version".freeze
|
5
|
+
|
6
|
+
##
|
7
|
+
# The Multiton extension implements the {multiton pattern}[https://en.wikipedia.org/wiki/Multiton_pattern] in a manner
|
8
|
+
# similar to Ruby standard library's Singleton[https://ruby-doc.org/stdlib-2.4.1/libdoc/singleton/rdoc/Singleton.html]
|
9
|
+
# module. As a matter of fact Multiton can be used to implement the singleton pattern in a very straightforward way:
|
10
|
+
#
|
11
|
+
# class C
|
12
|
+
# extend Multiton
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# C.instance.object_id #=> 47362978769640
|
16
|
+
# C.instance.object_id #=> 47362978769640
|
17
|
+
#
|
18
|
+
# In order to generate and access different instances a +key+ must be provided as a parameter to the +initialize+
|
19
|
+
# method:
|
20
|
+
#
|
21
|
+
# class C
|
22
|
+
# extend Multiton
|
23
|
+
#
|
24
|
+
# def initialize(key)
|
25
|
+
# # Initialize this instance (if needed).
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# one = C.instance(:one)
|
30
|
+
# two = C.instance(:two)
|
31
|
+
#
|
32
|
+
# one.object_id #=> 46941338337020
|
33
|
+
# C.instance(:one).object_id #=> 46941338337020
|
34
|
+
#
|
35
|
+
# two.object_id #=> 46941338047140
|
36
|
+
# C.instance(:two).object_id #=> 46941338047140
|
37
|
+
#
|
38
|
+
# Almost any object or set of objects will work as a valid +key+. The +key+ assembly is transparently handled by
|
39
|
+
# Multiton internally, leaving the end user with an uncluttered and familiar way of designing their classes:
|
40
|
+
#
|
41
|
+
# class Person
|
42
|
+
# extend Multiton
|
43
|
+
#
|
44
|
+
# attr_reader :full_name
|
45
|
+
#
|
46
|
+
# def initialize(name:, surname:)
|
47
|
+
# self.full_name = "#{surname}, #{name} --- (id: #{object_id})".freeze
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# private
|
51
|
+
#
|
52
|
+
# attr_writer :full_name
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# alice = Person.instance(name: "Alice", surname: "Alcorta")
|
56
|
+
# bob = Person.instance(name: "Bob", surname: "Berman")
|
57
|
+
#
|
58
|
+
# alice.full_name #=> "Alcorta, Alice --- (id: 46921440327980)"
|
59
|
+
# Person.instance(name: "Alice", surname: "Alcorta").full_name #=> "Alcorta, Alice --- (id: 46921440327980)"
|
60
|
+
#
|
61
|
+
# bob.full_name #=> "Berman, Bob --- (id: 46921440022260)"
|
62
|
+
# Person.instance(name: "Bob", surname: "Berman").full_name #=> "Berman, Bob --- (id: 46921440022260)"
|
63
|
+
#
|
64
|
+
# Note that even though keyword arguments will work with Multiton, passing the keywords in a different order will
|
65
|
+
# generate a different instance. It is left up to the end user to design their +key+ in a way that minimizes the
|
66
|
+
# possibility of improper use.
|
67
|
+
module Multiton
|
68
|
+
extend Extensible
|
69
|
+
|
70
|
+
when_extended do |klass|
|
71
|
+
unless klass.is_a? Class
|
72
|
+
raise TypeError, "expected to extend object of type `Class` with module `#{name}`, got `#{klass.class}` instead"
|
73
|
+
end
|
74
|
+
|
75
|
+
klass.class_eval do
|
76
|
+
include Mixin
|
77
|
+
@__multiton_instances = InstanceBox.new
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# call-seq:
|
83
|
+
# _load(args_string) => an_instance
|
84
|
+
#
|
85
|
+
# Creates or reconstitutes a multiton instance from +args_string+, which is a marshalled representation of the +key+
|
86
|
+
# argument(s) passed to #instance.
|
87
|
+
#
|
88
|
+
# Returns a multiton instance.
|
89
|
+
def _load(args_string)
|
90
|
+
instance(*Marshal.load(args_string)) # rubocop:disable Security/MarshalLoad
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# call-seq:
|
95
|
+
# dup => a_class
|
96
|
+
#
|
97
|
+
# Creates a duplicate of the multiton class. Instances will not be shared between the original and duplicate classes.
|
98
|
+
#
|
99
|
+
# Returns a new class.
|
100
|
+
def dup
|
101
|
+
super.tap {|klass| klass.instance_variable_set(:@__multiton_instances, InstanceBox.new) }
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# call-seq:
|
106
|
+
# instance(*args_key) => an_instance
|
107
|
+
#
|
108
|
+
# Creates or retrieves the instance corresponding to +args_key+.
|
109
|
+
#
|
110
|
+
# Returns a multiton instance.
|
111
|
+
def instance(*args_key)
|
112
|
+
key = Marshal.dump(args_key)
|
113
|
+
@__multiton_instances.get(key) || @__multiton_instances.store(key, new(*args_key))
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
##
|
119
|
+
# call-seq:
|
120
|
+
# allocate => nil
|
121
|
+
#
|
122
|
+
# Empty implementation of the +allocate+ method to block the original implemented in +Class+. Effectively does
|
123
|
+
# nothing.
|
124
|
+
#
|
125
|
+
# Returns +nil+.
|
126
|
+
def allocate; end
|
127
|
+
|
128
|
+
##
|
129
|
+
# call-seq:
|
130
|
+
# inherited(subclass) => nil
|
131
|
+
#
|
132
|
+
# This is called when a multiton class is inherited to properly initialize +subclass+. Instances will not be shared
|
133
|
+
# between the superclass and its subclasses.
|
134
|
+
#
|
135
|
+
# Returns +nil+.
|
136
|
+
def inherited(subclass)
|
137
|
+
super
|
138
|
+
subclass.instance_variable_set(:@__multiton_instances, InstanceBox.new)
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# call-seq:
|
144
|
+
# initialize_copy(source) => self
|
145
|
+
#
|
146
|
+
# This is called (on the clone) when a multiton class (+source+) is cloned to properly initialize it. Instances will
|
147
|
+
# not be shared between the original and cloned classes.
|
148
|
+
#
|
149
|
+
# Returns +self+.
|
150
|
+
def initialize_copy(_source)
|
151
|
+
super
|
152
|
+
extend Multiton
|
153
|
+
self
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# call-seq:
|
158
|
+
# new(*args) => new_instance
|
159
|
+
#
|
160
|
+
# Creates a new multiton instance and initializes it by passing +args+ to its constructor.
|
161
|
+
#
|
162
|
+
# Returns a new multiton instance.
|
163
|
+
def new(*args)
|
164
|
+
super
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "multiton".freeze
|
data/ruby_multiton.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_multiton
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabriel de Oliveira
|
@@ -172,6 +172,12 @@ extra_rdoc_files: []
|
|
172
172
|
files:
|
173
173
|
- LICENSE.txt
|
174
174
|
- README.md
|
175
|
+
- lib/multiton.rb
|
176
|
+
- lib/multiton/instance_box.rb
|
177
|
+
- lib/multiton/mixin.rb
|
178
|
+
- lib/multiton/utils.rb
|
179
|
+
- lib/multiton/version.rb
|
180
|
+
- lib/ruby_multiton.rb
|
175
181
|
- ruby_multiton.gemspec
|
176
182
|
homepage: https://github.com/gdeoliveira/ruby_multiton
|
177
183
|
licenses:
|