jackbox 0.9.6.6 → 0.9.6.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/LICENSE.lic +0 -0
- data/LICENSE.txt +7 -1
- data/README.md +509 -406
- data/Rakefile +5 -6
- data/Vagrantfile +77 -0
- data/bin/test-jacks +13 -0
- data/jackbox.gemspec +2 -2
- data/lib/jackbox.rb +1 -1
- data/lib/jackbox/examples/dir.rb +2 -7
- data/lib/jackbox/injectors.rb +1 -1
- data/lib/jackbox/injectors/coda.rb +1 -1
- data/lib/jackbox/injectors/prelude.rb +1 -1
- data/lib/jackbox/rake.rb +1 -1
- data/lib/jackbox/tools/prefs.rb +1 -1
- data/lib/jackbox/version.rb +1 -1
- data/spec/bin/jackup_cmd_shared.rb +2 -1
- data/spec/bin/jackup_cmd_spec.rb +4 -7
- data/spec/lib/jackbox/examples/dir_spec.rb +52 -17
- data/spec/lib/jackbox/injector_composition_spec.rb +554 -85
- data/spec/lib/jackbox/injector_inheritance_spec.rb +122 -75
- data/spec/lib/jackbox/injector_introspection_spec.rb +129 -187
- data/spec/lib/jackbox/injector_namespacing_spec.rb +19 -9
- data/spec/lib/jackbox/injector_spec.rb +2 -2
- data/spec/lib/jackbox/injector_versioning_spec.rb +37 -14
- data/spec/lib/jackbox/jiti_rules_spec.rb +536 -272
- data/spec/lib/jackbox/patterns_spec.rb +142 -105
- data/spec/lib/jackbox/prefs_spec.rb +50 -39
- data/spec/lib/jackbox/vmc_spec.rb +33 -28
- data/spec/lib/jackbox_spec.rb +39 -12
- data/spec/spec_helper.rb +33 -89
- data/work +8 -0
- metadata +26 -41
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NzlkNjhiN2MyYzI0YjM4NTIxOTczMmViYWY2ZGIyNzI1OWYyOTYwYg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NDAzMmMzOWY3MTcxMmY4ZGRmMTk1ZDRhNzMzNWI0YTIyMWI4Y2E1Yw==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MGM2ZWU1Y2E2MzRiYzk0Njc2ZDI4NzU0OTkzNDA1YjJiZDI1ZmFlODBmMTA0
|
10
|
+
YzVjODY2OTUyYWU5MGY1MjcyMzQzNGViNWQ2NjNmODAwODE2OTM1YTkwZGY4
|
11
|
+
NzAxZjA2MTUxODBhZWJkMTdiMmMwYmQ3MTc5ZjI5YTQxY2NmNzQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
Y2UxMWVjZDE1ODVmY2E4NDViYjEwZjkzNzlkZDMyOTFmNjgwZTI4Y2ZkYTBi
|
14
|
+
NmQ4ODc0YzgyMjA1MjZjOGYzNTdjYjdhNjY5MjU3Y2NhNmUxMzkzOWExODMy
|
15
|
+
ODdhNDI2YzEwOGJiNjk4ZGQxZTc5NzYxOTZjZjA2ZTk5OGJkNWQ=
|
data/LICENSE.lic
CHANGED
Binary file
|
data/LICENSE.txt
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
|
2
|
-
Copyright © 2014, 2015 LHA (Lou Henry Alvarez). All rights reserved.
|
2
|
+
Copyright © 2014, 2015, 2016, LHA (Lou Henry Alvarez). All rights reserved.
|
3
|
+
|
4
|
+
This means you cannot reproduce the whole or any part of the source code or
|
5
|
+
documentation at any time, or under any circumstances, without the express
|
6
|
+
permission of the author. Use of the binary encoded form of the software is
|
7
|
+
free of charge for a limited time, while the binary license and keys expire,
|
8
|
+
and is subject to the above Copyright notice and afore terms for reproduction.
|
3
9
|
|
4
10
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
5
11
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
data/README.md
CHANGED
@@ -18,24 +18,24 @@ Copyright © 2014, 2015 LHA. All rights reserved.
|
|
18
18
|
---
|
19
19
|
<h2 style="font-family:Impact">Modular Closures©, Ruby Traits©, Code Injectors, Class Constructors, and other Ruby programmer morphins</h2>
|
20
20
|
|
21
|
-
The defining thought behind Jackbox is a simple one: If Ruby is like Play-Doh, with Jackbox we
|
21
|
+
The defining thought behind Jackbox is a simple one: If Ruby is like Play-Doh, with Jackbox we aim to turn it into <a href="https://en.wikipedia.org/wiki/Plasticine">Plasticine</a>. The library functionality at this time takes this idea and materializes it in the concepts of trait injectors, class constructors, the application of versioning to objects, and a model of mix-in inheritance which includes a just-in-time inheritance form that together with the helper functions that bring them together, provide some new and interesting capabilities.
|
22
22
|
|
23
|
-
To make it easier to grasp, **Ruby Traits
|
23
|
+
To make it easier to grasp, **Ruby Traits©** and code injectors can perhaps be thought of as a form of **Modular Closures©** which is to say, closures which can also serve as modules. These modular closures most of all propose some additional 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 or the presence of trait injector code in targets with mechanisms involving trait injector canceling or ejection and directives to for example remain as silent traits, and to later force the reactivation of traits. They give your code the ability to capture the 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.
|
24
24
|
|
25
|
-
|
25
|
+
Following on this we 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 Trait/Injector Versioning below. Runtimes can morph their capabilities as they learn about themselves, and they can do so in blocks as granular or as coarse as needed. These blocks can be updated, ejected, silenced, or re-injected with more function. This versioning also provides a form of inheritance. We have called this versioned inheritance and it allows newer versions to inherit from previous ones, be tagged and labeled, and this way be capable of reuse. All this is further enhanced by the ability of Jackbox to resolve methods through the use of the VMC (Virtual Method Cache). See below.
|
26
26
|
|
27
|
-
|
27
|
+
**Class constructors** 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 trait injectors and helper functions, class constructors can be refined multiple times. Capabilities can be added and removed in blocks. Moreover, these constructors acquire introspecting abilities. A class constructor can be tested for existence, can tell you what traits it uses, and finally can be overridden with a more relevant one. Constructors also work with all Ruby versions including with Ruby 1.9 and related technologies.
|
28
28
|
|
29
29
|
Finally, we also present the concept of **Just-In-Time Inheritance©**. This is a feature which allows the production of an ancestor hierarchy similar to what you find in Ruby classes just as it is needed by your code. With it you can override previous members of a tag and expect to have access to its super members as part of the call, just like you would with classes. But, this inheritance is all going on in the mix-in --the Modular Closure. Families of traits can be built with the use of this and the previous versioned inheritance, and unlike class inheritance be readily applicable to any target.
|
30
30
|
|
31
|
-
We have chosen to keep the code obfuscated **for now** because we are a small company
|
31
|
+
We have chosen to keep the code obfuscated **for now** because we are a small company and we need to protect our germinating intellectual property. We take great pride in providing significant value at minimal cost. Our guiding principle through out it all has been keeping new constructs to a minimum. There are enough libraries out there which enhance the base Ruby language in many interesting ways but which also bring with them added complexity and a big learning curve. We on the other hand took an outer minimalistic approach requiring a lot more behind the scenes. Simplicity takes work. We hope that all this work is to your liking.
|
32
32
|
|
33
|
-
Advantages Of Trait Based Programming
|
33
|
+
Advantages Of Trait Injector Based Programming
|
34
34
|
------------------------------------
|
35
|
+
* Trait Injectors avoid the perils of monkey patching. You can just create a new version of the trait and leave the old one alone.
|
35
36
|
* Traits are inherited from their ancestors and can be mixed in with any target.
|
36
|
-
* With Traits you avoid the perils of monkey patching. You can just create a new version of the trait and leave the old one alone.
|
37
|
-
* Traits can be silenced and reactivated.
|
38
37
|
* With Traits runtime versioning is possible and traits can be upgraded with new versions of the trait.
|
38
|
+
* Traits can be injected, extended, cancelled, silenced, reinstated, tagged, versioned, and updated.
|
39
39
|
* Traits enable new and different coding patterns.
|
40
40
|
|
41
41
|
---
|
@@ -83,7 +83,7 @@ It also works like so:
|
|
83
83
|
|
84
84
|
|
85
85
|
#### #with obj, &blk
|
86
|
-
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
|
86
|
+
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 and works as a shortcut between contexts. Used in this fashion it can abstract some of the tediousness of an explicit self in some calls. The other thing about #with is that it allows you to directly place definitions on the object you pass in using its most natural form based on whether it's an instance of Object or Module. Then it returns the same object you passed into it after the block has done processing it. You can also pass multiple objects: #with a, b, c for example and the same block applies to all returning a, b, c afterwards. There are some other nuances to #with later on.
|
87
87
|
|
88
88
|
Here is some sample usage code:
|
89
89
|
|
@@ -144,8 +144,19 @@ Use it with **#decorate** on singleton classes like this:
|
|
144
144
|
|
145
145
|
|
146
146
|
#### #lets sym=nil, &blk
|
147
|
-
We could say, this is simple syntax sugar. It adds readability to some constructs, and it allows the creation of local or global procs using a more friendly syntax. But #lets, also opens the door to a new coding pattern using class constructors. 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 only in spirit,
|
147
|
+
We could say, this is simple syntax sugar. It adds readability to some constructs, and it allows the creation of local or global procs using a more friendly syntax. But #lets, also opens the door to a new coding pattern using class constructors. See below. The important thing about #lets is that it always defines some lambda/proc/method. It's use apparently differs from that of #define_method only in spirit, but its use with respect to class constructors shows another side to #lets. Aside from this however, #lets is mostly for one liners. Here are some examples:
|
148
148
|
|
149
|
+
As a shortcut for define_method. Use it for short functional definitions:
|
150
|
+
|
151
|
+
lets( :meth ){ |arg| arg * 2 } # read as: lets define symbol :meth to be ....
|
152
|
+
meth(3)
|
153
|
+
# => 6
|
154
|
+
|
155
|
+
Can be used to define a special values or pseudo-immutable strings:
|
156
|
+
|
157
|
+
lets(:foo){ 3+Math::Pi } # read as: lets set :foo to value
|
158
|
+
lets(:faa){ 'some important string' }
|
159
|
+
|
149
160
|
To define local functions/lambdas. Define symbols in local scope:
|
150
161
|
|
151
162
|
def main
|
@@ -157,42 +168,33 @@ To define local functions/lambdas. Define symbols in local scope:
|
|
157
168
|
#...
|
158
169
|
end
|
159
170
|
|
160
|
-
|
161
|
-
|
162
|
-
lets( :meth ){ |arg| arg * 2 } # read as: lets define symbol :meth to be ....
|
163
|
-
meth(3)
|
164
|
-
# => 6
|
165
|
-
|
166
|
-
Can be used to define a special values or pseudo-immutable strings:
|
171
|
+
Also see Class Constructors below.
|
167
172
|
|
168
|
-
lets(:foo){ 3+Math::Pi } # read as: lets set :foo to value
|
169
|
-
lets(:faa){ 'some important string' }
|
170
|
-
|
171
173
|
|
172
|
-
|
174
|
+
Trait Injectors
|
173
175
|
----------
|
174
|
-
|
176
|
+
Trait Injectors are the main tool in Jackbox at the time of this writing. These again are a form of mix-in that have 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 mix-in if you like. In the sections below we will discuss some of the methods available to you with Jackbox in connection with Trait Injectors, as well as elaborate on some of the other properties of traits. But, it is essential to understand there are some syntactical differences to Trait Injectors with respect to regular modules. We will show them first, with some examples:
|
175
177
|
|
176
|
-
**
|
178
|
+
**TRAIT INJECTORS ARE DECLARED IN THE FOLLOWING WAYS:**
|
177
179
|
|
178
180
|
|
179
|
-
|
181
|
+
injector :name
|
180
182
|
|
181
183
|
# or...
|
182
184
|
|
183
|
-
Name =
|
185
|
+
Name = injector :name
|
184
186
|
|
185
187
|
# or even ...
|
186
188
|
|
187
|
-
|
189
|
+
trait :Name # capitalized method, using alias #trait
|
188
190
|
|
189
191
|
|
190
|
-
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
|
192
|
+
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 also trait directives.
|
191
193
|
|
192
194
|
# somewhere in your code
|
193
|
-
include
|
195
|
+
include Injectors
|
194
196
|
|
195
|
-
|
197
|
+
injector :my_trait # define the injector
|
196
198
|
|
197
199
|
my_trait do
|
198
200
|
def bar
|
@@ -201,7 +203,7 @@ Their use and semantics are somewhat defined by the following snippet. But, to
|
|
201
203
|
end
|
202
204
|
|
203
205
|
# later on...
|
204
|
-
widget.extend my_trait # apply the
|
206
|
+
widget.extend my_trait # apply the injector
|
205
207
|
|
206
208
|
widget.bar
|
207
209
|
# => bar
|
@@ -210,16 +212,16 @@ Their use and semantics are somewhat defined by the following snippet. But, to
|
|
210
212
|
|
211
213
|
Mine = my_trait
|
212
214
|
class Target
|
213
|
-
inject Mine # apply the
|
215
|
+
inject Mine # apply the injector
|
214
216
|
end
|
215
217
|
|
216
218
|
Target.new.bar
|
217
219
|
# => bar
|
218
220
|
|
219
221
|
|
220
|
-
**
|
222
|
+
**TRAIT INJECTORS HAVE PROLONGATIONS:**
|
221
223
|
|
222
|
-
|
224
|
+
injector :my_trait
|
223
225
|
|
224
226
|
my_trait do # first prolongation
|
225
227
|
|
@@ -237,22 +239,22 @@ Their use and semantics are somewhat defined by the following snippet. But, to
|
|
237
239
|
|
238
240
|
end
|
239
241
|
|
240
|
-
|
242
|
+
Prolongations become versions once applied or tagged. See Tagging/Naming below. But, in lieu of this, they remain in the Virtual Method Cache (see below) in an un-versioned state and are available to any client.
|
241
243
|
|
242
244
|
#### #trait/#injector :sym
|
243
|
-
This is a global function. It defines an object of type
|
245
|
+
This is a global function. It defines an object of type Injector with the name of :symbol. Use it when you want to generate a Trait Injector object for later use. The symbol can then be used as a handle to the trait whenever you need to prolong the trait by adding methods to it, or to apply it to a target generating a version. Additionally, this symbol plays a role in defining the trait's scope. Injectors with capitalized names like :Function, :Style, etc have a global scope. That is, they are available throughout the program, regardless of where they are defined. Here is the code:
|
244
246
|
|
245
247
|
class A
|
246
|
-
|
248
|
+
injector :Function # defined
|
247
249
|
end
|
248
250
|
|
249
251
|
class B
|
250
252
|
include Function() # applied
|
251
253
|
end
|
252
254
|
|
253
|
-
# This is perfectly valid with
|
255
|
+
# This is perfectly valid with trait injectors
|
254
256
|
|
255
|
-
On the other hand Traits
|
257
|
+
On the other hand Traits Injectors with a lower case name are only available __from__ the scope in which they were defined, like the following example shows:
|
256
258
|
|
257
259
|
class A
|
258
260
|
trait :form
|
@@ -267,13 +269,13 @@ On the other hand Traits/Injectors with a lower case name are only available __f
|
|
267
269
|
end
|
268
270
|
|
269
271
|
|
270
|
-
For all this to happen Jackbox also introduces some additional
|
272
|
+
For all this to happen Jackbox also introduces some additional 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 traits you may want to use them instead, depending on context, to make clear your intent. Also #inject is public on classes (not on other traits) while #include is not.
|
271
273
|
|
272
274
|
#### #include/inject *t
|
273
|
-
This method is analogous to ruby's #include but its use is reserved for Trait Injectors. The scope of this method is the same as the scope of #include, and its intended use like that of #include is for class definitions. Use it to "include" a trait into a receiving class. Also takes multiple traits.
|
275
|
+
This method is analogous to ruby's #include but its use is reserved for Trait Injectors. The scope of this method is the same as the scope of #include, and its intended use like that of #include is for class definitions. Use it to "include" a trait into a receiving class. Also takes multiple traits. The order of precedence is like that of ruby's #include from left to right.
|
274
276
|
|
275
277
|
#### #extend/enrich *t
|
276
|
-
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
|
278
|
+
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 is for object definition. Use it to "extend" the receiver of a trait. Also takes multiple traits with the same order of precedence.
|
277
279
|
|
278
280
|
**IMPORTANT NOTE: Trait Injector lookup follows the method and not the constant lookup algorithm.**
|
279
281
|
|
@@ -281,15 +283,15 @@ If you need to follow constant lookup, here is the code for that:
|
|
281
283
|
|
282
284
|
Name = trait :sym .... # this also creates a hard tag (see below)
|
283
285
|
|
284
|
-
### Trait
|
286
|
+
### Trait Injector Versioning
|
285
287
|
|
286
|
-
One of the most valuable properties of Jackbox is Trait Injector Versioning. Versioning is the term used to identify a feature in the code that produces an artifact which contains a certain set of methods and associated outputs, and which represents a snapshot of that trait up until the point it's applied to an object. From, that point on the object contains only that version of
|
288
|
+
One of the most valuable properties of Jackbox is Trait Injector Versioning. Versioning is the term used to identify a feature in the code that produces an artifact which contains a certain set of methods and associated outputs, and which represents a snapshot of that trait up until the point it's applied to an object. From, that point on the object contains only that version of injector methods, and any subsequent overrides to those methods on the trait injector are only members of the "prolongation" of that trait and do not become part of previous targets unless some form of trait re-injection occurs. Newer versions of a trait only become part of newer targets or newer trait injections into existing targets. With Jackbox Injector Versioning, two different versions of the same code object can be running simultaneously.
|
287
289
|
|
288
290
|
We'll use some examples to illustrate the point. This is how versioning occurs:
|
289
291
|
|
290
|
-
# trait declaration
|
292
|
+
# trait injector declaration
|
291
293
|
#___________________
|
292
|
-
|
294
|
+
injector :my_trait do
|
293
295
|
def bar
|
294
296
|
:a_bar # version bar.1
|
295
297
|
end
|
@@ -298,10 +300,15 @@ We'll use some examples to illustrate the point. This is how versioning occurs:
|
|
298
300
|
end
|
299
301
|
end
|
300
302
|
|
303
|
+
###############################################
|
304
|
+
# First object has a preferred version #
|
305
|
+
###############################################
|
306
|
+
|
301
307
|
object1.extend my_trait # apply the trait --first snapshot
|
302
308
|
object1.bar.should == :a_bar # pass the test
|
303
309
|
|
304
|
-
|
310
|
+
|
311
|
+
# trait injector prolongation
|
305
312
|
#__________________
|
306
313
|
my_trait do
|
307
314
|
def bar
|
@@ -310,9 +317,14 @@ We'll use some examples to illustrate the point. This is how versioning occurs:
|
|
310
317
|
# ...
|
311
318
|
end
|
312
319
|
|
320
|
+
###############################################
|
321
|
+
# Second object has now its preferred version #
|
322
|
+
###############################################
|
323
|
+
|
313
324
|
object2.extend my_trait # apply the trait --second snapshot
|
314
325
|
object2.bar.should == :some_larger_bar # pass the test
|
315
326
|
|
327
|
+
|
316
328
|
###############################################
|
317
329
|
# First object has kept its preferred version #
|
318
330
|
###############################################
|
@@ -332,13 +344,11 @@ When trait re-injection occurs, and only then does the new version of the #bar m
|
|
332
344
|
|
333
345
|
object1.bar.should == :some_larger_bar # bar.2 now available
|
334
346
|
|
335
|
-
Re-injection on classes is a little bit trickier, because class injection is more pervasive. To re-inject a class with a trait we must use the Strategy Pattern (see below) or use private #update's.
|
347
|
+
Re-injection on classes is a little bit trickier, because class injection is more pervasive. To re-inject a class with a new trait we must use the Strategy Pattern (see below or the rspec files) or we have to use private #update's. Here is an example of Injector Versioning as it pertains to classes:
|
336
348
|
|
337
|
-
|
338
|
-
|
339
|
-
# trait declaration:
|
349
|
+
# trait injector declaration:
|
340
350
|
#___________________
|
341
|
-
|
351
|
+
injector :Versions do
|
342
352
|
def meth arg # version meth.1
|
343
353
|
arg ** arg
|
344
354
|
end
|
@@ -348,7 +358,8 @@ Here is an example of Injector Versioning as it pertains to classes:
|
|
348
358
|
inject Versions() # apply --snapshot
|
349
359
|
end
|
350
360
|
|
351
|
-
|
361
|
+
|
362
|
+
# trait injector prolongation:
|
352
363
|
#_________________
|
353
364
|
Versions do
|
354
365
|
def meth arg1, arg2 # version meth.2 ... redefines meth.1
|
@@ -384,7 +395,7 @@ To update the class, we then do the following:
|
|
384
395
|
|
385
396
|
### Tagging/Naming
|
386
397
|
|
387
|
-
The use of Tags is central to the concept of Versioning. Tagging happens in the following ways:
|
398
|
+
The use of Tags is central to the concept of Versioning. In a similar fashion to a version control system tags play a role in formalizing a set of changes, but unlike its more mundane sibling these changes represent actual object function and methods. Once a prolongation is tagged the it is always available in the same state and can be depended upon by any object needing that version of the functionality. More importantly once a tag is defined it should not be changed, and Jackbox goes to some lengths to prevent you from doing this. Of course this is Ruby out very own intent is to move from Play-doh to Plasticine. Tagging happens in the following ways:
|
388
399
|
|
389
400
|
Version1 = trait :function do
|
390
401
|
def meth arg
|
@@ -401,13 +412,13 @@ The use of Tags is central to the concept of Versioning. Tagging happens in the
|
|
401
412
|
end
|
402
413
|
end
|
403
414
|
|
404
|
-
Version1 and Version2 are two different hard tags of the same Trait Injector. They introduce a more formal approach to trait versioning and also pave the way for the inheritance models described in the introduction. Aside from hard tags, there are also soft tags (see below).
|
415
|
+
Version1 and Version2 are two different hard tags of the same Trait Injector. They introduce a more formal approach to trait versioning and also pave the way for the two different inheritance models described in the introduction. Aside from hard tags, there are also soft tags (see below).
|
405
416
|
|
406
417
|
### Local Binding
|
407
418
|
|
408
|
-
Before we move on, we also want to give some further treatment to
|
419
|
+
Before we move on, we also want to give some further treatment to injector local-binding. That is, the binding of a traits' methods is local to the prolongation/version in which they are located before the versioning occurs. Here, is what we mean by that:
|
409
420
|
|
410
|
-
**Note: In the following examples we use the notion of version naming/tagging. Once a version is tagged it shouldn't be modified. These tags comprise entities along the hierarchical structure of a trait.**
|
421
|
+
**Note: In the following examples we use the notion of version naming/tagging. Once a version is tagged it shouldn't be modified. These tags comprise entities along the hierarchical structure of a trait explained further below.**
|
411
422
|
|
412
423
|
|
413
424
|
# trait declaration
|
@@ -420,6 +431,7 @@ Before we move on, we also want to give some further treatment to trait local-bi
|
|
420
431
|
end
|
421
432
|
o = Object.new.extend Version1 # apply --snapshot (like above)
|
422
433
|
|
434
|
+
|
423
435
|
|
424
436
|
# trait prolongation
|
425
437
|
#_____________________
|
@@ -434,6 +446,7 @@ Before we move on, we also want to give some further treatment to trait local-bi
|
|
434
446
|
end
|
435
447
|
end
|
436
448
|
p = Object.new.extend Version2 # apply --snapshot (like above)
|
449
|
+
|
437
450
|
|
438
451
|
####################################################
|
439
452
|
# #compound.1 bound to the right version #basic.2 #
|
@@ -446,13 +459,15 @@ Before we move on, we also want to give some further treatment to trait local-bi
|
|
446
459
|
o.compound.should == 11 # compound.1 --bound locally to basic.2
|
447
460
|
|
448
461
|
|
462
|
+
Versioning together with local-binding allow the metamorphosis of traits to fit the particular purpose at hand, keeping those local modifications isolated from the rest of your program, and allowing your code to naturally evolve with your program. They cancel the need to monkey patch anything. If you need a local version of some code just open up a prolongation create the new version, inject it in to your targets, and leave the older versions and clients untouched.
|
463
|
+
|
449
464
|
### Virtual Method Cache (VMC)
|
450
465
|
|
451
|
-
When you are working with a trait injector in irb/pry it is often easier to just add methods to the trait without actually having to re-apply the trait to the the target to see the result. This is just what the Virtual Method Cache is for **among other things.** The VMC allows working with traits like you would with regular modules. It also enhances the normal method resolution of modules into that of modules in the chain but not directly applied to the target. Here is what the
|
466
|
+
When you are working with a trait injector in irb/pry it is often easier to just add methods to the trait without actually having to re-apply the trait to the the target to see the result. This is just what the Virtual Method Cache is for **among other things.** The VMC allows working with traits like you would with regular modules. It also enhances the normal method resolution of modules into that of modules in the chain but who are not directly applied to the target. Here is what the VMC looks like:
|
452
467
|
|
453
468
|
# definition
|
454
469
|
#_______________
|
455
|
-
|
470
|
+
injector :SomeTrait
|
456
471
|
|
457
472
|
# application
|
458
473
|
#_______________
|
@@ -463,14 +478,14 @@ When you are working with a trait injector in irb/pry it is often easier to just
|
|
463
478
|
obj = MyClass.new
|
464
479
|
|
465
480
|
SomeMethods do
|
466
|
-
def spm1 # #spm1 is only defined in the
|
467
|
-
:result # It is not actually part of the
|
481
|
+
def spm1 # #spm1 is only defined in the Virtual Method Cache
|
482
|
+
:result # It is not actually part of the MyClass yet!!
|
468
483
|
end # until this version/prolongation is applied
|
469
484
|
end
|
470
485
|
|
471
486
|
expect(obj.spm1).to eq(:result) # yet my obj can use it --no problem
|
472
487
|
|
473
|
-
The key idea here is that the
|
488
|
+
The key idea here is that the Virtual Method Cache is the same for all versions of the Injector and all its applications. This is what allows working with traits as if they were regular modules. If we redefine VMC methods they are also redefined for all versions. To actually lock the method versions into place you must apply the Injector with the methods defined in it that you want the version to have. To then change that application of the trait you then re-inject the target. But the VMC, provides a scratch pad of methods for you to work with. The VMC also provides extended method resolution to the injector. To understand what we mean by this, take a look at following code:
|
474
489
|
|
475
490
|
class Client
|
476
491
|
include trait :J1
|
@@ -494,16 +509,16 @@ The key idea here is that the virtual method cache is the same for all versions
|
|
494
509
|
Client.new.n2m1
|
495
510
|
Client.new.n3m1
|
496
511
|
|
497
|
-
Think of how this would be different with regular modules. For this to happen using regular Ruby modules K1 and L1 should have to be defined and included prior to their inclusion into our client. And no it is not just a matter of moving the include to the beginning of each container.
|
512
|
+
Think of how this would be different with regular modules. For this to happen using regular Ruby modules, K1 and L1 should have to be defined and included prior to their inclusion into our client. And no it is not just a matter of moving the include to the beginning of each container.
|
498
513
|
|
499
514
|
#### #define\_method sym, &blk
|
500
|
-
There is one more interesting property to method definition with Trait Injectors however. The use of #define\_method to define/re-define methods in any prolongation affects the entire trait and all its versions. This also preserves a fundamental tenet of traits: take some local context, enclose it, and use the trait to introduce it to some indiscriminate target, and additionally has some other uses as we'll see with in our description of patterns and trait composition.
|
515
|
+
There is one more interesting property to method definition with Trait Injectors however. The use of #define\_method to define/re-define methods in any prolongation affects the entire trait and all its versions except for any hard tagged as these are exactly that hard. This also preserves a fundamental tenet of traits: take some local context, enclose it, and use the trait to introduce it to some indiscriminate target, and additionally has some other uses as we'll see with in our description of Jackbox patterns and trait composition.
|
501
516
|
|
502
517
|
Here is an example of the difference with #define\_method:
|
503
518
|
|
504
519
|
# define trait
|
505
520
|
#_________________________
|
506
|
-
|
521
|
+
injector :some_trait do
|
507
522
|
def meth
|
508
523
|
:meth
|
509
524
|
end
|
@@ -549,13 +564,12 @@ Here is an example of the difference with #define\_method:
|
|
549
564
|
|
550
565
|
Client.new.foo_bar.should == 'fooooo and barrrrr'
|
551
566
|
|
567
|
+
This also allows the introduction of what we have termed a new coding pattern based on the late binding of decorators (see below).
|
552
568
|
|
553
|
-
|
554
|
-
|
555
|
-
### Trait/Injector introspection
|
569
|
+
### Trait Injector introspection
|
556
570
|
Trait Injectors have the ability to speak about themselves. Moreover traits can speak about their members just like any module or class, and can also inject their receivers with introspecting capabilities. Every injected/enriched object or module/class can enumerate its traits, and traits can enumerate their members, and so forth.
|
557
571
|
|
558
|
-
|
572
|
+
injector :Function do
|
559
573
|
def far
|
560
574
|
end
|
561
575
|
def close
|
@@ -589,10 +603,10 @@ Trait Injectors have the ability to speak about themselves. Moreover traits can
|
|
589
603
|
|
590
604
|
# later on...
|
591
605
|
|
592
|
-
Child.
|
606
|
+
Child.cancel *Child.traits
|
593
607
|
|
594
|
-
#### #traits *sym
|
595
|
-
Called with no arguments returns a list of traits. A call with a list of trait symbols however returns an array of actual Trait Injector objects matching the names supplied in a LIFO fashion. The method also extends into a
|
608
|
+
#### #traits/#injectors *sym
|
609
|
+
Called with no arguments returns a list of traits. A call with a list of trait symbols however returns an array of actual Trait Injector objects matching the names supplied in a LIFO fashion. The method also extends into a micro API. An example use goes like this:
|
596
610
|
|
597
611
|
#traits --(in this target)
|
598
612
|
|
@@ -648,7 +662,7 @@ Called with no arguments returns a list of traits. A call with a list of trait
|
|
648
662
|
# aliased to last_by_sym
|
649
663
|
|
650
664
|
#### #history alias #versions
|
651
|
-
This method returns a trace of all the
|
665
|
+
This method returns a trace of all the applied trait injector versions ordered based on the order in which they are created. It also includes the version hard tags and soft tags which can also be specifically accessed through the #tags method below. It is primarily a view of all existing versions of a trait. Here is the code:
|
652
666
|
|
653
667
|
# create our trait and
|
654
668
|
# host it a couple of times
|
@@ -680,7 +694,7 @@ This method returns a trace of all the hosted Trait Injectors which is ordered b
|
|
680
694
|
expect(HistorySample().history.last).to equal(HistorySample())
|
681
695
|
|
682
696
|
#### #tags
|
683
|
-
This method traces
|
697
|
+
This method traces tags only. It also extends into a micro API returning hard and soft tags independently. Here is the code:
|
684
698
|
|
685
699
|
# at this point from the above...
|
686
700
|
|
@@ -700,7 +714,7 @@ This method traces the tags only. The method also extends into a sub-mini API r
|
|
700
714
|
HistorySample().tags.hard
|
701
715
|
=> [(|HistorySample|:#234435),(|HistorySample|:#876679)]
|
702
716
|
|
703
|
-
|
717
|
+
Hard tags are an aid to inheritance while soft tags help with composition. For more on this take a look at the Solutions Pattern below for an application of soft tags and at JITI (Just-In-Time Inheritance) for and example of hard tags and its connection to inheritance. See also the Jackbox blog at <a href="http://jackbox.us">http://jackbox.us</a> and the rspec files for the project.
|
704
718
|
|
705
719
|
#### #precedent and #progenitor (alias #pre, #pro)
|
706
720
|
The #pre method gets the previous element in the history. Here is the code:
|
@@ -713,43 +727,218 @@ The #pre method gets the previous element in the history. Here is the code:
|
|
713
727
|
|
714
728
|
# expect the following
|
715
729
|
expect(HistorySample().history.last.precedent).to equal(HistorySample().history.first)
|
716
|
-
|
717
|
-
|
730
|
+
|
731
|
+
For more on this see the rspec files.
|
718
732
|
|
719
|
-
|
720
|
-
trait :Progample
|
721
|
-
|
722
|
-
# expect the following
|
723
|
-
expect(Progample().history).to be_empty
|
724
|
-
expect(Progample().precedent).to equal(Progample().spec)
|
725
|
-
expect(Progample().progenitor).to equal(Progample().spec)
|
726
|
-
|
727
|
-
# create some history
|
728
|
-
extend Progample(), Progample()
|
733
|
+
### Other Capabilities of Trait Injectors
|
729
734
|
|
730
|
-
|
731
|
-
expect(Progample().history.size).to eq(2)
|
732
|
-
expect(Progample().history.first.progenitor).to equal(Progample().spec)
|
733
|
-
expect(Progample().history.last.pro).to equal(Progample().spec)
|
734
|
-
expect(Progample().history.last.pre).to equal(Progample().history.first)
|
735
|
-
expect(Progample().history.first).to_not equal(Progample().spec)
|
736
|
-
|
737
|
-
Furthermore:
|
735
|
+
The functionality of Trait Injectors can be removed from individual targets, whether class or instance targets, in various different ways. This allows for whole 'classes' of functionality to be removed or withdrawn and then made available again at whim and under programer control. The first method of injector withdrawal is trait canceling or ejection. This is where a trait is completely removed from a target precipitating further calls on the trait to generate an error. Second there is trait silencing and reactivation. This on the other hand allows for the temporary quieting of a trait but which may need to be reactivated at a later time. Trait canceling or ejection can take place at the instance or the class level.
|
738
736
|
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
737
|
+
Here we have a Trait Injector removed after an #enrich to individual instance:
|
738
|
+
|
739
|
+
class Coffee
|
740
|
+
def cost
|
741
|
+
1.00
|
742
|
+
end
|
743
|
+
end
|
744
|
+
trait :milk do
|
745
|
+
def cost
|
746
|
+
super() + 0.50
|
747
|
+
end
|
744
748
|
end
|
745
749
|
|
746
|
-
|
747
|
-
|
748
|
-
|
750
|
+
cup = Coffee.new.enrich(milk)
|
751
|
+
friends_cup = Coffee.new.enrich(milk)
|
752
|
+
|
753
|
+
cup.cost.should == 1.50
|
754
|
+
friends_cup.cost.should == 1.50
|
755
|
+
|
756
|
+
cup.cancel :milk
|
757
|
+
|
758
|
+
cup.cost.should == 1.00
|
759
|
+
|
760
|
+
# friends cup didn't change price
|
761
|
+
friends_cup.cost.should == 1.50
|
762
|
+
|
763
|
+
Here it is removed after an #inject at the class level:
|
764
|
+
|
765
|
+
# create the injection
|
766
|
+
class Home
|
767
|
+
trait :layout do
|
768
|
+
def fractal
|
769
|
+
end
|
770
|
+
end
|
771
|
+
inject layout
|
772
|
+
end
|
773
|
+
expect{Home.new.fractal}.to_not raise_error
|
774
|
+
|
775
|
+
# build
|
776
|
+
my_home = Home.new
|
777
|
+
friends = Home.new
|
778
|
+
|
779
|
+
# eject the code
|
780
|
+
class Home
|
781
|
+
eject :layout
|
782
|
+
end
|
783
|
+
|
784
|
+
# the result
|
785
|
+
expect{my_home.fractal}.to raise_error
|
786
|
+
expect{friends.fractal}.to raise_error
|
787
|
+
expect{Home.new.fractal}.to raise_error
|
788
|
+
|
789
|
+
|
790
|
+
The code for these examples makes use of the #cancel alias #eject method which opens the door to this additional functionality provided by traits. See also the Strategy Pattern just below this. It is important to keep in mind that ejection is "permanent" (not really, can always be re-injected) and that this permanence is more of its intent. 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.
|
791
|
+
|
792
|
+
#### #cancel *sym (alias #eject)
|
793
|
+
This method cancels or ejects trait function from a single object or class. It is in scope on any classes injected or enriched with a trait. Its effect is that of completely removing one of our modular closures from the ancestor chain. Once this is done method calls on the trait will raise an error.
|
794
|
+
|
795
|
+
### Injector Directives
|
796
|
+
Once you have a trait handle you can also use it to issue directives to the trait. These directives can have a profound effect on your code. There are directives to silence a trait, to reactivate it, to create a soft tag, or to completely obliterate the trait including the handle to it.
|
797
|
+
|
798
|
+
#### :silence/:collapse directive
|
799
|
+
This description produces similar results to the one for trait ejection (see above) except that further trait method calls DO NOT raise an error. They just quietly return nil. Here are a couple of different cases:
|
800
|
+
|
801
|
+
The case with multiple object instances:
|
802
|
+
|
803
|
+
trait :copiable do
|
804
|
+
def object_copy
|
805
|
+
'a dubious copy'
|
806
|
+
end
|
807
|
+
end
|
808
|
+
|
809
|
+
o1 = Object.new.enrich(copiable)
|
810
|
+
o2 = Object.new.enrich(copiable)
|
811
|
+
|
812
|
+
o1.object_copy.should == 'a dubious copy'
|
813
|
+
o2.object_copy.should == 'a dubious copy'
|
814
|
+
|
815
|
+
copiable :silence
|
816
|
+
|
817
|
+
o1.object_copy.should == nil
|
818
|
+
o2.object_copy.should == nil
|
819
|
+
|
820
|
+
|
821
|
+
The case with a class receiver:
|
822
|
+
|
823
|
+
class SomeClass
|
824
|
+
trait :code do
|
825
|
+
def tester
|
826
|
+
'boo'
|
827
|
+
end
|
828
|
+
end
|
829
|
+
|
830
|
+
inject code
|
831
|
+
end
|
832
|
+
|
833
|
+
a = SomeClass.new
|
834
|
+
b = SomeClass.new
|
835
|
+
|
836
|
+
# collapse
|
837
|
+
SomeClass.code :collapse
|
838
|
+
|
839
|
+
a.tester.should == nil
|
840
|
+
b.tester.should == nil
|
841
|
+
|
842
|
+
|
843
|
+
#### :active/:rebuild directive
|
844
|
+
Trait Injectors that have been silenced or collapsed can at a later point be reactivated. Here are a couple of cases:
|
845
|
+
|
846
|
+
The case with multiple object receivers:
|
847
|
+
|
848
|
+
trait :reenforcer do
|
849
|
+
def thick_walls
|
850
|
+
'=====|||====='
|
851
|
+
end
|
852
|
+
end
|
853
|
+
|
854
|
+
o1 = Object.new.enrich(reenforcer)
|
855
|
+
o2 = Object.new.enrich(reenforcer)
|
856
|
+
|
857
|
+
reenforcer :collapse
|
858
|
+
|
859
|
+
o1.thick_walls.should == nil
|
860
|
+
o2.thick_walls.should == nil
|
861
|
+
|
862
|
+
reenforcer :rebuild
|
863
|
+
|
864
|
+
o1.thick_walls.should == '=====|||====='
|
865
|
+
o2.thick_walls.should == '=====|||====='
|
866
|
+
|
867
|
+
|
868
|
+
The case with a class receiver:
|
869
|
+
|
870
|
+
class SomeBloatedObject
|
871
|
+
trait :ThinFunction do
|
872
|
+
def perform
|
873
|
+
'do the deed'
|
874
|
+
end
|
875
|
+
end
|
876
|
+
inject ThinFunction()
|
877
|
+
end
|
878
|
+
SomeBloatedObject.ThinFunction :silence # alias to :collapse
|
879
|
+
|
880
|
+
tester = SomeBloatedObject.new
|
881
|
+
tester.perform.should == nil
|
882
|
+
|
883
|
+
SomeBloatedObject.ThinFunction :active # alias to :rebuild
|
884
|
+
tester.perform.should == 'do the deed'
|
885
|
+
|
886
|
+
#### :tag/:version directive
|
887
|
+
This directive creates a soft tagged version of a trait. For more on this see Soft Tags below.
|
888
|
+
|
889
|
+
#### :implode directive
|
890
|
+
This directive totally destroys the trait including the handle to it. Use it carefully!
|
891
|
+
|
892
|
+
class Model
|
893
|
+
def feature
|
894
|
+
'a standard feature'
|
895
|
+
end
|
896
|
+
end
|
897
|
+
|
898
|
+
trait :extras do
|
899
|
+
def feature
|
900
|
+
super() + ' plus some extras'
|
901
|
+
end
|
902
|
+
end
|
903
|
+
|
904
|
+
car = Model.new.enrich(extras)
|
905
|
+
car.feature.should == 'a standard feature plus some extras'
|
906
|
+
|
907
|
+
extras :implode
|
908
|
+
|
909
|
+
# total implosion
|
910
|
+
car.feature.should == 'a standard feature'
|
911
|
+
|
912
|
+
expect{extras}.to raise_error(NameError, /extras/)
|
913
|
+
expect{ new_car = Model.new.enrich(extras) }.to raise_error(NameError, /extras/)
|
914
|
+
expect{
|
915
|
+
extras do
|
916
|
+
def foo
|
917
|
+
end
|
918
|
+
end
|
919
|
+
}.to raise_error(NameError, /extras/)
|
920
|
+
|
921
|
+
### Soft Tags
|
922
|
+
Just like hard tags above but a name is not needed. They come about by the :tag directive from above and are intended as markers for versions of algorithms that need to be accessed at a later time on demand. For more on this see the Solutions Pattern below.
|
923
|
+
|
924
|
+
jack :SomeJack do
|
925
|
+
def foo
|
926
|
+
:foo
|
927
|
+
end
|
928
|
+
end
|
929
|
+
SomeJack :tag # Unnamed version
|
930
|
+
|
931
|
+
SomeJack(:tag) do # New unnamed version
|
932
|
+
def foo
|
933
|
+
:foooooooo
|
934
|
+
end
|
935
|
+
end
|
749
936
|
|
750
|
-
|
937
|
+
Accessible through trait#tags (an Array). Also available **trait#tags.hard** and **trait#tags.soft**. See introspection above.
|
938
|
+
|
939
|
+
### Trait Injector Equality and Difference
|
751
940
|
|
752
|
-
Injectors can be compared. This allows further introspection capabilities which can be used to determine if a certain piece of code possesses a certain block of capabilities, test if those are equal to some other component's capabilities, or determine what the difference is. This is similar to what you would find with simple modules, but capabilities are compared on a method basis. It only follows that if traits can be applied and withdrawn from any target we should be able to test for their similarities to other traits. Injector difference
|
941
|
+
Injectors can be compared. This allows further introspection capabilities which can be used to determine if a certain piece of code possesses a certain block of capabilities, test if those are "equal" to some other component's capabilities in terms of duck types, or determine what the difference is. This is similar to what you would find with simple modules, but capabilities are compared on a method basis. It only follows that if traits can be applied and withdrawn from any target we should be able to test for their similarities to other traits. Injector difference is the complement to all this, and it finds the actual delta between traits and returns those differences.
|
753
942
|
|
754
943
|
Here is how equality is defined:
|
755
944
|
|
@@ -777,7 +966,7 @@ Here is how equality is defined:
|
|
777
966
|
|
778
967
|
|
779
968
|
|
780
|
-
Inequality is based on a trait's methods. Once you add method definitions to a trait, that trait tests as inequality to
|
969
|
+
Inequality is based on a trait injector's methods. Once you add method definitions to a trait, that trait tests as an inequality to its precedent provided this is not the original (#spec) injector. This original trait is also the #pre and #pro to all others. It always tests as equal to its handle, but versions past or since do not. A different injector with the same methods is also not equal to the trait.
|
781
970
|
|
782
971
|
Here is how inequality is defined:
|
783
972
|
|
@@ -805,7 +994,7 @@ Here is how inequality is defined:
|
|
805
994
|
E().should == E() # always
|
806
995
|
E().should == E().spec
|
807
996
|
|
808
|
-
Difference is deeper than simple inequality. It returns the actual delta between what you have and what you pass in to the call as an array of two elements. The first element is the methods common to both operands, the second is the delta from the first to the second.
|
997
|
+
Difference is deeper than simple inequality. It returns the actual delta between what you have and what you pass in to the call as an array of two elements. The first element is comprised of the methods common to both operands, the second is the delta from the first to the second. This method also exposes a micro API. Furthermore, the elements of the array which are special arrays themselves also return a partial trait with their actual injector payload which can be used in further trait injection. Here is how difference is defined:
|
809
998
|
|
810
999
|
#### #trait.diff ver=nil
|
811
1000
|
|
@@ -882,54 +1071,52 @@ Difference is deeper than simple inequality. It returns the actual delta betwee
|
|
882
1071
|
#diff.join.injector
|
883
1072
|
#diff.delta.injector
|
884
1073
|
|
885
|
-
|
886
|
-
|
1074
|
+
# a tag as precedent
|
1075
|
+
ETag5 = E()
|
887
1076
|
|
888
1077
|
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
1078
|
+
# if E() definitions **
|
1079
|
+
E do
|
1080
|
+
def foo
|
1081
|
+
:foo
|
1082
|
+
end
|
1083
|
+
def bar
|
1084
|
+
:bar
|
1085
|
+
end
|
1086
|
+
end
|
898
1087
|
|
899
1088
|
|
900
|
-
|
901
|
-
|
1089
|
+
# then
|
1090
|
+
E().diff.should_not be_empty
|
902
1091
|
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
1092
|
+
# being that
|
1093
|
+
E().diff.join.should be_empty
|
1094
|
+
E().diff.delta.should_not be_empty
|
1095
|
+
# as for
|
1096
|
+
E().diff.delta.injector.instance_methods.should == [:foo, :bar]
|
1097
|
+
# and
|
1098
|
+
E().diff.delta.injector.should_not eq(E().diff.join.injector)
|
910
1099
|
|
911
|
-
|
912
|
-
|
913
|
-
|
1100
|
+
# being that
|
1101
|
+
E().diff.join.injector.instance_methods.should be_empty
|
1102
|
+
E().diff.delta.injector.instance_methods.should_not be_empty
|
914
1103
|
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
1104
|
+
# allows the following
|
1105
|
+
class Incomplete
|
1106
|
+
inject E().diff.delta.injector
|
1107
|
+
end
|
1108
|
+
# and
|
1109
|
+
Incomplete.new.foo.should eq(:foo)
|
921
1110
|
|
922
|
-
|
923
|
-
|
924
|
-
|
1111
|
+
# being that
|
1112
|
+
E().diff.delta.injector.should be_instance_of(Injector)
|
1113
|
+
E().diff.delta.injector.should be_instance_of(Trait)
|
925
1114
|
|
926
1115
|
|
927
1116
|
|
928
|
-
The version argument can
|
929
|
-
|
930
|
-
Again, for more on this see the rspec files.
|
1117
|
+
The version argument can be negative index (-1, etc), or another version. By default, it uses the previous version. NOTE: the previous version of an un-altered trait is equal to the trait. Again, for more on this see the rspec files.
|
931
1118
|
|
932
|
-
### Trait
|
1119
|
+
### Trait Injector composition
|
933
1120
|
The composition of multiple traits into an object can be specified as follows:
|
934
1121
|
|
935
1122
|
include Injectors
|
@@ -981,14 +1168,72 @@ The composition of multiple traits into an object can be specified as follows:
|
|
981
1168
|
end
|
982
1169
|
end
|
983
1170
|
|
984
|
-
One thing to note is the difference between defining function through the VMC, which allows working with traits already applied to targets and predefining function of a trait before application and versioning. The first allows a flow similar to that of regular modules and the later makes use of the true nature of
|
1171
|
+
For full examples see the rspec code. One thing to note is the difference between defining function through the VMC, which allows working with traits already applied to targets and predefining function of a trait before application and versioning. The first allows a flow similar to that of regular modules and the later makes use of the true nature of trait injectors and allows customizing trait injectors to their targets.
|
1172
|
+
|
1173
|
+
### Unbound Dependency (Soft Ancestors)
|
1174
|
+
|
1175
|
+
Another important feature of trait injectors is a property of injectors to combine with other traits in multiple ways (with different function or precedence) without actually permanently binding to any of their ancestors or their function. This on-the-fly soft binding allows for multiple combinations of form and function from the same basic components. This is another area where Jackbox mix-ins go beyond what is possible with regular modules. In the following example we combine the same injectors in this (soft) way to derive different function.
|
1176
|
+
|
1177
|
+
#####
|
1178
|
+
# injector declarations
|
1179
|
+
|
1180
|
+
injector :drive do
|
1181
|
+
def drive_result
|
1182
|
+
payload * 2
|
1183
|
+
end
|
1184
|
+
end
|
1185
|
+
|
1186
|
+
trait :strings do
|
1187
|
+
def payload
|
1188
|
+
'+++++'
|
1189
|
+
end
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
trait :numbers do
|
1193
|
+
def payload
|
1194
|
+
10
|
1195
|
+
end
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
#####
|
1199
|
+
# soft ancestry declarations
|
1200
|
+
|
1201
|
+
Result1 = drive strings
|
1202
|
+
Result2 = drive numbers
|
1203
|
+
|
1204
|
+
|
1205
|
+
class Driven
|
1206
|
+
include Result1
|
1207
|
+
end
|
1208
|
+
|
1209
|
+
Driven.new.drive_result.should == '++++++++++'
|
1210
|
+
|
1211
|
+
|
1212
|
+
class Driven
|
1213
|
+
update Result2
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
Driven.new.drive_result.should == 20
|
1217
|
+
|
1218
|
+
|
1219
|
+
#################################
|
1220
|
+
# remain unbound to ancestry
|
1221
|
+
#################################
|
1222
|
+
|
1223
|
+
drive.ancestors.should == [drive]
|
1224
|
+
|
1225
|
+
#################################
|
1226
|
+
# ----------amazing-------------
|
1227
|
+
#################################
|
1228
|
+
|
1229
|
+
Granted this is a somewhat contrived example but you get the idea. Think again how is this different than what is available from modules. With modules once one module is included in another that relationship is permanent. With Jackbox trait injectors and soft ancestors the same components can be reused in multiple bindings tying their function locally only but remaining globally decoupled.
|
985
1230
|
|
986
1231
|
### Inheritance
|
987
|
-
Inheritance with
|
1232
|
+
Inheritance with trait injectors comes in two flavors. The first form comes from the normal versioning of a trait. The second comes from JITI which follows a model similar to what you find in regular classes. With versioning a trait injector inherits all the function from its progenitor allowing customization of only the parts needed for the specific application at hand but cannot call upon previous versions of itself. With JITI this later dimension of access to its own ancestry is possible.
|
988
1233
|
|
989
|
-
But before this, the behavior of Trait Injectors under normal class inheritance is also of interest. Trait injectors act upon a class and its children. Introspection on traits under class inheritance is
|
1234
|
+
But before we get into all this, the behavior of Trait Injectors under normal class inheritance is also of interest. Trait injectors act upon a class and its children. Introspection on traits under class inheritance is possible and is achieved with the :all directive on the #traits/#injectors method. The behavior of trait injectors under normal class inheritance is partially specified by what follows:
|
990
1235
|
|
991
|
-
|
1236
|
+
injector :j
|
992
1237
|
|
993
1238
|
class C
|
994
1239
|
end
|
@@ -1014,9 +1259,9 @@ But before this, the behavior of Trait Injectors under normal class inheritance
|
|
1014
1259
|
C.new.foo.should == 'foo'
|
1015
1260
|
D.new.foo.should == 'foo'
|
1016
1261
|
|
1017
|
-
For
|
1262
|
+
For more on this also see the rspec files.
|
1018
1263
|
|
1019
|
-
More importantly though is the following example of trait inheritance due to versioning.
|
1264
|
+
More importantly though is the inheritance native to injectors. The following example touches on the first form of native trait injector inheritance, the inheritance due to versioning. Here we have an example of how inheritance due to versioning can be harnessed to promote the re-use of common elements. Like previously stated, the concept of tagging/naming also plays an important role with inheritance as illustrated in the following code:
|
1020
1265
|
|
1021
1266
|
trait :player do
|
1022
1267
|
def sound
|
@@ -1044,21 +1289,21 @@ More importantly though is the following example of trait inheritance due to ver
|
|
1044
1289
|
end
|
1045
1290
|
end
|
1046
1291
|
|
1047
|
-
class JukeBox < BoomBox
|
1048
|
-
inject CDPlayer
|
1292
|
+
class JukeBox < BoomBox
|
1293
|
+
inject CDPlayer # Mixed class and injector inheritance models
|
1049
1294
|
end
|
1050
1295
|
|
1051
1296
|
|
1052
|
-
The different versions inherit all of the pre-existing methods from the
|
1297
|
+
The different versions inherit all of the pre-existing methods from the previous one and "freeze" that function. We can either Tag/Name them for later use or simply include/extend then into a new target, but the function is fixed at that time. Tags cannot be modified or more clearly shouldn't be modified. Classes retain the fixed version of the trait until the time an update is made. But, trait injectors then also inherit new methods through the VMC (see above). For more on all this see, the Rspec examples.
|
1053
1298
|
|
1054
1299
|
### Just-In-Time Inheritance (JITI)
|
1055
1300
|
|
1056
|
-
This flavor of the inheritance model allows our modular closures to have similar properties to the inheritance of classes. With it you can expect to have access to the trait's super/ancestor members as part of the call, just like you would with
|
1301
|
+
This flavor of the inheritance model on the other hand allows our modular closures to have similar properties to the inheritance of classes. With it you can expect to have access to the trait's super/ancestor members as part of the call, just like you would with class inheritance. In addition to the inheritance resulting from versioning, JITI presents a more complete scenario adding color to the inheritance picture painted by trait injectors with one caveat, we must be aware of colluding ancestry when creating decorators based on JITI. This very narrow case is explained in the specs and Jackbox itself warns you of this however. The key takeaway here is: trait injectors are a form of mix-in that share a similar inheritance model with classes, but all happening in the mix-in however. You can version them to gain access to versioned inheritance or you can tag and then override its members to access an ancestor chain comprised of all previous tags. As always we will use some example code to illustrate:
|
1057
1302
|
|
1058
1303
|
#
|
1059
|
-
# Our Trait
|
1304
|
+
# Our Trait Injector
|
1060
1305
|
#
|
1061
|
-
Tag1 =
|
1306
|
+
Tag1 = injector :Functionality do
|
1062
1307
|
def m1
|
1063
1308
|
1
|
1064
1309
|
end
|
@@ -1076,18 +1321,15 @@ This flavor of the inheritance model allows our modular closures to have similar
|
|
1076
1321
|
'other' # -- same ancestors as before
|
1077
1322
|
end
|
1078
1323
|
end
|
1079
|
-
|
1080
1324
|
expect(Functionality().ancestors).to eql( [Functionality()] )
|
1081
1325
|
|
1082
|
-
|
1326
|
+
#####
|
1327
|
+
# test function
|
1083
1328
|
|
1084
1329
|
o = Object.new.extend(Functionality())
|
1085
1330
|
|
1086
|
-
# inherited
|
1087
1331
|
o.m1.should == 1
|
1088
1332
|
o.m2.should == :m2
|
1089
|
-
|
1090
|
-
# current
|
1091
1333
|
o.other.should == 'other'
|
1092
1334
|
|
1093
1335
|
|
@@ -1096,7 +1338,7 @@ This flavor of the inheritance model allows our modular closures to have similar
|
|
1096
1338
|
#
|
1097
1339
|
Tag2 = Functionality do
|
1098
1340
|
def m1 # The :m1 override invokes JIT Inheritance
|
1099
|
-
super +
|
1341
|
+
super + 2 # -- Tag1 is summoned into ancestor chain
|
1100
1342
|
end # -- allows the use of super
|
1101
1343
|
|
1102
1344
|
def m3
|
@@ -1104,12 +1346,13 @@ This flavor of the inheritance model allows our modular closures to have similar
|
|
1104
1346
|
end
|
1105
1347
|
end
|
1106
1348
|
|
1349
|
+
#####
|
1107
1350
|
# test it
|
1108
1351
|
|
1109
1352
|
p = Object.new.extend(Tag2)
|
1110
1353
|
|
1111
1354
|
# JIT inherited
|
1112
|
-
p.m1.should ==
|
1355
|
+
p.m1.should == 3
|
1113
1356
|
|
1114
1357
|
# regular inheritance
|
1115
1358
|
p.m2.should == :m2
|
@@ -1123,24 +1366,20 @@ This flavor of the inheritance model allows our modular closures to have similar
|
|
1123
1366
|
|
1124
1367
|
JITI (Just-In-Time Inheritance) is governed by a set of rules framing its behavior. Here are these rules and their descriptions:
|
1125
1368
|
|
1126
|
-
1. JITI works like class inheritance but
|
1127
|
-
2. The trait handle is always in sync with the last hard tag
|
1128
|
-
3. It allows initial external basing and also external base substitution. A trait can be based on an external trait or even module serving as a shell or casing for external function as long as any internal definitions don't overwrite the external ones.
|
1129
|
-
4.
|
1130
|
-
5. It
|
1131
|
-
6.
|
1369
|
+
1. JITI works like class inheritance but at the mix-in. It holds onto method definitions of earlier version hard tags. It lets you override or rebase (start fresh) individual methods at any level. It works for object extension. It works for class inclusion.
|
1370
|
+
2. The trait injector handle (its access method) is always in sync with the last hard tag. This also means the injector definitions use the last hard tag as a departing base for any further changes.
|
1371
|
+
3. It allows initial external basing and also external base substitution. A trait injector can be based on an external trait or even module serving as a shell or casing for external function as long as any internal definitions don't overwrite the external ones.
|
1372
|
+
4. It forces internal basing once instated. Definitions internal to the trait injector always take precedence over external definitions by the same signature. This blocks external ancestor intrusion enforcing internal injector consistency.
|
1373
|
+
5. It works with the VMC. Like all trait injectors, the VMC is always available, acting as a cache of methods available globally to all versions of the injector and which fills in those version methods from the main injector onto its descendants, supplementing this inheritance models as well.
|
1374
|
+
6. It works with directives. Like all trait injectors, JITI trait injectors respond to normal injector directives. Here they work to cancel and reinstate entire trait hierarchies.
|
1132
1375
|
|
1133
|
-
For more on this please see the rspec files in the project, or
|
1134
|
-
|
1135
|
-
But, this is the basic idea here. Traits are an extended closure which can be used as a mix-in, prolonged to add function, and shaped, versioned, tagged, and inherited to fit the purpose at hand. With Traits however you avoid the perils of monkey patching. You can just create a new version of the trait and leave the old one alone. Combining work flows of both modules and traits is also possible through the use of the VMC and the special version of #define_method. Moreover, using traits Jackbox also goes on to solve some traditional shortcomings of Ruby with some GOF(Gang of Four) object patterns.
|
1376
|
+
For more on all of this please see the rspec files in the GitHub project, or visit our blog at http://jackbox.us. But, this is the basic idea here. Trait injectors are an extended modular closure which can be used as a mix-in and prolonged to add function, shaped, versioned, tagged, and overridden to fit the purpose at hand. With trait injectors however you avoid the perils of monkey patching. You can just create a new version of the injector and leave the old one alone. They allow you to combine work flows from both modules and traits through the use of the VMC. They provide inheritance in enhanced forms and a compositional model that loosely couples member traits. Finally, they work under code directives. Moreover, using trait injectors Jackbox also goes on to solve some traditional shortcomings of Ruby with some of the GOF(Gang of Four) object oriented patterns.
|
1136
1377
|
|
1137
1378
|
---
|
1138
1379
|
|
1139
1380
|
|
1140
1381
|
### The GOF Decorator Pattern:
|
1141
|
-
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. A trait used as a decorator does not confuse class identity for the receiver. Decorators are useful in several areas of OOP: presentation layers, string processing, command processors to name a few.
|
1142
|
-
|
1143
|
-
Here is the code:
|
1382
|
+
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. Here are a couple of articles on the matter: <a href="https://robots.thoughtbot.com/evaluating-alternative-decorator-implementations-in">Evaluating Alternative Decorator Implementations</a> and <a href="http://nithinbekal.com/posts/ruby-decorators/">Decorator Pattern in Ruby</a> With Jackbox however all of this is solved. A trait injector used as a decorator does not confuse class identity for the receiver. Decorators are useful in several areas of OOP: presentation layers, string processing, command processors to name a few. Here is the code:
|
1144
1383
|
|
1145
1384
|
class Coffee
|
1146
1385
|
def cost
|
@@ -1182,194 +1421,6 @@ Additionally, these same decorators can then be re-applied MULTIPLE TIMES to the
|
|
1182
1421
|
# and other markup in html or...
|
1183
1422
|
|
1184
1423
|
|
1185
|
-
### Other Capabilities of Trait Injectors
|
1186
|
-
|
1187
|
-
The functionality of Trait Injectors can be removed from individual targets, whether class 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. First we have trait canceling or ejection. This is where a trait is completely removed from a target precipitating further calls on the trait to generate an error. Second there is trait silencing and reactivation. This on the other hand allows for the temporary quieting of a trait but which may need to be reactivated at a later time.
|
1188
|
-
|
1189
|
-
Trait canceling or ejection can take place at the instance or the class level. Here we have a Trait Injector removed after an #enrich to individual instance:
|
1190
|
-
|
1191
|
-
class Coffee
|
1192
|
-
def cost
|
1193
|
-
1.00
|
1194
|
-
end
|
1195
|
-
end
|
1196
|
-
trait :milk do
|
1197
|
-
def cost
|
1198
|
-
super() + 0.50
|
1199
|
-
end
|
1200
|
-
end
|
1201
|
-
|
1202
|
-
cup = Coffee.new.enrich(milk)
|
1203
|
-
friends_cup = Coffee.new.enrich(milk)
|
1204
|
-
|
1205
|
-
cup.cost.should == 1.50
|
1206
|
-
friends_cup.cost.should == 1.50
|
1207
|
-
|
1208
|
-
cup.cancel :milk
|
1209
|
-
|
1210
|
-
cup.cost.should == 1.00
|
1211
|
-
|
1212
|
-
# friends cup didn't change price
|
1213
|
-
friends_cup.cost.should == 1.50
|
1214
|
-
|
1215
|
-
Here it is removed after an #inject at the class level:
|
1216
|
-
|
1217
|
-
# create the injection
|
1218
|
-
class Home
|
1219
|
-
trait :layout do
|
1220
|
-
def fractal
|
1221
|
-
end
|
1222
|
-
end
|
1223
|
-
inject layout
|
1224
|
-
end
|
1225
|
-
expect{Home.new.fractal}.to_not raise_error
|
1226
|
-
|
1227
|
-
# build
|
1228
|
-
my_home = Home.new
|
1229
|
-
friends = Home.new
|
1230
|
-
|
1231
|
-
# eject the code
|
1232
|
-
class Home
|
1233
|
-
eject :layout
|
1234
|
-
end
|
1235
|
-
|
1236
|
-
# the result
|
1237
|
-
expect{my_home.fractal}.to raise_error
|
1238
|
-
expect{friends.fractal}.to raise_error
|
1239
|
-
expect{Home.new.fractal}.to raise_error
|
1240
|
-
|
1241
|
-
|
1242
|
-
The code for these examples makes use of the #cancel alias #eject method which opens the door to this additional functionality provided by traits. See also the Strategy Pattern just below this. It is important to keep in mind that ejection is "permanent" (not really, can always be re-injected) and that this permanence is more of its intent. 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.
|
1243
|
-
|
1244
|
-
#### #cancel *sym (alias #eject)
|
1245
|
-
This method cancels or ejects trait function from a single object or class. It is in scope on any classes injected or enriched with a trait. Its effect is that of completely removing one of our modular closures from the ancestor chain. Once this is done method calls on the trait will raise an error.
|
1246
|
-
|
1247
|
-
### Injector Directives
|
1248
|
-
Once you have a trait handle you can also use it to issue directives to the trait. These directives can have a profound effect on your code. There are directives to silence a trait, to reactivate it, to create a soft tag, or to completely obliterate the trait including the handle to it.
|
1249
|
-
|
1250
|
-
#### :silence/:collapse directive
|
1251
|
-
This description produces similar results to the one for trait ejection (see above) except that further trait method calls DO NOT raise an error. They just quietly return nil. Here are a couple of different cases:
|
1252
|
-
|
1253
|
-
The case with multiple object instances:
|
1254
|
-
|
1255
|
-
trait :copiable do
|
1256
|
-
def object_copy
|
1257
|
-
'a dubious copy'
|
1258
|
-
end
|
1259
|
-
end
|
1260
|
-
|
1261
|
-
o1 = Object.new.enrich(copiable)
|
1262
|
-
o2 = Object.new.enrich(copiable)
|
1263
|
-
|
1264
|
-
o1.object_copy.should == 'a dubious copy'
|
1265
|
-
o2.object_copy.should == 'a dubious copy'
|
1266
|
-
|
1267
|
-
copiable :silence
|
1268
|
-
|
1269
|
-
o1.object_copy.should == nil
|
1270
|
-
o2.object_copy.should == nil
|
1271
|
-
|
1272
|
-
|
1273
|
-
The case with a class receiver:
|
1274
|
-
|
1275
|
-
class SomeClass
|
1276
|
-
trait :code do
|
1277
|
-
def tester
|
1278
|
-
'boo'
|
1279
|
-
end
|
1280
|
-
end
|
1281
|
-
|
1282
|
-
inject code
|
1283
|
-
end
|
1284
|
-
|
1285
|
-
a = SomeClass.new
|
1286
|
-
b = SomeClass.new
|
1287
|
-
|
1288
|
-
# collapse
|
1289
|
-
SomeClass.code :collapse
|
1290
|
-
|
1291
|
-
a.tester.should == nil
|
1292
|
-
b.tester.should == nil
|
1293
|
-
|
1294
|
-
|
1295
|
-
#### :active/:rebuild directive
|
1296
|
-
Trait Injectors that have been silenced or collapsed can at a later point be reactivated. Here are a couple of cases:
|
1297
|
-
|
1298
|
-
The case with multiple object receivers:
|
1299
|
-
|
1300
|
-
trait :reenforcer do
|
1301
|
-
def thick_walls
|
1302
|
-
'=====|||====='
|
1303
|
-
end
|
1304
|
-
end
|
1305
|
-
|
1306
|
-
o1 = Object.new.enrich(reenforcer)
|
1307
|
-
o2 = Object.new.enrich(reenforcer)
|
1308
|
-
|
1309
|
-
reenforcer :collapse
|
1310
|
-
|
1311
|
-
o1.thick_walls.should == nil
|
1312
|
-
o2.thick_walls.should == nil
|
1313
|
-
|
1314
|
-
reenforcer :rebuild
|
1315
|
-
|
1316
|
-
o1.thick_walls.should == '=====|||====='
|
1317
|
-
o2.thick_walls.should == '=====|||====='
|
1318
|
-
|
1319
|
-
|
1320
|
-
The case with a class receiver:
|
1321
|
-
|
1322
|
-
class SomeBloatedObject
|
1323
|
-
trait :ThinFunction do
|
1324
|
-
def perform
|
1325
|
-
'do the deed'
|
1326
|
-
end
|
1327
|
-
end
|
1328
|
-
inject ThinFunction()
|
1329
|
-
end
|
1330
|
-
SomeBloatedObject.ThinFunction :silence # alias to :collapse
|
1331
|
-
|
1332
|
-
tester = SomeBloatedObject.new
|
1333
|
-
tester.perform.should == nil
|
1334
|
-
|
1335
|
-
SomeBloatedObject.ThinFunction :active # alias to :rebuild
|
1336
|
-
tester.perform.should == 'do the deed'
|
1337
|
-
|
1338
|
-
#### :tag/:version directive
|
1339
|
-
This directive creates a soft tagged version of a trait. For more on this see Soft Tags below.
|
1340
|
-
|
1341
|
-
#### :implode directive
|
1342
|
-
This directive totally destroys the trait including the handle to it. Use it carefully!
|
1343
|
-
|
1344
|
-
class Model
|
1345
|
-
def feature
|
1346
|
-
'a standard feature'
|
1347
|
-
end
|
1348
|
-
end
|
1349
|
-
|
1350
|
-
trait :extras do
|
1351
|
-
def feature
|
1352
|
-
super() + ' plus some extras'
|
1353
|
-
end
|
1354
|
-
end
|
1355
|
-
|
1356
|
-
car = Model.new.enrich(extras)
|
1357
|
-
car.feature.should == 'a standard feature plus some extras'
|
1358
|
-
|
1359
|
-
extras :implode
|
1360
|
-
|
1361
|
-
# total implosion
|
1362
|
-
car.feature.should == 'a standard feature'
|
1363
|
-
|
1364
|
-
expect{extras}.to raise_error(NameError, /extras/)
|
1365
|
-
expect{ new_car = Model.new.enrich(extras) }.to raise_error(NameError, /extras/)
|
1366
|
-
expect{
|
1367
|
-
extras do
|
1368
|
-
def foo
|
1369
|
-
end
|
1370
|
-
end
|
1371
|
-
}.to raise_error(NameError, /extras/)
|
1372
|
-
|
1373
1424
|
### The GOF Strategy Pattern:
|
1374
1425
|
Another pattern that Jackbox helps with is the GOF Strategy Pattern. This is a pattern which changes the guts of an object as opposed to just changing its outer shell. Traditional examples of this pattern in Ruby use PORO component injection within constructors, and then a form of delegation. With Jackbox Trait Injectors all this is eliminated.
|
1375
1426
|
|
@@ -1426,25 +1477,6 @@ But, with #cancel/#eject it is possible to have an even more general alternate i
|
|
1426
1477
|
cup.brew
|
1427
1478
|
cup.strategy.should == 'sweedish'
|
1428
1479
|
|
1429
|
-
### Soft Tags
|
1430
|
-
Just like hard tags above but a name is not needed:
|
1431
|
-
|
1432
|
-
jack :SomeJack do
|
1433
|
-
def foo
|
1434
|
-
:foo
|
1435
|
-
end
|
1436
|
-
end
|
1437
|
-
SomeJack :tag # Unnamed version
|
1438
|
-
|
1439
|
-
SomeJack(:tag) do # New unnamed version
|
1440
|
-
def foo
|
1441
|
-
:foooooooo
|
1442
|
-
end
|
1443
|
-
end
|
1444
|
-
|
1445
|
-
Accessible through trait#tags (an Array). Also available **trait#tags.hard** and **trait#tags.soft**. See introspection above.
|
1446
|
-
|
1447
|
-
---
|
1448
1480
|
### Patterns of a Different Flavor
|
1449
1481
|
|
1450
1482
|
Jackbox Traits also make possible some additional coding patterns. Although not part of the traditional GOF set these new patterns are only possible thanks to languages like Ruby that although not as flexible as Lisp, permit the morphing of normal forms into newer ones. We hope that as Ruby evolves it continues to give programmers more power redefining the language itself. Here are some new patterns:
|
@@ -1473,7 +1505,7 @@ __1) Late Decorator.-__ Another flow that also benefits from #define\_method in
|
|
1473
1505
|
|
1474
1506
|
w.cost.should == 15
|
1475
1507
|
|
1476
|
-
The actual decorating trait function is late bound and defined only after some other
|
1508
|
+
The actual decorating trait function is late bound and defined only after some other process has completed.
|
1477
1509
|
|
1478
1510
|
__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:
|
1479
1511
|
|
@@ -1492,21 +1524,25 @@ __2) The Super Pattern.-__ No. This is not a superlative kind of pattern. Simp
|
|
1492
1524
|
|
1493
1525
|
__3) The Solutions 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:
|
1494
1526
|
|
1495
|
-
jack :Solution
|
1527
|
+
jack :Solution do
|
1528
|
+
def meth
|
1529
|
+
1
|
1530
|
+
end
|
1531
|
+
end
|
1496
1532
|
|
1497
1533
|
Solution( :tag ) do
|
1498
1534
|
def solution
|
1499
|
-
1
|
1535
|
+
meth + 1
|
1500
1536
|
end
|
1501
1537
|
end
|
1502
1538
|
Solution( :tag ) do
|
1503
1539
|
def solution
|
1504
|
-
2
|
1540
|
+
meth + 2
|
1505
1541
|
end
|
1506
1542
|
end
|
1507
1543
|
Solution( :tag ) do
|
1508
1544
|
def solution
|
1509
|
-
3
|
1545
|
+
meth + 3
|
1510
1546
|
end
|
1511
1547
|
end
|
1512
1548
|
|
@@ -1586,18 +1622,97 @@ The important thing to remember here is that #String() is a method now. We can r
|
|
1586
1622
|
|
1587
1623
|
For more on this see, the rspec files and the Jackbox blog at <a href="http://jackbox.us">http://jackbox.us</a>.
|
1588
1624
|
|
1589
|
-
#### #reclass?(klass)
|
1625
|
+
#### #constructor?/#reclass?(klass)
|
1590
1626
|
|
1591
1627
|
This helper verifies a certain re-class exists within the current namespace. It returns a boolean. Example:
|
1592
1628
|
|
1593
1629
|
module One
|
1594
|
-
if
|
1630
|
+
if constructor? String
|
1595
1631
|
String('our string')
|
1596
1632
|
end
|
1597
1633
|
end
|
1598
1634
|
|
1635
|
+
__5. The Web Widget Pattern.__
|
1636
|
+
This example uses Jackbox Ruby Traits to render web controls. There are a couple of different variations possible which we show in the rspec files. Here we'll use the one based on JITI. Here is the code:
|
1637
|
+
|
1638
|
+
# some data
|
1639
|
+
|
1640
|
+
def database_content # could be any model
|
1641
|
+
%{car truck airplane boat}
|
1642
|
+
end
|
1643
|
+
|
1644
|
+
# rendering helper controls
|
1645
|
+
|
1646
|
+
class MyWidget
|
1647
|
+
def initialize(content)
|
1648
|
+
@content = content
|
1649
|
+
end
|
1650
|
+
|
1651
|
+
def render
|
1652
|
+
"<div id='MyWidget'>#{@content}</div>"
|
1653
|
+
end
|
1654
|
+
end
|
1655
|
+
|
1599
1656
|
|
1600
|
-
|
1657
|
+
MainFace = trait :WidgetFace do # our trait
|
1658
|
+
|
1659
|
+
attr_accessor :font, :width, :height
|
1660
|
+
|
1661
|
+
def dim(width, heigth)
|
1662
|
+
@width, @heigth = width, heigth
|
1663
|
+
end
|
1664
|
+
|
1665
|
+
def render
|
1666
|
+
%{
|
1667
|
+
<style>
|
1668
|
+
#MyWidget{
|
1669
|
+
font: 14px, #{@font};
|
1670
|
+
width:#{@width};
|
1671
|
+
height: #{@heigth}
|
1672
|
+
}
|
1673
|
+
</style>
|
1674
|
+
#{super()}
|
1675
|
+
}
|
1676
|
+
end
|
1677
|
+
end
|
1678
|
+
|
1679
|
+
# somewhere in a view
|
1680
|
+
|
1681
|
+
browser = 'Safari' # the user selected media
|
1682
|
+
@content = database_content
|
1683
|
+
|
1684
|
+
|
1685
|
+
my_widget = case browser
|
1686
|
+
when match(/Safari|Firefox|IE/)
|
1687
|
+
|
1688
|
+
MyWidget.new(@content).enrich(WidgetFace() do
|
1689
|
+
|
1690
|
+
def render # override invoking JIT inheritance
|
1691
|
+
dim '600px', '200px' # normal inherited method call
|
1692
|
+
@font = 'helvetica'
|
1693
|
+
|
1694
|
+
super()
|
1695
|
+
end
|
1696
|
+
end)
|
1697
|
+
|
1698
|
+
else
|
1699
|
+
|
1700
|
+
MyWidget.new(@content).enrich(WidgetFace() do
|
1701
|
+
|
1702
|
+
def render # override invoking JIT inheritance
|
1703
|
+
dim '200px', '600px' # normal inherited method call
|
1704
|
+
@font ='arial'
|
1705
|
+
|
1706
|
+
super()
|
1707
|
+
end
|
1708
|
+
end)
|
1709
|
+
|
1710
|
+
end
|
1711
|
+
|
1712
|
+
WidgetFace(:implode)
|
1713
|
+
|
1714
|
+
|
1715
|
+
For more information and additional examples see the rspec examples on this project. There you'll find a long list of over __250__ rspec examples and code showcasing features of Jackbox Trait Injectors along with some additional descriptions.
|
1601
1716
|
|
1602
1717
|
---
|
1603
1718
|
## Additional Tools
|
@@ -1627,28 +1742,13 @@ With Abstract the code goes like this:
|
|
1627
1742
|
expect{Velocity.new}.to_not raise_error
|
1628
1743
|
Velocity.new.speed.should == 35
|
1629
1744
|
|
1630
|
-
|
1631
|
-
With Prefs you can add persistent properties to a class. These properties persist even through program termination. Here is the example code:
|
1632
|
-
|
1633
|
-
module Jester
|
1634
|
-
extend Prefs
|
1635
|
-
|
1636
|
-
pref :value => 10
|
1637
|
-
end
|
1638
|
-
|
1639
|
-
Jester.value.should == 10
|
1640
|
-
Jester.value = 3
|
1641
|
-
Jester.value.should == 3
|
1642
|
-
Jester.reset :value
|
1643
|
-
Jester.value.should == 10
|
1644
|
-
|
1645
1745
|
There is also command line utility called **jackup** that simply allows users to bring projects up to 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.
|
1646
1746
|
|
1647
1747
|
## Availability
|
1648
1748
|
|
1649
1749
|
Jackbox is current available for Linux, Mac, and Windows versions of Ruby 1.9.3 thru 2.2.1
|
1650
1750
|
|
1651
|
-
## Installation
|
1751
|
+
## Installation and Testing
|
1652
1752
|
|
1653
1753
|
Add this line to your application's Gemfile:
|
1654
1754
|
|
@@ -1664,8 +1764,11 @@ Or install it yourself as:
|
|
1664
1764
|
|
1665
1765
|
And then execute the following command inside the project directory:
|
1666
1766
|
|
1667
|
-
$jackup
|
1767
|
+
$ jackup
|
1768
|
+
|
1769
|
+
To run the test suite inside the gem directory, run the command test-jacks. This will run the test battery using rspec with Jackbox isolated within the confines of its own gem directory.
|
1668
1770
|
|
1771
|
+
$ test-jacks
|
1669
1772
|
|
1670
1773
|
|
1671
1774
|
## Support
|
@@ -1690,5 +1793,5 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
1690
1793
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
1691
1794
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1692
1795
|
|
1693
|
-
In the above copyright notice, the letters LHA are the
|
1796
|
+
In the above copyright notice, the letters LHA are the English acronym
|
1694
1797
|
for Luis Enrique Alvarez (Barea) who is the author and owner of the copyright.
|