compound 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: