compound 0.0.1 → 0.0.2
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/README.md +229 -0
- data/lib/compound/host.rb +20 -5
- data/lib/compound/part.rb +3 -2
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f4f1025df16249c199852986752859e1182992dc
|
|
4
|
+
data.tar.gz: 4cf21ac8e1adaf7c9b2d86f62437b6e73d5412e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f4a198d31d92fd0e91c46b8519d2592177d524e486e6df2f5481df9303cf6f5f3539d804c62a74f1c3ce8c3e40590ffe5d2e8137e422ea66f4676935205372d8
|
|
7
|
+
data.tar.gz: 4566764d60336ee085f208c74bf5b1c5d63cbd8084ca5712902c94c69ff21c4d1c3d4d50f56b61dba77f8c3f7c4b97cf1d613d2727cadd6547593898e92b6384
|
data/README.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
|
|
2
|
+
Compound provides a mechanism for mixing together modules into an object
|
|
3
|
+
while maintaining some degree of separation to help avoid namespace collisions.
|
|
4
|
+
|
|
5
|
+
# Compounding
|
|
6
|
+
|
|
7
|
+
An instance of a class that includes the `Compound::Host` module gains
|
|
8
|
+
the ability to host one or more `Compound::Part`s within it.
|
|
9
|
+
|
|
10
|
+
``` ruby
|
|
11
|
+
require 'compound'
|
|
12
|
+
|
|
13
|
+
class ManyFacedObject
|
|
14
|
+
include Compound::Host
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
A `Part` is constructed within the `Host` when `#compound` is called.
|
|
19
|
+
The new `Part` is an object `#extend`ed by the module passed to `#compound`.
|
|
20
|
+
Any number of modules can be `#compound`ed into the `Host`.
|
|
21
|
+
|
|
22
|
+
``` ruby
|
|
23
|
+
module Anger; end
|
|
24
|
+
module Sorrow; end
|
|
25
|
+
module Joy; end
|
|
26
|
+
|
|
27
|
+
host = ManyFacedObject.new
|
|
28
|
+
host.compound Anger
|
|
29
|
+
host.compound Sorrow
|
|
30
|
+
host.compound Joy
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
# Method Forwarding
|
|
34
|
+
|
|
35
|
+
If a method is called on the `Host` which is not defined by the `Host`, but
|
|
36
|
+
is defined by one of the `#compound`ed modules, the method and arguments
|
|
37
|
+
will be forwarded to the internal `Part` object associated with that module.
|
|
38
|
+
If more than one module defines the method, it will be forwarded to the one
|
|
39
|
+
which was most recently `#compound`ed.
|
|
40
|
+
|
|
41
|
+
``` ruby
|
|
42
|
+
host.countenance #=> raises NoMethodError
|
|
43
|
+
|
|
44
|
+
module Anger
|
|
45
|
+
def countenance
|
|
46
|
+
:grimace
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
host.countenance #=> :grimace # host can now forward to the new Anger method
|
|
51
|
+
|
|
52
|
+
module Joy
|
|
53
|
+
def countenance
|
|
54
|
+
:grin
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
host.countenance #=> :grin # Joy shadows Anger because Joy was compounded later
|
|
59
|
+
|
|
60
|
+
module Sorrow
|
|
61
|
+
def countenance
|
|
62
|
+
:frown
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
host.countenance #=> :grin # Joy also shadows Sorrow because Joy was compounded later
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`Part` objects will also forward unknown methods back to the `Host`,
|
|
70
|
+
allowing methods of a `#compound`ed module to call public methods
|
|
71
|
+
of the other `#compound`ed modules or of the `Host` itself "natively"
|
|
72
|
+
(without specifying an explicit receiver).
|
|
73
|
+
|
|
74
|
+
``` ruby
|
|
75
|
+
module Anger
|
|
76
|
+
def shout(msg)
|
|
77
|
+
msg.upcase + '!'
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
module Joy
|
|
82
|
+
def exclaim
|
|
83
|
+
shout 'hooray'
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
host.exclaim #=> 'HOORAY!'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
# Privacy
|
|
91
|
+
|
|
92
|
+
Due to this forwarding of methods, the `Host` appears to an outside object
|
|
93
|
+
as if all of the `#compound`ed modules were mixed into it with `#extend`.
|
|
94
|
+
However, the modules' private parts remain partitioned from one another.
|
|
95
|
+
For example, private methods and instance variables are not shared among them.
|
|
96
|
+
|
|
97
|
+
``` ruby
|
|
98
|
+
module Sorrow
|
|
99
|
+
private
|
|
100
|
+
def weep
|
|
101
|
+
'...'
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
module Joy
|
|
106
|
+
def overcome_by_beauty
|
|
107
|
+
weep #=> raises NoMethodError
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
host.overcome_by_beauty #=> call to :weep raises NoMethodError
|
|
112
|
+
```
|
|
113
|
+
``` ruby
|
|
114
|
+
module Joy
|
|
115
|
+
attr_accessor :value
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
module Anger
|
|
119
|
+
def internalize(value)
|
|
120
|
+
@value = value
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def recall
|
|
124
|
+
@value
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
host.value #=> nil # @value retrieved from Joy
|
|
129
|
+
host.internalize 88 #=> 88 # @value stored in Anger
|
|
130
|
+
host.recall #=> 88 # @value retrieved from Anger
|
|
131
|
+
host.value #=> nil # @value retrieved from Joy
|
|
132
|
+
host.value = 999 #=> 999 # @value stored in Joy
|
|
133
|
+
host.recall #=> 88 # @value retrieved from Anger
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This is the chief advantage to using `#compound` instead of `#extend`.
|
|
137
|
+
The modules need no longer worry about avoiding namespace collisions in
|
|
138
|
+
private behaviour. This leads to fewer mixing compatibility issues among
|
|
139
|
+
modules that may not necessarily be versioned in relation to one another.
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Uncompounding
|
|
143
|
+
|
|
144
|
+
As luck would have it, a module can be `#uncompound`ed just as simply
|
|
145
|
+
as it was `#compound`ed.
|
|
146
|
+
|
|
147
|
+
``` ruby
|
|
148
|
+
host.uncompound Anger
|
|
149
|
+
host.uncompound Sorrow
|
|
150
|
+
#=> only Joy remains!
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# Defining Modules for Compounding
|
|
155
|
+
|
|
156
|
+
Some points to keep in mind when defining a module specifically for compounding:
|
|
157
|
+
|
|
158
|
+
- Public methods are 'shared' and should be considered the 'interface' to
|
|
159
|
+
your module; this interface should remain well-documented and versioned.
|
|
160
|
+
|
|
161
|
+
- Private methods are defined only in support of the module,
|
|
162
|
+
and because they are not shared, they are free to change and rearrange
|
|
163
|
+
without worry of namespace collisions with methods of the extended objects
|
|
164
|
+
or about other object methods depending on the use of them.
|
|
165
|
+
|
|
166
|
+
- Instance variables are also not shared, neither among modules nor between
|
|
167
|
+
compounded module and host. However, when accessors are defined for them
|
|
168
|
+
the accessors are part of the public interface along with other
|
|
169
|
+
public methods, and should be treated as such.
|
|
170
|
+
|
|
171
|
+
Because the mechanism of compounding uses a module somewhat differently
|
|
172
|
+
from how it is used in the traditional inclusion/extension mechanism,
|
|
173
|
+
one might want to ensure that the module is used correctly.
|
|
174
|
+
If `Compound::Guard` is `include`d in a module, it will `warn` the user
|
|
175
|
+
if that module is `include`d or `extend`ed instead of `compound`ed.
|
|
176
|
+
|
|
177
|
+
``` ruby
|
|
178
|
+
module CompoundOnly
|
|
179
|
+
include Compound::Guard
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
module OtherModule
|
|
183
|
+
include CompoundOnly #=> warns the user
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
Object.new.extend CompoundOnly #=> warns the user
|
|
187
|
+
|
|
188
|
+
host.compound CompoundOnly #=> intended usage, no warning
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Inversely, one might wish to ensure that a module intended to be used
|
|
192
|
+
traditionally is not used in compounding.
|
|
193
|
+
If `Compound::GuardAgainst` is `include`d in a module,
|
|
194
|
+
it will `warn` the user if that module is `compound`ed.
|
|
195
|
+
|
|
196
|
+
``` ruby
|
|
197
|
+
module TraditionalModule
|
|
198
|
+
include Compound::GuardAgainst
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
module OtherModule
|
|
202
|
+
include TraditionalModule #=> intended usage, no warning
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
Object.new.extend TraditionalModule #=> intended usage, no warning
|
|
206
|
+
|
|
207
|
+
host.compound TraditionalModule #=> warns the user
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# Initialization of Modules
|
|
212
|
+
|
|
213
|
+
Sometimes, a module might want to know that it has been compounded so that it
|
|
214
|
+
can initialize its internal state. Traditional module composition will call
|
|
215
|
+
the `extended` or `self.included` methods if they are defined by the module.
|
|
216
|
+
However, modules intended for compounding should define a `compounded` method
|
|
217
|
+
instead, to be called upon creation of the `Host`'s internal `Part` object.
|
|
218
|
+
|
|
219
|
+
``` ruby
|
|
220
|
+
module Paint
|
|
221
|
+
def compounded
|
|
222
|
+
@color = :royal_blue
|
|
223
|
+
end
|
|
224
|
+
attr_accessor :color
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
host.compound Paint
|
|
228
|
+
host.color #=> :royal_blue
|
|
229
|
+
```
|
data/lib/compound/host.rb
CHANGED
|
@@ -1,24 +1,42 @@
|
|
|
1
1
|
|
|
2
2
|
module Compound
|
|
3
3
|
|
|
4
|
-
module
|
|
4
|
+
# Include this module in a class to gain the ability to host Parts,
|
|
5
|
+
# which each embody a module, appearing to an outside object as if
|
|
6
|
+
# the modules themselves were mixed in, while maintaining separation
|
|
7
|
+
# among the modules. Refer to the README for more information
|
|
8
|
+
module Host
|
|
9
|
+
|
|
10
|
+
# Internalize a new Part embodying the given module
|
|
5
11
|
def compound mod
|
|
6
12
|
@_compound_parts ||= []
|
|
13
|
+
uncompound mod
|
|
7
14
|
@_compound_parts.unshift ::Compound::Part.new self, mod
|
|
8
15
|
mod.compounded(self) if mod.respond_to? :compounded
|
|
16
|
+
return mod
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Remove the Part associated with the given module
|
|
20
|
+
def uncompound mod
|
|
21
|
+
(@_compound_parts &&
|
|
22
|
+
(@_compound_parts.reject! { |part| part.is_a? mod } ? mod : nil))
|
|
9
23
|
end
|
|
10
24
|
|
|
25
|
+
# Forward an undefined method if it is in one of the Parts
|
|
11
26
|
def method_missing sym, *args, &block
|
|
12
27
|
component = @_compound_parts &&
|
|
13
28
|
@_compound_parts.detect { |obj| obj.respond_to? sym }
|
|
14
29
|
component ? (component.send sym, *args, &block) : super
|
|
15
30
|
end
|
|
16
31
|
|
|
32
|
+
# Pretend to also respond_to methods in the Parts as well as the Host
|
|
17
33
|
def respond_to? sym
|
|
18
34
|
super || !!(@_compound_parts &&
|
|
19
35
|
@_compound_parts.detect { |obj| obj.respond_to? sym })
|
|
20
36
|
end
|
|
21
37
|
|
|
38
|
+
# Return the Method object associated with the symbol,
|
|
39
|
+
# even if it is in one of the Parts and not the Host
|
|
22
40
|
def method sym
|
|
23
41
|
return super if methods.include? sym
|
|
24
42
|
component = @_compound_parts &&
|
|
@@ -26,10 +44,7 @@ module Compound
|
|
|
26
44
|
component ? component.method(sym) :
|
|
27
45
|
raise(NameError, "undefined method `#{sym}' for object `#{self}'")
|
|
28
46
|
end
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class Host
|
|
32
|
-
include Hosting
|
|
47
|
+
|
|
33
48
|
end
|
|
34
49
|
|
|
35
50
|
end
|
data/lib/compound/part.rb
CHANGED
|
@@ -6,9 +6,10 @@ module Compound
|
|
|
6
6
|
@_compound_component_parent.send sym, *args, &block
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def initialize parent,
|
|
9
|
+
def initialize parent, mod
|
|
10
10
|
@_compound_component_parent = parent
|
|
11
|
-
extend
|
|
11
|
+
extend mod
|
|
12
|
+
compounded(parent) if mod.instance_methods.include? :compounded
|
|
12
13
|
end
|
|
13
14
|
end
|
|
14
15
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: compound
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joe McIlvain
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2013-12-
|
|
11
|
+
date: 2013-12-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -90,6 +90,7 @@ files:
|
|
|
90
90
|
- lib/compound/host.rb
|
|
91
91
|
- lib/compound/part.rb
|
|
92
92
|
- lib/compound.rb
|
|
93
|
+
- README.md
|
|
93
94
|
homepage: https://github.com/jemc/compound/
|
|
94
95
|
licenses:
|
|
95
96
|
- MIT License
|
|
@@ -110,9 +111,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
110
111
|
version: '0'
|
|
111
112
|
requirements: []
|
|
112
113
|
rubyforge_project:
|
|
113
|
-
rubygems_version: 2.1.
|
|
114
|
+
rubygems_version: 2.1.11
|
|
114
115
|
signing_key:
|
|
115
116
|
specification_version: 4
|
|
116
117
|
summary: compound
|
|
117
118
|
test_files: []
|
|
118
|
-
has_rdoc:
|