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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bde58c968de960b402bea81f6fbbf9b79432dca0
4
- data.tar.gz: addbbf2bb047dab73b91fe0d1e4a3467e48bfcaf
3
+ metadata.gz: f4f1025df16249c199852986752859e1182992dc
4
+ data.tar.gz: 4cf21ac8e1adaf7c9b2d86f62437b6e73d5412e9
5
5
  SHA512:
6
- metadata.gz: a5751bb4d778bca22ef25041478f7554970bbb3466fc572ffece3f489a73e02ec01f93582c688a3d12c93c0abcb933acf1422656ce5a6cfb25e5c3db5fc525b6
7
- data.tar.gz: 3650a8512ade312a0dedbb3f9d8ca4a75dd2497a064b95ccae5b7083a0e6019b801802eb0a7adc3acac2c40025a96e36d9e6aa075f5fececbccc7fbeda11c8a2
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 Hosting
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
- end
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, component_module
9
+ def initialize parent, mod
10
10
  @_compound_component_parent = parent
11
- extend component_module
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.1
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-15 00:00:00.000000000 Z
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.5
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: