jackbox 0.9.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +5 -0
  3. data/CHANGES.txt +108 -0
  4. data/LICENSE.lic +0 -0
  5. data/LICENSE.txt +13 -0
  6. data/README.md +1395 -0
  7. data/Rakefile +6 -0
  8. data/bin/jackup +248 -0
  9. data/jackbox.gemspec +27 -0
  10. data/jackbox.jpg +0 -0
  11. data/lib/.document +0 -0
  12. data/lib/jackbox.rb +2 -0
  13. data/lib/jackbox/examples/dir.rb +80 -0
  14. data/lib/jackbox/examples/dx.rb +182 -0
  15. data/lib/jackbox/examples/transformers.rb +101 -0
  16. data/lib/jackbox/injectors.rb +2 -0
  17. data/lib/jackbox/rake.rb +2 -0
  18. data/lib/jackbox/tools/prefs.rb +2 -0
  19. data/lib/jackbox/version.rb +4 -0
  20. data/rgloader/loader.rb +23 -0
  21. data/rgloader/rgloader.darwin.bundle +0 -0
  22. data/rgloader/rgloader.freebsd.so +0 -0
  23. data/rgloader/rgloader.freebsd.x86_64.so +0 -0
  24. data/rgloader/rgloader.linux.so +0 -0
  25. data/rgloader/rgloader.linux.x86_64.so +0 -0
  26. data/rgloader/rgloader.mingw.so +0 -0
  27. data/rgloader/rgloader19.darwin.bundle +0 -0
  28. data/rgloader/rgloader19.freebsd.so +0 -0
  29. data/rgloader/rgloader19.freebsd.x86_64.so +0 -0
  30. data/rgloader/rgloader19.linux.so +0 -0
  31. data/rgloader/rgloader19.linux.x86_64.so +0 -0
  32. data/rgloader/rgloader19.mingw.so +0 -0
  33. data/rgloader/rgloader191.mingw.so +0 -0
  34. data/rgloader/rgloader192.darwin.bundle +0 -0
  35. data/rgloader/rgloader192.freebsd.so +0 -0
  36. data/rgloader/rgloader192.freebsd.x86_64.so +0 -0
  37. data/rgloader/rgloader192.linux.so +0 -0
  38. data/rgloader/rgloader192.linux.x86_64.so +0 -0
  39. data/rgloader/rgloader192.mingw.so +0 -0
  40. data/rgloader/rgloader193.darwin.bundle +0 -0
  41. data/rgloader/rgloader193.freebsd.so +0 -0
  42. data/rgloader/rgloader193.freebsd.x86_64.so +0 -0
  43. data/rgloader/rgloader193.linux.so +0 -0
  44. data/rgloader/rgloader193.linux.x86_64.so +0 -0
  45. data/rgloader/rgloader193.mingw.so +0 -0
  46. data/rgloader/rgloader20.darwin.bundle +0 -0
  47. data/rgloader/rgloader20.freebsd.so +0 -0
  48. data/rgloader/rgloader20.freebsd.x86_64.so +0 -0
  49. data/rgloader/rgloader20.linux.so +0 -0
  50. data/rgloader/rgloader20.linux.x86_64.so +0 -0
  51. data/rgloader/rgloader20.mingw.so +0 -0
  52. data/rgloader/rgloader20.mingw.x64.so +0 -0
  53. data/rgloader/rgloader21.darwin.bundle +0 -0
  54. data/rgloader/rgloader21.freebsd.so +0 -0
  55. data/rgloader/rgloader21.freebsd.x86_64.so +0 -0
  56. data/rgloader/rgloader21.linux.so +0 -0
  57. data/rgloader/rgloader21.linux.x86_64.so +0 -0
  58. data/rgloader/rgloader21.mingw.so +0 -0
  59. data/rgloader/rgloader21.mingw.x64.so +0 -0
  60. data/rgloader/rgloader22.darwin.bundle +0 -0
  61. data/rgloader/rgloader22.freebsd.so +0 -0
  62. data/rgloader/rgloader22.linux.so +0 -0
  63. data/rgloader/rgloader22.linux.x86_64.so +0 -0
  64. data/rgloader/rgloader22.mingw.so +0 -0
  65. data/rgloader/rgloader22.mingw.x64.so +0 -0
  66. data/spec/bin/jackup_cmd_shared.rb +176 -0
  67. data/spec/bin/jackup_cmd_spec.rb +292 -0
  68. data/spec/lib/abtract_spec.rb +56 -0
  69. data/spec/lib/jackbox/examples/dir_spec.rb +112 -0
  70. data/spec/lib/jackbox/examples/dx_spec.rb +346 -0
  71. data/spec/lib/jackbox/examples/result.xml +15 -0
  72. data/spec/lib/jackbox/examples/source1.xml +11 -0
  73. data/spec/lib/jackbox/examples/source2.xml +15 -0
  74. data/spec/lib/jackbox/examples/source3.xml +11 -0
  75. data/spec/lib/jackbox/examples/trasnformers_spec.rb +35 -0
  76. data/spec/lib/jackbox/injector_composition_spec.rb +950 -0
  77. data/spec/lib/jackbox/injector_directives_spec.rb +266 -0
  78. data/spec/lib/jackbox/injector_inheritance_spec.rb +799 -0
  79. data/spec/lib/jackbox/injector_introspection_spec.rb +614 -0
  80. data/spec/lib/jackbox/injector_namespacing_spec.rb +345 -0
  81. data/spec/lib/jackbox/injector_spec.rb +847 -0
  82. data/spec/lib/jackbox/injector_versioning_spec.rb +334 -0
  83. data/spec/lib/jackbox/patterns_spec.rb +410 -0
  84. data/spec/lib/jackbox/prefs_spec.rb +212 -0
  85. data/spec/lib/jackbox/reclassing_spec.rb +394 -0
  86. data/spec/lib/jackbox_spec.rb +595 -0
  87. data/spec/spec_helper.rb +139 -0
  88. metadata +218 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 647fddf71b104c650965b120aa7e74a9e8e65efb
4
+ data.tar.gz: 0096d7e7ea26e861b1fc5aa9fcf7f385d8c73439
5
+ SHA512:
6
+ metadata.gz: 4cfa3f28479fc0c5b56c574b7c80a23a9e41e4ee86ecd12e66c104873424535e6a3acfa7827d80892fba1fa69059dec862fc0414194d604914dad8ee72727c70
7
+ data.tar.gz: 71d4b3d519a7d8db7181f6c097d0f847667876f50b68cd221ac8e137cc6b992371ecbdb5025c01e1f0db651b4dd952b4115d55e6eaaca76e00f276aca527d305
data/.yardopts ADDED
@@ -0,0 +1,5 @@
1
+ --no-private
2
+ --hide-void-return
3
+
4
+ --markup-provider=redcarpet
5
+ --markup=markdown
data/CHANGES.txt ADDED
@@ -0,0 +1,108 @@
1
+ 0.9.5.9
2
+
3
+ . Added a spec for Injector behavior under Inheritance
4
+ . Fixed problem with same method name object level class ejection
5
+ . Changed the injector namespace resolution to be more consistent
6
+ . all injectors accessible from the object they were defined
7
+ . top level injectors still accessible at the top
8
+ . Object level injectors accessible everywhere
9
+ . Fixed problem found with late code un-evaluation for blocks called on include/extend/update
10
+ . Fixed problems found with injections on class instances
11
+ . injectors are now reported in this order: Class instance first, instances of THE class second
12
+ . class instance injectors don't get include/reported on instances of the class
13
+ . ejection on the Class instance now works normally
14
+ . Added Name Spacing spec to the set
15
+ . injectors with caps are always part of the global name space
16
+ . the rest of injectors are in scope where they are defined
17
+ . can still be made follow constant lookup
18
+ . Finished spec-out for injector tagging
19
+ . Version Tagging/Naming now has a formal spec as part of name spacing
20
+ . tags are frozen versions of an injector
21
+ . Redefined #ancestors to more correctly reflect the actual ancestor chain
22
+ . the only thing left off are the injectors subject to :implosion
23
+ . the :implosion is marked on the individual injectors
24
+ . Relocated the ancestors examples to the inheritance spec
25
+ . Added a couple of methods relating to injector versions
26
+ . defined history/versions method
27
+ . defined #precedent method
28
+ . Added the concept of soft tags
29
+ . they serve to define a new coding pattern employing the idea of iterative injection
30
+ . they simultaneously create a method :name_ in the callers namespace
31
+ . Changed #with method to module_eval on Modules
32
+ . fixed issue with referring #define_method to caller
33
+ . if it's not in the object for the with it errors out
34
+ . Added transformers example(Working Beautifully)
35
+ . Changed #injectors reflection api to have more flexibility
36
+ . injectors :name gets the last injector by that name
37
+ . injectors :name, :name gets those last injectors
38
+ . injectors simply gets all injectors
39
+ . Finalized injectors.#... sub object model api
40
+ . Name.injectors == [j,......]
41
+ . Name.injectors.by_name == [:name, ......]
42
+ . Name.injectors.sym_list == [:name, ......]
43
+ . Name.injectors.collect_by_name(:name) == [j,......] (default method)
44
+ . Name.injectors.all_by_sym(:name) == [j,......] (default method)
45
+ . same as Name.injectors :name
46
+ . Name.injectors.find_by_name(:name) == j (the last injector by name)
47
+ . Name.injectors.pick_by_sym(:name) == j (the last injector by name)
48
+ . Name.injectors.#Enumerable...
49
+ . Added a way to refers to tags on ancestor introspection( not displaying under pry only irb )
50
+ . Changed the show method to a more compact version
51
+ . Reworked the inheritance and ancestor chain examples
52
+ . Added inheritance spec examples to show ancestor search
53
+ . Added examples into the name spacing spec to show Tags on ancestor chains
54
+ . Fixed an issue existing only on Ruby 1.9 with define method at the top level
55
+ . Cleaned code and examples
56
+ . Changed #to_s/#inspect/#show display routines including for tags
57
+ . Started work on optimizations
58
+ . Added sym_for object method to Module class
59
+ . Solved automatic tag tracing
60
+ . Override module_eval to work with functor(&code)
61
+ . added functor definition
62
+ . Added the notion of equality to Injectors
63
+ . Reworked examples to:
64
+ . comply with new equality operators
65
+ . accommodate hard tags
66
+ . Added the tags method
67
+ . Polished the history method
68
+ . Started exploration on using Weak References for Injectors
69
+ . Fixed complete Injector implosion from leaving the Injector name obliterated
70
+ . Refactored some code
71
+ . Examples to test history
72
+ . Renamed and refactored other examples
73
+ . Added examples to the Patterns spec
74
+ . With statement streamlined and rescued
75
+ . Combined history and tags
76
+ . ensure all tags are accounted for before
77
+ . ensuring history returns correct results
78
+ . Finished with equality
79
+ . Broke out injector spec into smaller files:
80
+ . Directives
81
+ . Introspection
82
+ . Added examples of new workflows and patterns
83
+ . Cleaned up tagging and other examples
84
+ . Updated Ruby, gems, and tested compatibility
85
+ . compatible with the latest and greatest
86
+ ALL SPECS PASSING
87
+
88
+
89
+ 0.9.5.8
90
+
91
+ . More seamless integration with the Ruby base
92
+
93
+ . Injectors now work at the top level
94
+ . extend/enrich to work at the top level
95
+ . include/inject also work at the top level
96
+
97
+ Previously injection/enrichment were faltering at the top level. Now they
98
+ work the same way all the way through.
99
+
100
+ . Ancestors are reported the right way now
101
+ . ejected injectors do not show in the ancestors chain
102
+ . works both on class/singleton_class ancestors calls
103
+
104
+ . Added Injector#show, #to_s, #inspect implementations
105
+ . Changed Injectors name spacing. Now contained within Jackbox.
106
+ . Added some specs and reworked others.
107
+ . Cleaned up some code.
108
+
data/LICENSE.lic ADDED
Binary file
data/LICENSE.txt ADDED
@@ -0,0 +1,13 @@
1
+
2
+ Copyright © 2014, 2015 LHA (Lou Henry Alvarez). All rights reserved.
3
+
4
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
5
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
6
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
7
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
8
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
9
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
10
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11
+
12
+ In the above copyright notice, the letters LHA are the english acronym
13
+ for Luis Enrique Alvarez (Barea) who is the author and owner of the copyright.
data/README.md ADDED
@@ -0,0 +1,1395 @@
1
+ <script>
2
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
3
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
4
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
5
+ })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
6
+
7
+ ga('create', 'UA-58877141-3', 'auto');
8
+ ga('send', 'pageview');
9
+
10
+ </script>
11
+ <!---
12
+ # @author Lou Henry Alvarez
13
+ -->
14
+ Copyright © 2014, 2015 LHA. All rights reserved.
15
+
16
+ <a href="http://jackbox.us"><h1>Jackbox</h1></a>
17
+
18
+ ---
19
+ <h2 style="font-family:Papyrus">Modular Closures, Code Injectors, Re-Classings, and other programmer morphins</h2>
20
+ ---
21
+ The defining idea behind Jackbox is: If Ruby is like Play-Doh, with Jackbox we turn it into <a href="https://en.wikipedia.org/wiki/Plasticine">Plasticine</a>. The main library function at this time centers around the concept of code injectors, the idea of re-classings, and the helper functions that bring them together to provide some new and interesting capabilities.
22
+
23
+ To make it easier to grasp, code injectors can perhaps be thought of as a form of **closures which can also serve as modules**. These modular closures most of all propose some additional interesting properties to the idea of a mix-in. For instance, they make it possible to solve several general problems in some areas of OOP, overcoming traditional Ruby shortcomings with the GOF Decorator and Strategy Patterns, and enabling **some new code patterns** of our own. They instrument control over (code presence) the presence of injector code in targets with mechanisms involving injector ejection and directives. They give your code the ability to capture its surrounding context and mix it into an indiscriminate target. They extend Ruby's mix-in and method resolution over and beyond what is possible with regular modules. Finally, they introduce the concept of Injector Versioning. This is a feature which allows you to redefine parts of your program in local isolation and without it affecting others. See Injector Versioning below.
24
+
25
+ Re-classings on the other hand present an alternative way to refine a class. They provide similar benefits to refinements with a different underpinning. Together with Jackbox helper functions and injectors, re-classings can be be refined multiple times. Capabilities can be added and removed in blocks. Moreover, these re-classings acquire introspecting abilities. A re-class can be tested for existence, can tell you what injectors it uses, and finally can be overridden with a more relevant one.
26
+
27
+ Our guiding principle through out it all has been keeping new constructs to a minimum. We do not aspire to be one of those libraries that add as many methods as one can possibly think of, but which in reality never get used because nobody has the time to read them all. We take an outer minimalistic approach that in reality takes a lot more behind the scenes to make things work. Simplicity takes a lot of work.
28
+
29
+ Basic Methods
30
+ --------------------------
31
+ There are some basic methods to Jackbox. These are just rudimentary helpers, which in effect are a form of syntax sugar for every day things. But, behind their apparent sugar coating lie some powerful capabilities as shown the deeper you delve into Jackbox. For more on them read the following sections, but their preliminary descriptions follow here:
32
+
33
+ #### #decorate :sym, &blk
34
+ This method allows for decorations to be placed on a single method, be it an instance or class method without too much fuss. One important thing about #decorate is that it works like #define_method, but in addition, it also makes possible the use of Ruby's #super within the body of the decorator. It really presents a better alternative and can be used instead of #alias\_method\_chain.
35
+
36
+ At the class level:
37
+
38
+ class One
39
+ decorate :foo do
40
+ super() + 'decoration ' # super available within decoration
41
+ end
42
+ end
43
+
44
+ One.new.foo
45
+ #=> foo decoration
46
+
47
+ Or, at the object level:
48
+
49
+ one = One.new
50
+
51
+ one.decorate :foo do |arg|
52
+ super() + arg # again the use of super is possible
53
+ end
54
+
55
+ one.foo('after')
56
+ #=> foo decoration after
57
+
58
+ It also works like so:
59
+
60
+ Object.decorate :inspect do
61
+ puts super() + " is your object"
62
+ end
63
+
64
+ Object.new.inspect
65
+ #=> #<Object:0x00000101787e20> is your object
66
+
67
+
68
+ #### #with obj, &blk
69
+ There is also a new version of the #with construct. The important thing to remember about #with is it has a primary context which is the object passed to it, and a secondary context which is the object you are making the call from. This allows you to work **with** both contexts at the same time. The other important thing about #with is that it allows you to directly place definitions on and returns the same object you passed into it or the result of the last evaluation in the #with block.
70
+
71
+ Here is some sample usage code:
72
+
73
+ class One
74
+ def foo(arg)
75
+ 'in One ' + arg
76
+ end
77
+ end
78
+
79
+ class Two
80
+ def faa(arg)
81
+ 'and in Two ' + arg
82
+ end
83
+ def meth
84
+ with One.new do # context of One and Two available simultaneously!!!
85
+ return foo faa 'with something'
86
+ end # return object
87
+ end
88
+ end
89
+
90
+ Two.new.meth
91
+ #=> 'in One and in Two with something'
92
+
93
+ Use it to define function:
94
+
95
+ # internal facade for Marshal
96
+ with Object.new do
97
+
98
+ @file_spec = [file, mode]
99
+ def dump hash
100
+ File.open(*@file_spec) do |file|
101
+ Marshal.dump( hash, file)
102
+ end
103
+ end
104
+ def load hash
105
+ File.open(*@file_spec) do |file|
106
+ hash.merge!(Marshal.load( file ))
107
+ end
108
+ end
109
+
110
+ end
111
+
112
+ Use it with **#decorate** on singleton classes like this:
113
+
114
+ class Dir
115
+
116
+ with singleton_class do
117
+ decorate :entries do |name='.', opts=nil| #:doc:
118
+ super name, opts
119
+ end
120
+ decorate :new do |name, &code| #:doc:
121
+ FileUtils.mkpath name unless exists?(name)
122
+ return Dir.open(name, &code) if code
123
+ Dir.open name
124
+ end
125
+ end
126
+
127
+ end
128
+
129
+
130
+ #### #lets sym=nil, &blk
131
+ We could say, this is simple syntax sugar. It adds readability to some constructs. It allows the creation of local or global procs using a more function-like syntax. But #lets, also opens the door to a new coding pattern termed Re-Classing. See below. The important thing about #lets is that it always defines some lambda/proc/method. It's use differs from that of #define_method in spirit, #lets is mostly for one liners. Here are some examples:
132
+
133
+ To define local functions/lambdas. Define symbols in local scope:
134
+
135
+ def main
136
+ lets bar =->(arg){ arg * arg } # read as: lets set bar to lambda/proc
137
+
138
+ # later on ...
139
+
140
+ var = bar[3] # bar is only available within #main
141
+ #...
142
+ end
143
+
144
+ As a shortcut for define_method. Use it for short functional definitions:
145
+
146
+ lets( :meth ){ |arg| arg * 2 } # read as: lets define symbol :meth to be ....
147
+ meth(3)
148
+ # => 6
149
+
150
+ Can be used to define a special values or pseudo-immutable strings:
151
+
152
+ lets(:foo){ 3+Math::Pi } # read as: lets set :foo to value
153
+ lets(:faa){ 'some important string' }
154
+
155
+
156
+ Injectors
157
+ ----------
158
+ Injectors are the main tool in Jackbox at the time of this writing. These again are a form of mix-in that has properties of both a closure and a module. They can also be thought of as an **extended closure** if you will or as a special kind of module if you want. In the sections below we will discuss some of the methods available to you with Jackbox in connection with Injectors, as well as elaborate on some of the other properties of injectors. But, it is essential to understand there are some syntactical differences to Injectors with respect to regular modules. We will show them first, with some examples:
159
+
160
+ **INJECTORS ARE DECLARED IN THE FOLLOWING WAYS:**
161
+
162
+
163
+ injector :name
164
+
165
+ # or...
166
+
167
+ Name = injector :name
168
+
169
+ # or even ...
170
+
171
+ facet :Name # capitalized method, using alias #facet
172
+
173
+
174
+ Their use and semantics are somewhat defined by the following snippet. But, to fully understand their implications to your code, you have to understand the sections on injector versioning, their behavior under inheritance, and perhaps injector directives.
175
+
176
+ # somewhere in your code
177
+ include Injectors
178
+
179
+ injector :my_injector # define the injector
180
+
181
+ my_injector do
182
+ def bar
183
+ :a_bar
184
+ end
185
+ end
186
+
187
+ # later on...
188
+ widget.enrich my_injector # apply the injector
189
+ widget.bar
190
+ # => bar
191
+
192
+ # or...
193
+
194
+ Mine = my_injector
195
+ class Target
196
+ inject Mine # apply the injector
197
+ end
198
+
199
+ Target.new.bar
200
+ # => bar
201
+
202
+ # etc ...
203
+
204
+ **INJECTORS HAVE PROLONGATIONS:**
205
+
206
+ injector :my_injector
207
+
208
+ my_injector do # first prolongation
209
+
210
+ def another_method
211
+ end
212
+
213
+ end
214
+
215
+ # ...
216
+
217
+ my_injector do # another prolongation
218
+
219
+ def yet_another_method
220
+ end
221
+
222
+ end
223
+
224
+ #### #injector :sym
225
+ This is a global function. It defines an object of type Injector with the name of symbol. Use it when you want to generate an Injector object for later use. The symbol can then be used as a handle to the injector whenever you need to prolong the injector by adding methods to it or apply it to another object. Additionally, this symbol plays a role in defining the injector's scope. Injectors with capitalized names like :Function, :Style, etc have a global scope. That is they are available throughout the program:
226
+
227
+ class A
228
+ injector :Function
229
+ end
230
+
231
+ class B
232
+ include Function()
233
+ end
234
+
235
+ # This is perfectly valid with injectors
236
+
237
+ On the other hand Injectors with a lower case name are only available __from__ the scope in which they were defined, like the following example shows:
238
+
239
+ class AA
240
+ injector :form
241
+ end
242
+
243
+ class BB
244
+ include form # This genenerates and ERROR!
245
+ end
246
+
247
+ class BB
248
+ include AA.form
249
+ end
250
+
251
+ # This is perfectly valid with injectors
252
+
253
+ For all this to happen Jackbox also introduces some additional Ruby constructs, namely the keywords #inject and #enrich. These can be thought as simply new corollaries to #include and #extend. In fact they can be used interchangeably. If you're working with injectors you may want to use them instead depending on context to make clear your intent.
254
+
255
+ #### #include/inject *jack
256
+ This method is analogous to ruby's #include but its use is reserved for Injectors. The scope of this method is the same as the scope of #include, and its intended use like include's is for class definitions. Use it to "include" an Injector into a receiving class. Takes multiple injectors.
257
+
258
+ #### #extend/enrich *jack
259
+ This method in turn is analogous to ruby's #extend. The scope of this method is also the same as that of #extend, and its intended use if for object definition. Use it to extend the receiver of an injector. Takes multiple injectors.
260
+
261
+ **IMPORTANT NOTE: Injector lookup follows the method and not the constant lookup algorithm.**
262
+
263
+ If you need to follow constant lookup, here is the code for that:
264
+
265
+ Name = injector :sym .... # this also creates a hard tag (see below)
266
+
267
+ ### Injector Versioning
268
+
269
+ One of the most valuable properties of injectors is Injector Versioning. Versioning is the term used to identify a feature in the code that produces an artifact of injection which contains a certain set of methods with their associated outputs, and represents a snapshot of that injector up until the point it's applied to an object. From, that point on the object contains only that version of methods from that injector, and any subsequent overrides to those methods are only members of the "prolongation" of the injector and do not become part of the object of injection unless some form of re-injection occurs. Newer versions of an injector's methods only become part of newer objects or newer injections into existing targets. With Jackbox Injector Versioning two different versions of the same code object can be running simultaneously.
270
+
271
+ We'll use some examples to illustrate the point. This is how versioning occurs:
272
+
273
+ # injector declaration
274
+ #___________________
275
+ injector :my_injector do
276
+ def bar
277
+ :a_bar # version bar.1
278
+ end
279
+ def foo
280
+ # ...
281
+ end
282
+ end
283
+
284
+ object1.enrich my_injector # apply the injector --first snapshot
285
+ object1.bar.should == :a_bar # pass the test
286
+
287
+ # injector prolongation
288
+ #__________________
289
+ my_injector do
290
+ def bar
291
+ :some_larger_bar # version bar.2 ... re-defines bar
292
+ end
293
+ # ...
294
+ end
295
+
296
+ object2.enrich my_injector # apply the injector --second snapshot
297
+ object2.bar.should == :some_larger_bar
298
+
299
+ # result
300
+
301
+ object1.bar.should == :a_bar # bar.1 is still the one
302
+
303
+ ###############################################
304
+ # First object has kept its preferred version #
305
+ ###############################################
306
+
307
+
308
+ When re-injection occurs, and only then does the new version of the #bar method come into play. But the object remains unaffected otherwise, keeping its preferred version of methods. The new version is available for further injections down the line and to newer client code. Internal local-binding is preserved. If re-injection is executed then clients of the previous version get updated with the newer one. Here is the code:
309
+
310
+ # re-injection
311
+ #_________________
312
+ object1.enrich my_injector # re-injection --third snapshot
313
+
314
+ object1.bar.should == :some_larger_bar # bar.2 now available
315
+
316
+ ###############################################
317
+ # First object now has the updated version #
318
+ ###############################################
319
+
320
+
321
+ Re-injection on classes is a little bit trickier. Why? Because class injection should be more pervasive --we don't necessarily want to be redefining a class at every step. To re-inject a class we must use the Strategy Pattern (see below) or use a private update. See the sections below as well as the rspec files for more on this.
322
+
323
+ Here is an example of Injector Versioning as it pertains to classes:
324
+
325
+ # injector declaration:
326
+ #___________________
327
+ injector :Versions do
328
+ def meth arg # version meth.1
329
+ arg ** arg
330
+ end
331
+ end
332
+
333
+ class One
334
+ inject Versions() # apply --snapshot
335
+ end
336
+
337
+ # injector prolongation:
338
+ #_________________
339
+ Versions do
340
+ def meth arg1, arg2 # version meth.2 ... redefines meth.1
341
+ arg1 * arg2
342
+ end
343
+ end
344
+
345
+ class Two
346
+ inject Versions() # apply --snapshot
347
+ end
348
+
349
+ # result
350
+
351
+ Two.new.meth(2,4).should == 8 # meth.2
352
+ One.new.meth(3).should == 27 # meth.1
353
+
354
+ ##############################################
355
+ # Two different injector versions coexisting #
356
+ ##############################################
357
+
358
+ To update the class, we then do the following:
359
+
360
+ class One
361
+ update Versions() # private call to #update
362
+ end
363
+
364
+ One.new.meth(2,4).should == 8 # meth.2
365
+ Two.new.meth(2,4).should == 8 # meth.2
366
+
367
+ ##############################################
368
+ # class One is now updated to the latest #
369
+ ##############################################
370
+
371
+
372
+ ### Tagging/Naming
373
+
374
+ The use of Tags is central to the concept of Injector Versioning. Tagging happens in the following ways:
375
+
376
+ Version1 = jack :function do
377
+ def meth arg
378
+ arg
379
+ end
380
+ def mith
381
+ meth 2
382
+ end
383
+ end
384
+
385
+ Version2 = function do
386
+ def mith arg
387
+ meth(arg) * meth(arg)
388
+ end
389
+ end
390
+
391
+ Version1 and Version2 are two different hard versions/tags/names of the same Injector. There are also soft tags (see below).
392
+
393
+ ### Local Binding
394
+
395
+ Before we move on, we also want to give some further treatment to injector local-binding. That is, the binding of an injectors' methods is local to the prolongation/version in which they are located before the versioning occurs. Here, is the code:
396
+
397
+ **Note: In the following examples we use the notion of version naming/tagging. This allows you to tag different versions/prolongations of an Injector for later use. Once a version is tagged it shouldn't be modified**
398
+
399
+
400
+ # injector declaration
401
+ #_____________________
402
+
403
+ Version1 = injector :functionality do
404
+ def basic arg # version basic.1
405
+ arg * 2
406
+ end
407
+ end
408
+
409
+ o = Object.new.enrich Version1 # apply --snapshot (like above)
410
+ o.basic(1).should == 2 # basic.1
411
+
412
+ # injector prolongation
413
+ #_____________________
414
+
415
+ Version2 = functionality do
416
+ def basic arg # version basic.2
417
+ arg * 3 # specific use in compound.1
418
+ end
419
+
420
+ def compound # compound.1
421
+ basic(3) + 2
422
+ end
423
+ end
424
+
425
+ p = Object.new.enrich Version2 # apply --snapshot (like above)
426
+ p.basic(1).should == 3 # basic.2
427
+ p.compound.should == 11 # compound.1 --bound locally to basic.2
428
+
429
+ o.basic(1).should == 2 # basic.1
430
+ o.compound.should == 11 # compound.1 --bound locally to basic.2
431
+
432
+ ####################################################
433
+ # #compound.1 bound to the right version #basic.2 #
434
+ ####################################################
435
+
436
+
437
+ ### Method Virtual Cache
438
+
439
+ When you are working with an Injector in irb/pry it is often easier to just add methods to the injector without actually having to re-apply the injector to the the target to see the result. This is just what the Jackbox method virtual cache is for among other things. Here is what the code looks like:
440
+
441
+ # Facet definition
442
+ facet :SpecialMethods
443
+
444
+ class MyClass
445
+ include SpecialMethods
446
+ end
447
+
448
+ obj = MyClass.new
449
+
450
+ SpecialMethods do
451
+ def spm1 # spm1 is only defined in the virtual cache
452
+ :result # It is not actually part of the class yet!!
453
+ end # until this version/prolongation is applied
454
+ end
455
+
456
+ expect(obj.spm1).to eq(:result) # yet my obj can use it --no problem
457
+
458
+ The key idea here is that the method virtual cache is the same for all versions of the Injector and all its applications. If we redefine those methods they also get redefined for all versions. To actually lock the method versions you must apply the Injector.
459
+
460
+ #### #define\_method sym, &blk
461
+ There is one more interesting property to method definition on Injectors however. The use of #define\_method to re-define methods in any prolongation updates the entire injector and all its versions. This also preserves a fundamental tenet of injectors: take some local context, enclose it, and use the injector to introduce it to some indiscriminate target, and additionally has some other uses as we'll see with in our description of patterns and injector composition.
462
+
463
+ Here is an example of the difference with #define\_method:
464
+
465
+ facet :some_facet do
466
+ def meth
467
+ :meth
468
+ end
469
+
470
+ def foo_bar
471
+ 'a foo and a bar'
472
+ end
473
+ end
474
+
475
+ class Client ################################
476
+ inject some_facet # Injector appplied #
477
+ end # #
478
+ ################################
479
+ Client.new.meth.should == :meth
480
+ Client.new.foo_bar.should == 'a foo and a bar'
481
+
482
+
483
+ some_facet do
484
+ def meth # New version
485
+ puts :them
486
+ end
487
+
488
+ define_method :foo_bar do # New version
489
+ 'fooooo and barrrrr'
490
+ end
491
+ end
492
+ ################################
493
+ # Like above! #
494
+ Client.new.meth.should == :meth # No re-injection == No change #
495
+ ################################
496
+
497
+ ################################
498
+ Client.new.foo_bar.should == # Different!!! #
499
+ 'fooooo and barrrrr' # No re-injection == Change #
500
+ # . Thanks to define_method #
501
+ ################################
502
+
503
+ Injector Versioning together with injector local-binding allow the metamorphosis of injectors to fit the particular purpose at hand and keeping those local modifications isolated from the rest of your program making your code to naturally evolve with your program.
504
+
505
+ ### Injector introspection
506
+ Injectors have the ability to speak about themselves. Moreover injectors can speak about their members just like any module or class, and can also inject their receivers with these introspecting capabilities. Every injected/enriched object or module/class can enumerate its injectors, and injectors can enumerate their members, and so forth.
507
+
508
+ injector :Function do
509
+ def far
510
+ end
511
+ def close
512
+ end
513
+ end
514
+
515
+ injector :Style do
516
+ def pretty
517
+ end
518
+ end
519
+
520
+ class Target
521
+ inject Function(), Style()
522
+ end
523
+
524
+ # class ?
525
+
526
+ Function().class.should == Injector
527
+ Style().class.should == Injector
528
+
529
+ #### #injectors *sym
530
+ Called with no arguments returns a list of injectors. A call with a list of injector symbols however returns an array of actual Injector objects matching the names supplied in a LIFO fashion. An example use goes like this:
531
+
532
+ # injectors (in this target) ?
533
+
534
+ Target.injectors
535
+ => [(#944120:|Function|), (#942460:|Style|)]
536
+
537
+ # injectors :name ?
538
+
539
+ Target.injectors :Function
540
+ => [(#944120:|Function|)] # same as #injectors.collect_by_name :name
541
+
542
+ Target.injectors :all # all injectors in this class's hierarchy
543
+ (see section on Inheritance)
544
+
545
+ The method also extends into a minuscule API:
546
+
547
+ Target.injectors.by_name.should == [:Function, :Style]
548
+ # ...
549
+ Target.injectors(:all).by_name
550
+ # aliased to :sym_list
551
+
552
+ Target.injectors.collect_by_name :name # see above
553
+ # ...
554
+ Target.injectors(:all).collect_by_name :name
555
+ # aliased to :all_by_sym
556
+
557
+ Target.injectors.find_by_name :Function # last one in first out
558
+ => (#944120:|Function|)
559
+ # ...
560
+ Target.injectors(:all).find_by_name :name
561
+ # aliased to last_by_sym
562
+
563
+ Function().instance_methods.should == [:far, :close]
564
+ Style().instance_methods.should == [:pretty]
565
+
566
+ # later on...
567
+
568
+ # eject all injectors in target
569
+ Target.injectors.each{ |j| Target.eject j }
570
+
571
+ # or..
572
+
573
+ Target.eject *Target.injectors
574
+
575
+ #### #history alias #versions
576
+ This method returns a trace of all the target hosted Injectors which is ordered based on the order in which they are created. It includes tags and soft tags which can be specifically accessed thru the #tags method below. Here is the code:
577
+
578
+ # create our injector
579
+ injector :HistorySample
580
+
581
+ # host it a couple of times
582
+ extend( HistorySample(), HistorySample() )
583
+
584
+ # expect the following
585
+ expect(injectors).to eq(HistorySample().history)
586
+ expect(HistorySample().history.size).to eq(2)
587
+ expect(HistorySample().history.last).to eql(HistorySample())
588
+ expect(HistorySample().history.last).to_not eq(HistorySample().spec)
589
+
590
+ # create a tag
591
+ HistorySampleTag = HistorySample()
592
+
593
+ expect(HistorySample().history.size).to eq(3)
594
+ expect(HistorySample().history.last).to equal(HistorySampleTag)
595
+
596
+ #### #tags
597
+ This method traces the tags only. Here is the code:
598
+
599
+ # at this point from the above
600
+ expect(HistorySample().tags.size).to eq(1)
601
+
602
+ HistorySample(:tag) do
603
+ # some definitions
604
+ end
605
+
606
+ # expect the following
607
+ expect(HistorySample().tags.size).to eq(2)
608
+
609
+ Take a look at the Transformers Pattern below for an application of this and also the Jackbox blog at <a href="http://jackbox.us">http://jackbox.us</a>
610
+
611
+ #### #precedent and #progenitor (alias #pre, #pro)
612
+ The #pre method gets the previous element in the history. Here is the code:
613
+
614
+ # create the injector
615
+ injector :HistorySample
616
+
617
+ # create some history
618
+ extend HistorySample(), HistorySample()
619
+
620
+ # expect the following
621
+ expect(HistorySample().history.last.precedent).to equal(HistorySample().history.first)
622
+
623
+ The #pro method is a little different. It gets the version from which a particular injector was generated. This may not necessarily be the precedent. Take a look at the following code.
624
+
625
+ # create the injector
626
+ injector :Progample
627
+
628
+ # expect the following
629
+ expect(Progample().history).to be_empty
630
+ expect(Progample().progenitor).to equal(Progample().spec)
631
+
632
+ # create some history
633
+ extend Progample(), Progample()
634
+
635
+ # expect the following
636
+ expect(Progample().history.size).to eq(2)
637
+ expect(Progample().history.first.progenitor).to equal(Progample().spec)
638
+ expect(Progample().history.last.progenitor).to equal(Progample().spec)
639
+
640
+ For more on this see the rspec files.
641
+
642
+ ### Injector composition
643
+ The composition of multiple injectors into an object can be specified as follows:
644
+
645
+ include Injectors
646
+
647
+ # declare injectors
648
+ injector :FuelSystem # capitalized methods
649
+ injector :Engines
650
+ injector :Capsule
651
+ injector :Landing
652
+
653
+ # compose the object
654
+ class SpaceShip
655
+
656
+ inject FuelSystem(), Engines(), Capsule(), Langing() # capitalized method use
657
+
658
+ def launch
659
+ gas_tank fuel_lines burners ignition :go
660
+ self
661
+ end
662
+ end
663
+
664
+ Spaceship.injectors.by_name == [:FuelSystem, :Engines, :Capsule, :Landing]
665
+
666
+ # define functionality
667
+ FuelSystem do
668
+ def gas_tank arg
669
+ :gas
670
+ end
671
+ def fuel_lines arg
672
+ :fuel
673
+ end
674
+ def burners arg
675
+ :metal
676
+ end
677
+ end
678
+
679
+ # ...
680
+
681
+ # create object
682
+ flyer = SpaceShip.new.launch
683
+
684
+
685
+ # in-flight definitions, ha ha ha
686
+ var = 'wheels'
687
+
688
+ Landing do
689
+ define_method :gear do # a clolsure !!
690
+ var
691
+ end
692
+ end
693
+
694
+ ### Inheritance
695
+ The behavior of Injectors under inheritance is partially specified by what follows:
696
+
697
+ injector :j
698
+
699
+ class C
700
+ end
701
+ C.inject j { #foo pre-defined at time of injection
702
+ def foo
703
+ 'foo'
704
+ end
705
+ }
706
+ C.injectors.by_name.should == [:j]
707
+ C.new.injectors.by_name.should == [:j]
708
+
709
+ C.new.foo.should == 'foo'
710
+
711
+ # D inherits from C
712
+
713
+ class D < C # methods are inherited from j
714
+ end
715
+ D.injectors.by_name.should == []
716
+ D.injectors(:all).by_name == [:j]
717
+
718
+ # New Objects
719
+ C.new.foo.should == 'foo'
720
+ D.new.foo.should == 'foo'
721
+
722
+
723
+ More importantly though is the following:
724
+
725
+ facet :player do
726
+ def sound
727
+ 'Lets make some music'
728
+ end
729
+ end
730
+
731
+ TapePlayer = player do # version Tag
732
+ def play # inherirts :sound
733
+ return 'Tape playing...' + sound()
734
+ end
735
+ end
736
+
737
+ CDPlayer = player do # another version Tag
738
+ def play # also inherits sound
739
+ return 'CD playing...' + sound()
740
+ end
741
+ end
742
+
743
+ class BoomBox
744
+ include TapePlayer
745
+
746
+ def on
747
+ play
748
+ end
749
+ end
750
+
751
+ class JukeBox < BoomBox # regular class inheritance
752
+ inject CDPlayer
753
+ end
754
+
755
+ BoomBox.new.on.should == 'Tape playing...Lets make some music'
756
+ JukeBox.new.on.should == 'CD playing...Lets make some music'
757
+
758
+ jack :speakers
759
+
760
+ Bass = speakers do # adding composition
761
+ def sound
762
+ super + '...boom boom boom...'
763
+ end
764
+ end
765
+ JukeBox.inject Bass
766
+
767
+ JukeBox.new.on.should == 'CD playing...Lets make some music...boom boom boom...'
768
+
769
+ From all this, the important thing to take is that injectors provide a sort of versioned inheritance. The version inherits all of the pre-existing methods from the injector and freezes that function. We can either Tag/Name it of simply include/extend into a target but the function is frozen at that time. Tags cannot be modified or more clearly shouldn't be modified. Classes retain the frozen version of the injector until the time an update is made. Of course, there is always #define\_method. For more on all this see, the Rspec examples.
770
+
771
+
772
+ ---
773
+ But, this is the basic idea here. An extended closure which can be used as a mix-in, prolonged to add function, and versioned and renamed to fit the purpose at hand. Using this approach Jackbox also goes on to solve the Decorator Pattern problem in the Ruby language.
774
+
775
+ ---
776
+
777
+
778
+ ### The GOF Decorator Pattern:
779
+ Traditionally this is only partially solved in Ruby through PORO decorators or the use of modules. However, there are the problems of loss of class identity for the former and the limitations on the times it can be re-applied to the same object for the latter. With Jackbox this is solved. An injector used as a decorator does not confuse class identity for the receiver. Decorators are useful in several areas of OOP: presentation layers, stream processing, command processors to name a few.
780
+
781
+ Here is the code for that:
782
+
783
+ class Coffee
784
+ def cost
785
+ 1.50
786
+ end
787
+ end
788
+
789
+ injector :milk do
790
+ def cost
791
+ super() + 0.30
792
+ end
793
+ end
794
+ injector :vanilla do
795
+ def cost
796
+ super() + 0.15
797
+ end
798
+ end
799
+
800
+ cup = Coffee.new.enrich(milk).enrich(vanilla)
801
+ cup.should be_instance_of(Coffee)
802
+
803
+ cup.cost.should == 1.95
804
+
805
+
806
+ Furthermore, these same decorators can be then re-applied MULTIPLE TIMES to the same receiver. This is something that is normally not possible with the regular Ruby base language. Here are further examples:
807
+
808
+ cup = Coffee.new.enrich(milk).enrich(vanilla).enrich(vanilla)
809
+
810
+ # or even...
811
+
812
+ cup = Coffee.new.enrich milk, vanilla, vanilla
813
+
814
+ cup.cost.should == 2.10
815
+ cup.should be_instance_of(Coffee)
816
+ cup.injectors.should == [:milk, :vanilla, :vanilla]
817
+
818
+
819
+ ### Other Capabilities of Injectors
820
+
821
+ The functionality of Injectors can be removed from individual targets be them class targets or instance targets in various different ways. This allows for whole 'classes' of functionality to be removed and made un-available and then available again at whim and under programer control.
822
+
823
+ Here is an Injector removed after an #enrich to individual instance:
824
+
825
+ class Coffee
826
+ def cost
827
+ 1.00
828
+ end
829
+ end
830
+ injector :milk do
831
+ def cost
832
+ super() + 0.50
833
+ end
834
+ end
835
+
836
+ cup = Coffee.new.enrich(milk)
837
+ friends_cup = Coffee.new.enrich(milk)
838
+
839
+ cup.cost.should == 1.50
840
+ friends_cup.cost.should == 1.50
841
+
842
+ cup.eject :milk
843
+
844
+ cup.cost.should == 1.00
845
+
846
+ # friends cup didn't change price
847
+ friends_cup.cost.should == 1.50
848
+
849
+ Here it is removed after an #inject at the class level:
850
+
851
+ # create the injection
852
+ class Home
853
+ injector :layout do
854
+ def fractal
855
+ end
856
+ end
857
+ inject layout
858
+ end
859
+ expect{Home.new.fractal}.to_not raise_error
860
+
861
+ # build
862
+ my_home = Home.new
863
+ friends = Home.new
864
+
865
+ # eject the code
866
+ class Home
867
+ eject :layout
868
+ end
869
+
870
+ # the result
871
+ expect{my_home.fractal}.to raise_error
872
+ expect{friends.fractal}.to raise_error
873
+ expect{Home.new.fractal}.to raise_error
874
+
875
+
876
+ The code for these examples makes use of the #eject method which is also opens the door to some additional functionality provided by injectors. See the Strategy Pattern just below this.
877
+
878
+ #### #eject *sym
879
+ This method ejects injector function from a single object or class. It is in scope on any classes injected or enriched by an injector. For other forms of injector withdrawal see the next sections as in addition to this method, there are other ways to control code presence in targets through the use of Injector Directives. See below. For more on this also see the rspec examples.
880
+
881
+ ### Injector Equality and Difference
882
+
883
+ Injectors can be compared. This allows for further introspection capabilities which could be used to determine if a certain piece of code possesses a block of capabilities, test if those are equal to some other component's capabilities, or test what the difference is. It only follows that if injectors can be applied and withdrawn from any target we should be able to test for their similarities to other injectors. Here is how equality is defined:
884
+
885
+ # Equality
886
+
887
+ E().should == E()
888
+ E().should_not == E().spec
889
+
890
+ E(:tag).should == E()
891
+ ETag1 = E()
892
+ ETag1.should == E()
893
+
894
+ extend E()
895
+ injectors.first.should == E()
896
+
897
+ E() do
898
+ def foo # ** definition **
899
+ end
900
+ end
901
+
902
+ E().should == E()
903
+ ETag1.should_not == E()
904
+ injectors.first.should_not == E()
905
+ E(:tag).should == E()
906
+
907
+ E().should_not == F()
908
+
909
+ Here is how difference is defined:
910
+
911
+ # Difference
912
+
913
+ E().diff.should_not be_empty
914
+ # because
915
+ E().should_not == E().spec # like above
916
+
917
+
918
+ ##################################
919
+ E().diff.should_not be_loaded
920
+ # because
921
+ E().diff.join.should be_empty
922
+ E().diff.delta.should_not be_empty
923
+
924
+
925
+ ##################################
926
+ E().diff(E()).should be_empty
927
+ # because
928
+ E().should == E() # like above
929
+
930
+ ETag2 = E()
931
+
932
+
933
+ ##################################
934
+ E().diff(ETag2).should be_empty
935
+ ETag2.diff(E()).should be_empty
936
+ # because
937
+ ETag2.should == E() # like above
938
+
939
+
940
+ Again, for more on this see the rspec files.
941
+
942
+ ### Injector Directives
943
+ Once you have an injector handle you can also use it to issue directives to the injector. These directives can have a profound effect on your code.
944
+
945
+ #### :collapse directive
946
+ This description produces similar results to the one for injector ejection (see above) except that further injector method calls DO NOT raise an error. They just quietly return nil. Here are a couple of different cases:
947
+
948
+ The case with multiple objects
949
+
950
+ injector :copiable do
951
+ def object_copy
952
+ 'a dubious copy'
953
+ end
954
+ end
955
+
956
+ o1 = Object.new.enrich(copiable)
957
+ o2 = Object.new.enrich(copiable)
958
+
959
+ o1.object_copy.should == 'a dubious copy'
960
+ o2.object_copy.should == 'a dubious copy'
961
+
962
+ copiable :silence
963
+
964
+ o1.object_copy.should == nil
965
+ o2.object_copy.should == nil
966
+
967
+
968
+ The case with a class receiver:
969
+
970
+ class SomeClass
971
+ injector :code do
972
+ def tester
973
+ 'boo'
974
+ end
975
+ end
976
+
977
+ inject code
978
+ end
979
+
980
+ a = SomeClass.new
981
+ b = SomeClass.new
982
+
983
+ # collapse
984
+ SomeClass.code :collapse
985
+
986
+ a.tester.should == nil
987
+ b.tester.should == nil
988
+
989
+ # further
990
+ SomeClass.eject :code
991
+ expect{ a.tester }.to raise_error
992
+ expect{ b.tester }.to raise_error
993
+
994
+
995
+
996
+ #### :rebuild directive
997
+ Injectors that have been collapsed can at a later point then be reconstituted. Here are a couple of cases:
998
+
999
+ The case with multiple object receivers:
1000
+
1001
+ injector :reenforcer do
1002
+ def thick_walls
1003
+ '=====|||====='
1004
+ end
1005
+ end
1006
+
1007
+ o1 = Object.new.enrich(reenforcer)
1008
+ o2 = Object.new.enrich(reenforcer)
1009
+
1010
+ reenforcer :collapse
1011
+
1012
+ o1.thick_walls.should == nil
1013
+ o2.thick_walls.should == nil
1014
+
1015
+ reenforcer :rebuild
1016
+
1017
+ o1.thick_walls.should == '=====|||====='
1018
+ o2.thick_walls.should == '=====|||====='
1019
+
1020
+
1021
+ The case with a class receiver:
1022
+
1023
+ class SomeBloatedObject
1024
+ injector :ThinFunction do
1025
+ def perform
1026
+ 'do the deed'
1027
+ end
1028
+ end
1029
+ inject ThinFunction()
1030
+ end
1031
+ SomeBloatedObject.ThinFunction :silence # alias to :collapse
1032
+
1033
+ tester = SomeBloatedObject.new
1034
+ tester.perform.should == nil
1035
+
1036
+ SomeBloatedObject.ThinFunction :active # alias to :rebuild
1037
+ tester.perform.should == 'do the deed'
1038
+
1039
+
1040
+ #### :implode directive
1041
+ This directive totally destroys the injector including the handle to it. Use it carefully!
1042
+
1043
+ class Model
1044
+ def feature
1045
+ 'a standard feature'
1046
+ end
1047
+ end
1048
+
1049
+ injector :extras do
1050
+ def feature
1051
+ super() + ' plus some extras'
1052
+ end
1053
+ end
1054
+
1055
+ car = Model.new.enrich(extras)
1056
+ car.feature.should == 'a standard feature plus some extras'
1057
+
1058
+ extras :implode
1059
+
1060
+ # total implosion
1061
+ car.feature.should == 'a standard feature'
1062
+
1063
+ expect{extras}.to raise_error(NameError, /extras/)
1064
+ expect{ new_car = Model.new.enrich(extras) }.to raise_error(NameError, /extras/)
1065
+ expect{
1066
+ extras do
1067
+ def foo
1068
+ end
1069
+ end
1070
+ }.to raise_error(NameError, /extras/)
1071
+
1072
+ ### The GOF Strategy Pattern:
1073
+ Another pattern that Jackbox helps with is the GOF Strategy Pattern. This is a pattern with changes the guts of an object as opposed to just changing its face. Traditional examples of this pattern use PORO component injection within constructors.
1074
+
1075
+ Here are a couple alternate implementations:
1076
+
1077
+ class Coffee
1078
+ attr_reader :strategy
1079
+
1080
+ def initialize
1081
+ @strategy = nil
1082
+ end
1083
+ def cost
1084
+ 1.00
1085
+ end
1086
+ def brew
1087
+ @strategy = 'normal'
1088
+ end
1089
+ end
1090
+
1091
+ cup = Coffee.new
1092
+ cup.brew
1093
+ cup.strategy.should == 'normal'
1094
+
1095
+
1096
+ injector :sweedish do
1097
+ def brew
1098
+ @strategy = 'sweedish'
1099
+ end
1100
+ end
1101
+
1102
+ cup = Coffee.new.enrich(sweedish) # clobbers original strategy for this instance only!!
1103
+ cup.brew
1104
+ cup.strategy.should == ('sweedish')
1105
+
1106
+
1107
+ But, with #eject it is possible to have an even more general alternate implementation. This time we completely replace the current strategy by actually ejecting it out of the class and then injecting a new one:
1108
+
1109
+ class Tea < Coffee # Tea is a type of coffee!! ;~Q)
1110
+ injector :SpecialStrategy do
1111
+ def brew
1112
+ @strategy = 'special'
1113
+ end
1114
+ end
1115
+ inject SpecialStrategy()
1116
+ end
1117
+
1118
+ cup = Tea.new
1119
+ cup.brew
1120
+ cup.strategy.should == 'special'
1121
+
1122
+ Tea.eject :SpecialStrategy
1123
+
1124
+ Tea.inject sweedish
1125
+
1126
+ cup.brew
1127
+ cup.strategy.should == 'sweedish'
1128
+
1129
+ ### Soft Tags
1130
+ Just like hard tags above but a name is not needed:
1131
+
1132
+ jack :SomeJack do
1133
+ def foo
1134
+ :foo
1135
+ end
1136
+ end
1137
+
1138
+ SomeJack(:tag) do # New Version, not named
1139
+ def foo
1140
+ :foooooooo
1141
+ end
1142
+ end
1143
+
1144
+ ---
1145
+ ### Patterns of a Different Flavor
1146
+
1147
+ There are also some additional coding patterns possible with Jackbox Injectors. Although not part of the traditional GOF set these new patterns are only possible now thanks to languages like Ruby that permit the morphing of traditional forms into newer constructs. Here are some new patterns:
1148
+
1149
+ __1) Late Decorator.-__ Another flow that also benefits from #define\_method in an interesting way is the following:
1150
+
1151
+ class Widget
1152
+ def cost
1153
+ 1
1154
+ end
1155
+ end
1156
+ w = Widget.new
1157
+
1158
+ injector :decorator
1159
+
1160
+ w.enrich decorator, decorator, decorator, decorator
1161
+
1162
+ # user input
1163
+ bid = 3.5
1164
+
1165
+ decorator do
1166
+ define_method :cost do # defines function on all injectors of the class
1167
+ super() + bid
1168
+ end
1169
+ end
1170
+
1171
+ w.cost.should == 15
1172
+
1173
+ The actual injector function is late bound and defined only after some other data is available.
1174
+
1175
+ __2) The Super Pattern.-__ No. This is not a superlative kind of pattern. Simply, the use of #super can be harnessed into a pattern of controlled recursion, like in the following example:
1176
+
1177
+ facet :Superb
1178
+
1179
+ Superb do
1180
+ def process string, additives, index
1181
+ str = string.gsub('o', additives.slice!(index))
1182
+ super(string, additives, index) + str rescue str
1183
+ end
1184
+ extend Superb(), Superb(), Superb()
1185
+ end
1186
+
1187
+ Superb().process( 'food ', 'aeiu', 0 ).should == 'fuud fiid feed faad '
1188
+ Superb(:implode)
1189
+
1190
+ __3) The Transformer Pattern.-__ For a specific example of what can be accomplished using this workflow please refer to the rspec directory under the transformers spec. Here is the basic flow:
1191
+
1192
+ jack :Solution
1193
+
1194
+ Solution( :tag ) do
1195
+ def solution
1196
+ 1
1197
+ end
1198
+ end
1199
+ Solution( :tag ) do
1200
+ def solution
1201
+ 2
1202
+ end
1203
+ end
1204
+ Solution( :tag ) do
1205
+ def solution
1206
+ 3
1207
+ end
1208
+ end
1209
+
1210
+
1211
+ class Client
1212
+ inject Solution()
1213
+
1214
+ def self.solve
1215
+ Solution().tags.each { |e|
1216
+ update e
1217
+ puts new.solution rescue nil
1218
+ }
1219
+
1220
+ # or...
1221
+
1222
+ solutions = Solution().tags.each
1223
+ begin
1224
+ update solutions.next
1225
+ puts solved = new().solution()
1226
+ end until solved
1227
+ solved
1228
+ end
1229
+
1230
+ end
1231
+
1232
+ Client.solve
1233
+
1234
+ __4) The Re-Classing Pattern.-__ Our base method #lets has one more interesting use which allows for an alternative way to refine classes. We have termed this Re-Classing. Look at the following code:
1235
+
1236
+ # Injector declaration
1237
+
1238
+ SR1 = jack :StringRefinements do
1239
+ lets String do
1240
+ with singleton_class do
1241
+ alias _new new
1242
+ def new *args, &code
1243
+ super(*args, &code) + ' is a special string'
1244
+ end
1245
+ end
1246
+ end
1247
+ end
1248
+
1249
+ class OurClass
1250
+ include SR1
1251
+
1252
+ def foo_bar
1253
+ String('foo and bar')
1254
+ end
1255
+ end
1256
+
1257
+ c = OurClass.new
1258
+ c.foo_bar.class.should == String
1259
+ c.foo_bar.should == 'foo and bar is a special string'
1260
+
1261
+ SR2 = StringRefinements do # New Version
1262
+ lets String do
1263
+ def to_s
1264
+ super + '****'
1265
+ end
1266
+ end
1267
+ end
1268
+
1269
+ # c is still the same
1270
+
1271
+ c.foo_bar.should == 'foo and bar is a special string'
1272
+ c.foo_bar.class.should == String
1273
+
1274
+
1275
+ class OurOtherClass
1276
+ include SR2 # Apply new version
1277
+ # to another class
1278
+ def foo_bar
1279
+ String('foo and bar')
1280
+ end
1281
+ end
1282
+
1283
+ d = OurOtherClass.new
1284
+ d.foo_bar.should == 'foo and bar'
1285
+ d.foo_bar.to_s.should == 'foo and bar****'
1286
+
1287
+ The important thing to remember here is that #String() is a method now. We can redefine it, name-space it, test for its presence, etc. We can also use it to redefine the re-class's methods. For more on this see, the rspec files and the Jackbox blog at <a href="http://jackbox.us">http://jackbox.us</a>.
1288
+
1289
+ #### reclass? cls
1290
+
1291
+ This helper verifies a certain class re-classing exists within the current namespace. It returns a boolean. Ex:
1292
+
1293
+ module One
1294
+ if reclass? String
1295
+ String('our string')
1296
+ end
1297
+ end
1298
+
1299
+
1300
+ ---
1301
+ For more information and additional examples see the rspec examples on this project. There you'll find a long list of nearly __200__ rspec examples and code showcasing some additional features of Jackbox Injectors along with some additional descriptions.
1302
+
1303
+ ---
1304
+ ## Additional Tools
1305
+ Jackbox includes a couple of additional ancillary tools. The first is an Abstract class base that prevents instantiation of the base class itself but not of its descendants. The second is a persistent properties module named Prefs; it creates class/module/namespace level persistent properties.
1306
+
1307
+ With Abstract the code goes like this:
1308
+
1309
+ class Vector
1310
+ extend Abstract
1311
+ def speed
1312
+ 0
1313
+ end
1314
+ def direction
1315
+ end
1316
+ end
1317
+ expect{Vector.new}.to raise_error
1318
+
1319
+ class Velocity < Vector
1320
+ def speed
1321
+ super + 35
1322
+ end
1323
+ def direction
1324
+ :north
1325
+ end
1326
+ end
1327
+
1328
+ expect{Velocity.new}.to_not raise_error
1329
+ Velocity.new.speed.should == 35
1330
+
1331
+
1332
+ With Prefs you can add persistent properties to a class. These properties persist even through program termination. Here is the example code:
1333
+
1334
+ module Jester
1335
+ extend Prefs
1336
+
1337
+ pref :value => 10
1338
+ end
1339
+
1340
+ Jester.value.should == 10
1341
+ Jester.value = 3
1342
+ Jester.value.should == 3
1343
+ Jester.reset :value
1344
+ Jester.value.should == 10
1345
+
1346
+ There is also command line utility called **jackup** that simply allows users to bring their projects into a *"Jackbox level"*. It inserts the right references and turns the targeted project into a bundler gem if it isn't already one also adding a couple of rake tasks.
1347
+
1348
+ ## Availability
1349
+
1350
+ Jackbox is current available for Linux, Mac, and Windows versions of Ruby 1.9.3 thru 2.2.1
1351
+
1352
+ ## Installation
1353
+
1354
+ Add this line to your application's Gemfile:
1355
+
1356
+ gem 'jackbox'
1357
+
1358
+ And then execute:
1359
+
1360
+ $ bundle
1361
+
1362
+ Or install it yourself as:
1363
+
1364
+ $ gem install jackbox
1365
+
1366
+ And then execute the following command inside the project directory:
1367
+
1368
+ $jackup
1369
+
1370
+
1371
+
1372
+ ## Support
1373
+ Any questions/suggestions can be directed to the following email address:
1374
+
1375
+ __service.delivered@ymail.com__.
1376
+
1377
+ Please include your platform along with a description of the problem and any available stack trace. Please keep in mind that, at this time we have limited staff and we will do our best to have a quick response time.
1378
+
1379
+ Also please follow us at http://jackbox.us
1380
+
1381
+ ## Licensing
1382
+
1383
+ Jackbox single use and multi-use licenses are free.
1384
+ Copyright © 2014, 2015 LHA. All rights reserved.
1385
+
1386
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1387
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1388
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1389
+ NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
1390
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
1391
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1392
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1393
+
1394
+ In the above copyright notice, the letters LHA are the english acronym
1395
+ for Luis Enrique Alvarez (Barea) who is the author and owner of the copyright.