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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e8cf6a0a0a714b94d3b1fb2ee4445c3eed9e463d
4
- data.tar.gz: 60969f326372e90e304b652abf64b11f3fc27185
3
+ metadata.gz: afff779cb9cfe9e4064c5ae34eb3b586eb4d9fd8
4
+ data.tar.gz: a56bd90775c2936ac005e71096d2e18da0dc423d
5
5
  SHA512:
6
- metadata.gz: e61c552243c7343d68fd446616fd3ab0b934fb6053b67701e726b790c345e01e60b7ea455c51f06b79c1e127502be4718fead2334ae35d0c838bcf9f66c2c5f7
7
- data.tar.gz: 0b1ac68e83427d364f70a5983ad96a497956d50322cfc8c097bf942ac21ab8636e50943994c59b60a95e36fabaa2cfad64c0de66d79b39d0df807b4d3110134c
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
@@ -0,0 +1,5 @@
1
+ module Multiton
2
+ ##
3
+ # Current version of Multiton.
4
+ VERSION = "0.0.2.pre".freeze
5
+ 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
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  "*.gemspec".freeze,
12
12
  "LICENSE.*".freeze,
13
13
  "README.*".freeze,
14
- "liv/**/*.rb".freeze
14
+ "lib/**/*.rb".freeze
15
15
  ]
16
16
  spec.name = "ruby_multiton".freeze
17
17
  spec.summary = "Ruby Multiton pattern implementation.".freeze
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.1.pre
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: