jackbox 0.9.6.3 → 0.9.6.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/CHANGES.txt +38 -4
- data/LICENSE.lic +0 -0
- data/README.md +568 -364
- data/lib/jackbox.rb +1 -1
- data/lib/jackbox/injectors.rb +1 -1
- data/lib/jackbox/injectors/coda.rb +2 -0
- data/lib/jackbox/injectors/prelude.rb +2 -0
- data/lib/jackbox/rake.rb +1 -1
- data/lib/jackbox/tools/prefs.rb +1 -1
- data/lib/jackbox/version.rb +1 -1
- data/spec/lib/jackbox/injector_composition_spec.rb +111 -111
- data/spec/lib/jackbox/injector_directives_spec.rb +25 -27
- data/spec/lib/jackbox/injector_inheritance_spec.rb +610 -1004
- data/spec/lib/jackbox/injector_introspection_spec.rb +265 -219
- data/spec/lib/jackbox/injector_namespacing_spec.rb +17 -17
- data/spec/lib/jackbox/injector_spec.rb +26 -0
- data/spec/lib/jackbox/injector_versioning_spec.rb +37 -37
- data/spec/lib/jackbox/jiti_rules_spec.rb +663 -0
- data/spec/lib/jackbox/patterns_spec.rb +224 -122
- data/spec/lib/jackbox/prefs_spec.rb +4 -4
- data/spec/lib/jackbox/reclassing_spec.rb +154 -406
- data/spec/lib/jackbox/vmc_spec.rb +169 -10
- data/spec/lib/jackbox_spec.rb +241 -131
- metadata +26 -26
- data/spec/lib/jackbox/examples/dx_spec.rb +0 -346
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
NTJlZWM5NGJkZTA2YzA3NWJlY2RmMDY3NTc0NzE3ODJmODg3MzgzMw==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3ad85cf59fad7a3d581d62ed26afd80a2c753552
|
4
|
+
data.tar.gz: 4aeaaa6afe5317d230af20778946e574ad5d730b
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
N2M4YTg5ZmU5MWFlM2NhMzUxOTNlZWU5OTM1NGRjZTE5MTM0MjIzNjQ4Mjc4
|
11
|
-
NDdmM2M1ZDc4YzFjM2QyODY5NWJmMzQzOWFhNjczZmYwMjdjZTI=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
MzE0MGI3Zjg1ZGRmNzNjZTM0OTM0MGZlMGI3YTgzOTMwNjI5MjQzOTllMDVk
|
14
|
-
ZjQ0NjJmYWYzYWU3NDZkMWM5NTliNjA1NWI2MzZlZDQ2ZGFkNjg5NTZjMDU5
|
15
|
-
YmUxNmUxZDBmNjdkMzYwM2QzZWExMjhjOGE4MmFkNDNhMjg0NWY=
|
6
|
+
metadata.gz: 869801891af0e806593ab8561ae88cfb0c0935a5a2649204bfdab4c7a2e83f50e77159e56558dc4c58ca03c74b2078b99dce2e171d0e0556b6ad35e1fcfb21ac
|
7
|
+
data.tar.gz: ff8ecf25e9e2da21ef8f70588abb06a0d9d6f2b372720923acce2f3108092a074b5f22f69e2812c14e509d1b25e1165dbdc94e6df77fe306dff7041a85132769
|
data/CHANGES.txt
CHANGED
@@ -1,6 +1,42 @@
|
|
1
|
-
0.9.6.
|
1
|
+
0.9.6.5 RC3
|
2
|
+
|
3
|
+
. Established The Rules of JITI
|
4
|
+
. Changed the focus from Injector based to a Trait metaphor.
|
5
|
+
. Fixed inheritance problem with multiple same tag ancestors.
|
6
|
+
. Masked out external module intrusion
|
7
|
+
. Ironed out some problems with external ancestor masking
|
8
|
+
. can only base it on external modules
|
9
|
+
. but cannot intrude further down the chain
|
10
|
+
. Cleaned up and split up inheritance examples.
|
11
|
+
. All Tests passing
|
12
|
+
. broke down *some* of the long examples to please those who complained
|
13
|
+
(this was originally done because it was easier for testing trait life-cycles)
|
14
|
+
. Resolved JITI Decorators to error out in case of colluding ancestors
|
15
|
+
. Refactored #decorate once more
|
16
|
+
. Trimmed down directory structure
|
17
|
+
. Cleaned up examples and code
|
18
|
+
. FIXED issues with VMC under Ruby 1.9 and 2.0 related to flat_map underlying flatten bug
|
19
|
+
. Changed "fixed" a couple of #metaclass issues where:
|
20
|
+
. #root was getting defined multiple times
|
21
|
+
. errors were preventing the class from being returned
|
22
|
+
. also changed how the #metaclass? test is done
|
23
|
+
. Moved basic #dup and #clone to Object to avoid constant redefinitions
|
24
|
+
. Based Injector on Abstract to avoid normal instantiation
|
25
|
+
. Done fixing some issues with the #with construct:
|
26
|
+
. was obliterating target definitions of #method_missing on return. Now uses #decorate
|
27
|
+
. #decorate was suppressing to many warnings with long #suppress_warnings block
|
28
|
+
. now handles #with a, b, c ... do
|
29
|
+
. dropped *params to #with
|
30
|
+
. Fixed an issue with Equality
|
31
|
+
. Fixed #undecorate for object instances
|
32
|
+
. Brought in some re-class fixes
|
33
|
+
. Changed ReadMe
|
34
|
+
. Fixed ejection of inherited tags
|
35
|
+
. when you eject the original all inherited tags go with it
|
36
|
+
. Cleaned up comments
|
2
37
|
|
3
|
-
|
38
|
+
|
39
|
+
0.9.6.3 RC2
|
4
40
|
|
5
41
|
. Got re-classes working with rspec 3.4
|
6
42
|
. Changed the jack #inspect method. Cleaned up examples
|
@@ -97,8 +133,6 @@ Cleaned up inheritance, vmc, and patterns examples.
|
|
97
133
|
. Started work on optimizations
|
98
134
|
. Added sym_for object method to Module class
|
99
135
|
. Solved automatic tag tracing
|
100
|
-
. Override module_eval to work with functor(&code)
|
101
|
-
. added functor definition
|
102
136
|
. Added the notion of equality to Injectors
|
103
137
|
. Reworked examples to:
|
104
138
|
. comply with new equality operators
|
data/LICENSE.lic
CHANGED
Binary file
|
data/README.md
CHANGED
@@ -16,39 +16,54 @@ Copyright © 2014, 2015 LHA. All rights reserved.
|
|
16
16
|
<a href="http://jackbox.us"><h1>Jackbox</h1></a>
|
17
17
|
|
18
18
|
---
|
19
|
-
<h2 style="font-family:Impact">
|
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
|
21
|
+
The defining thought behind Jackbox is a simple one: If Ruby is like Play-Doh, with Jackbox we want 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 traits/injectors, class constructors, the application of versioning to runtimes, and a just-in-time inheritance model 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, code injectors can perhaps be thought of as a form of **Modular Closures
|
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 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 also trait directives to for example remain as silent traits and to 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
|
+
**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 code traits 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 Ruby 1.9 and related technologies.
|
26
26
|
|
27
|
-
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 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
|
27
|
+
Following on this we introduce the concept of Trait/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.
|
28
28
|
|
29
|
-
Finally, we also present
|
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
|
31
|
+
We have chosen to keep the code obfuscated **for now** because we are a small company with fewer resources and we need to protect our germinating intellectual property. But, as our business model evolves we will be considering open sourcing it. 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. We 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
|
+
|
33
|
+
Advantages Of Trait Based Programming
|
34
|
+
------------------------------------
|
35
|
+
* 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
|
+
* With Traits runtime versioning is possible and traits can be upgraded with new versions of the trait.
|
39
|
+
* Traits enable new and different coding patterns.
|
40
|
+
|
41
|
+
---
|
32
42
|
|
33
43
|
Basic Methods
|
34
44
|
--------------------------
|
35
45
|
There are some basic methods to Jackbox. These are just rudimentary helpers, which in effect appear to be a form of syntax sugar for every day things. But, behind their apparent sugar coating lie some additional capabilities as shown the deeper you delve into Jackbox. For more on them read the following sections, but their preliminary descriptions follow here:
|
36
46
|
|
37
47
|
#### #decorate :sym, &blk
|
38
|
-
This method allows for decorations to be placed on a single method
|
48
|
+
This method allows for decorations to be placed on a single method whether 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.
|
39
49
|
|
40
50
|
At the class level:
|
41
|
-
|
51
|
+
|
52
|
+
class One
|
53
|
+
def foo
|
54
|
+
'foo'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
42
58
|
class One
|
43
59
|
decorate :foo do
|
44
60
|
super() + 'decoration ' # super available within decoration
|
45
61
|
end
|
46
62
|
end
|
47
63
|
|
48
|
-
One.new.foo
|
49
|
-
#=> foo decoration
|
64
|
+
One.new.foo.should == 'foo decoration'
|
50
65
|
|
51
|
-
Or, at the
|
66
|
+
Or, at the instance level:
|
52
67
|
|
53
68
|
one = One.new
|
54
69
|
|
@@ -56,43 +71,40 @@ Or, at the object level:
|
|
56
71
|
super() + arg # again the use of super is possible
|
57
72
|
end
|
58
73
|
|
59
|
-
one.foo('after')
|
60
|
-
#=> foo decoration after
|
74
|
+
one.foo('after').should == 'foo decoration after'
|
61
75
|
|
62
76
|
It also works like so:
|
63
77
|
|
64
|
-
Object.decorate :
|
65
|
-
|
78
|
+
Object.decorate :to_s do
|
79
|
+
super() + " is your object"
|
66
80
|
end
|
67
|
-
|
68
|
-
Object.new.
|
69
|
-
#=> #<Object:0x00000101787e20> is your object
|
81
|
+
|
82
|
+
Object.new.to_s.should match(/is your object/)
|
70
83
|
|
71
84
|
|
72
85
|
#### #with obj, &blk
|
73
|
-
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.
|
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 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 on the alternative.
|
74
87
|
|
75
88
|
Here is some sample usage code:
|
76
89
|
|
77
90
|
class One
|
78
|
-
def
|
91
|
+
def meth1(arg)
|
79
92
|
'in One ' + arg
|
80
93
|
end
|
81
94
|
end
|
82
95
|
|
83
96
|
class Two
|
84
|
-
def
|
97
|
+
def meth2(arg)
|
85
98
|
'and in Two ' + arg
|
86
99
|
end
|
87
100
|
def meth
|
88
101
|
with One.new do # context of One and Two available simultaneously!!!
|
89
|
-
return
|
102
|
+
return meth1 meth2 'with something'
|
90
103
|
end # return object
|
91
104
|
end
|
92
105
|
end
|
93
106
|
|
94
|
-
Two.new.meth
|
95
|
-
#=> 'in One and in Two with something'
|
107
|
+
Two.new.meth.should == 'in One and in Two with something'
|
96
108
|
|
97
109
|
Use it to define function:
|
98
110
|
|
@@ -132,7 +144,7 @@ Use it with **#decorate** on singleton classes like this:
|
|
132
144
|
|
133
145
|
|
134
146
|
#### #lets sym=nil, &blk
|
135
|
-
We could say, this is simple syntax sugar. It adds readability to some constructs
|
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, aside its use with respect to class constructors, #lets is mostly for one liners. Here are some examples:
|
136
148
|
|
137
149
|
To define local functions/lambdas. Define symbols in local scope:
|
138
150
|
|
@@ -157,59 +169,59 @@ Can be used to define a special values or pseudo-immutable strings:
|
|
157
169
|
lets(:faa){ 'some important string' }
|
158
170
|
|
159
171
|
|
160
|
-
Injectors
|
172
|
+
Traits/Injectors
|
161
173
|
----------
|
162
|
-
|
174
|
+
Traits 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 want. In the sections below we will discuss some of the methods available to you with Jackbox in connection with Traits, as well as elaborate on some of the other properties of traits. But, it is essential to understand there are some syntactical differences to Traits/Injectors with respect to regular modules. We will show them first, with some examples:
|
163
175
|
|
164
|
-
**INJECTORS ARE DECLARED IN THE FOLLOWING WAYS:**
|
176
|
+
**TRAITS/INJECTORS ARE DECLARED IN THE FOLLOWING WAYS:**
|
165
177
|
|
166
178
|
|
167
|
-
|
179
|
+
trait :name
|
168
180
|
|
169
181
|
# or...
|
170
182
|
|
171
|
-
Name =
|
183
|
+
Name = trait :name
|
172
184
|
|
173
185
|
# or even ...
|
174
186
|
|
175
|
-
|
187
|
+
injector :Name # capitalized method, using alias #trait
|
176
188
|
|
177
189
|
|
178
|
-
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
|
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 trait versioning, their behavior under inheritance, and also trait directives.
|
179
191
|
|
180
192
|
# somewhere in your code
|
181
|
-
include
|
193
|
+
include Traits
|
182
194
|
|
183
|
-
|
195
|
+
trait :my_trait # define the trait
|
184
196
|
|
185
|
-
|
197
|
+
my_trait do
|
186
198
|
def bar
|
187
199
|
:a_bar
|
188
200
|
end
|
189
201
|
end
|
190
202
|
|
191
203
|
# later on...
|
192
|
-
widget.
|
204
|
+
widget.extend my_trait # apply the trait
|
205
|
+
|
193
206
|
widget.bar
|
194
207
|
# => bar
|
195
208
|
|
196
209
|
# or...
|
197
210
|
|
198
|
-
Mine =
|
211
|
+
Mine = my_trait
|
199
212
|
class Target
|
200
|
-
inject Mine
|
213
|
+
inject Mine # apply the trait
|
201
214
|
end
|
202
215
|
|
203
216
|
Target.new.bar
|
204
217
|
# => bar
|
205
218
|
|
206
|
-
# etc ...
|
207
219
|
|
208
|
-
**INJECTORS HAVE PROLONGATIONS:**
|
220
|
+
**TRAITS/INJECTORS HAVE PROLONGATIONS:**
|
209
221
|
|
210
|
-
|
222
|
+
trait :my_trait
|
211
223
|
|
212
|
-
|
224
|
+
my_trait do # first prolongation
|
213
225
|
|
214
226
|
def another_method
|
215
227
|
end
|
@@ -218,119 +230,115 @@ Their use and semantics are somewhat defined by the following snippet. But, to
|
|
218
230
|
|
219
231
|
# ...
|
220
232
|
|
221
|
-
|
233
|
+
my_trait do # another prolongation
|
222
234
|
|
223
235
|
def yet_another_method
|
224
236
|
end
|
225
237
|
|
226
238
|
end
|
227
239
|
|
228
|
-
These prolongations become versions
|
240
|
+
These prolongations become versions once applied or tagged. See Tagging/Naming below. In lieu of this they remain in the Virtual Method Cache (see below) in an un-versioned state available to any client.
|
229
241
|
|
230
|
-
#### #injector :sym
|
231
|
-
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
|
242
|
+
#### #trait/#injector :sym
|
243
|
+
This is a global function. It defines an object of type Trait/Injector with the name of :symbol. Use it when you want to generate an 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. Traits/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:
|
232
244
|
|
233
245
|
class A
|
234
|
-
|
246
|
+
trait :Function # defined
|
235
247
|
end
|
236
248
|
|
237
249
|
class B
|
238
|
-
include Function()
|
250
|
+
include Function() # applied
|
239
251
|
end
|
240
252
|
|
241
|
-
# This is perfectly valid with
|
253
|
+
# This is perfectly valid with traits
|
242
254
|
|
243
|
-
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:
|
255
|
+
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:
|
244
256
|
|
245
|
-
class
|
246
|
-
|
257
|
+
class A
|
258
|
+
trait :form
|
247
259
|
end
|
248
260
|
|
249
|
-
class
|
261
|
+
class B
|
250
262
|
include form # This genenerates and ERROR!
|
251
263
|
end
|
252
264
|
|
253
|
-
class
|
254
|
-
include
|
265
|
+
class B
|
266
|
+
include A.form # This is valid however!
|
255
267
|
end
|
256
268
|
|
257
|
-
# This is also perfectly valid with injectors
|
258
269
|
|
259
|
-
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
|
270
|
+
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 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.
|
260
271
|
|
261
|
-
#### #include/inject *
|
262
|
-
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
|
272
|
+
#### #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.
|
263
274
|
|
264
|
-
#### #extend/enrich *
|
265
|
-
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
|
275
|
+
#### #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 if for object definition. Use it to "extend" the receiver of a trait. Also takes multiple traits.
|
266
277
|
|
267
|
-
**IMPORTANT NOTE: Injector lookup follows the method and not the constant lookup algorithm.**
|
278
|
+
**IMPORTANT NOTE: Trait Injector lookup follows the method and not the constant lookup algorithm.**
|
268
279
|
|
269
280
|
If you need to follow constant lookup, here is the code for that:
|
270
281
|
|
271
|
-
Name =
|
282
|
+
Name = trait :sym .... # this also creates a hard tag (see below)
|
272
283
|
|
273
|
-
### Injector Versioning
|
284
|
+
### Trait/Injector Versioning
|
274
285
|
|
275
|
-
One of the most valuable properties of
|
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 trait methods, and any subsequent overrides to those methods on the trait 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 Trait/Injector Versioning, two different versions of the same code object can be running simultaneously.
|
276
287
|
|
277
288
|
We'll use some examples to illustrate the point. This is how versioning occurs:
|
278
289
|
|
279
|
-
#
|
290
|
+
# trait declaration
|
280
291
|
#___________________
|
281
|
-
|
292
|
+
trait :my_trait do
|
282
293
|
def bar
|
283
|
-
:a_bar
|
294
|
+
:a_bar # version bar.1
|
284
295
|
end
|
285
296
|
def foo
|
286
297
|
# ...
|
287
298
|
end
|
288
299
|
end
|
289
300
|
|
290
|
-
object1.
|
291
|
-
object1.bar.should == :a_bar
|
301
|
+
object1.extend my_trait # apply the trait --first snapshot
|
302
|
+
object1.bar.should == :a_bar # pass the test
|
292
303
|
|
293
|
-
#
|
304
|
+
# trait prolongation
|
294
305
|
#__________________
|
295
|
-
|
306
|
+
my_trait do
|
296
307
|
def bar
|
297
|
-
:some_larger_bar
|
308
|
+
:some_larger_bar # version bar.2 ... re-defines bar
|
298
309
|
end
|
299
310
|
# ...
|
300
311
|
end
|
301
312
|
|
302
|
-
object2.
|
303
|
-
object2.bar.should == :some_larger_bar
|
304
|
-
|
305
|
-
# result
|
306
|
-
|
307
|
-
object1.bar.should == :a_bar # bar.1 is still the one
|
313
|
+
object2.extend my_trait # apply the trait --second snapshot
|
314
|
+
object2.bar.should == :some_larger_bar # pass the test
|
308
315
|
|
309
316
|
###############################################
|
310
317
|
# First object has kept its preferred version #
|
311
318
|
###############################################
|
319
|
+
|
320
|
+
object1.bar.should == :a_bar # bar.1 is still the one
|
312
321
|
|
313
322
|
|
314
|
-
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-
|
323
|
+
When trait 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 but existing targets are untouched. Internal local-binding is also preserved. If a trait is then re-injected on an instance only then does the instance get updated with the newer version. Here is the code:
|
315
324
|
|
316
325
|
# re-injection
|
317
326
|
#_________________
|
318
|
-
object1.
|
319
|
-
|
320
|
-
object1.bar.should == :some_larger_bar # bar.2 now available
|
327
|
+
object1.extend my_trait # re-injection --third snapshot
|
321
328
|
|
322
329
|
###############################################
|
323
330
|
# First object now has the updated version #
|
324
331
|
###############################################
|
325
332
|
|
333
|
+
object1.bar.should == :some_larger_bar # bar.2 now available
|
326
334
|
|
327
|
-
Re-injection on classes is a little bit trickier
|
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. See the sections below as well as the rspec files for more on this.
|
328
336
|
|
329
337
|
Here is an example of Injector Versioning as it pertains to classes:
|
330
338
|
|
331
|
-
#
|
339
|
+
# trait declaration:
|
332
340
|
#___________________
|
333
|
-
|
341
|
+
trait :Versions do
|
334
342
|
def meth arg # version meth.1
|
335
343
|
arg ** arg
|
336
344
|
end
|
@@ -340,7 +348,7 @@ Here is an example of Injector Versioning as it pertains to classes:
|
|
340
348
|
inject Versions() # apply --snapshot
|
341
349
|
end
|
342
350
|
|
343
|
-
#
|
351
|
+
# trait extension:
|
344
352
|
#_________________
|
345
353
|
Versions do
|
346
354
|
def meth arg1, arg2 # version meth.2 ... redefines meth.1
|
@@ -352,14 +360,13 @@ Here is an example of Injector Versioning as it pertains to classes:
|
|
352
360
|
inject Versions() # apply --snapshot
|
353
361
|
end
|
354
362
|
|
355
|
-
|
363
|
+
##############################################
|
364
|
+
# Two different trait versions coexisting #
|
365
|
+
##############################################
|
356
366
|
|
357
|
-
Two.new.meth(2,4).should == 8 # meth.2
|
358
367
|
One.new.meth(3).should == 27 # meth.1
|
368
|
+
Two.new.meth(2,4).should == 8 # meth.2
|
359
369
|
|
360
|
-
##############################################
|
361
|
-
# Two different injector versions coexisting #
|
362
|
-
##############################################
|
363
370
|
|
364
371
|
To update the class, we then do the following:
|
365
372
|
|
@@ -367,19 +374,19 @@ To update the class, we then do the following:
|
|
367
374
|
update Versions() # private call to #update
|
368
375
|
end
|
369
376
|
|
370
|
-
One.new.meth(2,4).should == 8 # meth.2
|
371
|
-
Two.new.meth(2,4).should == 8 # meth.2
|
372
|
-
|
373
377
|
##############################################
|
374
378
|
# class One is now updated to the latest #
|
375
379
|
##############################################
|
380
|
+
|
381
|
+
One.new.meth(2,4).should == 8 # meth.2
|
382
|
+
Two.new.meth(2,4).should == 8 # meth.2
|
376
383
|
|
377
384
|
|
378
385
|
### Tagging/Naming
|
379
386
|
|
380
|
-
The use of Tags is central to the concept of
|
387
|
+
The use of Tags is central to the concept of Versioning. Tagging happens in the following ways:
|
381
388
|
|
382
|
-
Version1 =
|
389
|
+
Version1 = trait :function do
|
383
390
|
def meth arg
|
384
391
|
arg
|
385
392
|
end
|
@@ -394,28 +401,27 @@ The use of Tags is central to the concept of Injector Versioning. Tagging happe
|
|
394
401
|
end
|
395
402
|
end
|
396
403
|
|
397
|
-
Version1 and Version2 are two different hard
|
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).
|
398
405
|
|
399
406
|
### Local Binding
|
400
407
|
|
401
|
-
Before we move on, we also want to give some further treatment to
|
408
|
+
Before we move on, we also want to give some further treatment to trait 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 the code:
|
402
409
|
|
403
|
-
**Note: In the following examples we use the notion of version naming/tagging.
|
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.**
|
404
411
|
|
405
412
|
|
406
|
-
#
|
413
|
+
# trait declaration
|
407
414
|
#_____________________
|
408
415
|
|
409
|
-
Version1 =
|
416
|
+
Version1 = trait :functionality do
|
410
417
|
def basic arg # version basic.1
|
411
418
|
arg * 2
|
412
419
|
end
|
413
420
|
end
|
421
|
+
o = Object.new.extend Version1 # apply --snapshot (like above)
|
414
422
|
|
415
|
-
o = Object.new.enrich Version1 # apply --snapshot (like above)
|
416
|
-
o.basic(1).should == 2 # basic.1
|
417
423
|
|
418
|
-
#
|
424
|
+
# trait prolongation
|
419
425
|
#_____________________
|
420
426
|
|
421
427
|
Version2 = functionality do
|
@@ -427,54 +433,57 @@ Before we move on, we also want to give some further treatment to injector local
|
|
427
433
|
basic(3) + 2
|
428
434
|
end
|
429
435
|
end
|
436
|
+
p = Object.new.extend Version2 # apply --snapshot (like above)
|
430
437
|
|
431
|
-
|
438
|
+
####################################################
|
439
|
+
# #compound.1 bound to the right version #basic.2 #
|
440
|
+
####################################################
|
441
|
+
|
432
442
|
p.basic(1).should == 3 # basic.2
|
433
443
|
p.compound.should == 11 # compound.1 --bound locally to basic.2
|
434
444
|
|
435
445
|
o.basic(1).should == 2 # basic.1
|
436
446
|
o.compound.should == 11 # compound.1 --bound locally to basic.2
|
437
447
|
|
438
|
-
####################################################
|
439
|
-
# #compound.1 bound to the right version #basic.2 #
|
440
|
-
####################################################
|
441
|
-
|
442
448
|
|
443
449
|
### Virtual Method Cache (VMC)
|
444
450
|
|
445
|
-
When you are working with
|
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 code looks like:
|
446
452
|
|
447
453
|
# definition
|
448
|
-
|
454
|
+
#_______________
|
455
|
+
trait :SomeTrait
|
449
456
|
|
457
|
+
# application
|
458
|
+
#_______________
|
450
459
|
class MyClass
|
451
|
-
include
|
460
|
+
include SomeTrait # Application (with no methods)
|
452
461
|
end
|
453
462
|
|
454
463
|
obj = MyClass.new
|
455
464
|
|
456
|
-
|
457
|
-
def spm1 # spm1 is only defined in the virtual cache
|
465
|
+
SomeMethods do
|
466
|
+
def spm1 # #spm1 is only defined in the virtual cache
|
458
467
|
:result # It is not actually part of the class yet!!
|
459
468
|
end # until this version/prolongation is applied
|
460
469
|
end
|
461
470
|
|
462
471
|
expect(obj.spm1).to eq(:result) # yet my obj can use it --no problem
|
463
472
|
|
464
|
-
The key idea here is that the virtual method cache is the same for all versions of the Injector and all its applications. If we redefine
|
473
|
+
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 trait. To understand what we mean by this, take a look at following code:
|
465
474
|
|
466
475
|
class Client
|
467
|
-
include
|
476
|
+
include trait :J1
|
468
477
|
end
|
469
478
|
J1 do
|
470
479
|
def n1m1
|
471
480
|
end
|
472
|
-
include
|
481
|
+
include trait :K1
|
473
482
|
end
|
474
483
|
K1 do
|
475
484
|
def n2m1
|
476
485
|
end
|
477
|
-
include
|
486
|
+
include trait :L1
|
478
487
|
end
|
479
488
|
L1 do
|
480
489
|
def n3m1
|
@@ -488,11 +497,13 @@ The key idea here is that the virtual method cache is the same for all versions
|
|
488
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.
|
489
498
|
|
490
499
|
#### #define\_method sym, &blk
|
491
|
-
There is one more interesting property to method definition
|
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.
|
492
501
|
|
493
502
|
Here is an example of the difference with #define\_method:
|
494
503
|
|
495
|
-
|
504
|
+
# define trait
|
505
|
+
#_________________________
|
506
|
+
trait :some_trait do
|
496
507
|
def meth
|
497
508
|
:meth
|
498
509
|
end
|
@@ -502,147 +513,200 @@ Here is an example of the difference with #define\_method:
|
|
502
513
|
end
|
503
514
|
end
|
504
515
|
|
505
|
-
class Client
|
506
|
-
inject
|
507
|
-
end
|
508
|
-
|
516
|
+
class Client
|
517
|
+
inject some_trait # Injector appplied
|
518
|
+
end
|
519
|
+
|
520
|
+
# test it
|
521
|
+
|
509
522
|
Client.new.meth.should == :meth
|
510
523
|
Client.new.foo_bar.should == 'a foo and a bar'
|
511
524
|
|
512
|
-
|
513
|
-
|
514
|
-
|
525
|
+
# new prolongation
|
526
|
+
#________________________
|
527
|
+
some_trait do
|
528
|
+
def meth
|
515
529
|
puts :them
|
516
530
|
end
|
517
531
|
|
518
|
-
define_method :foo_bar do #
|
532
|
+
define_method :foo_bar do # new method version
|
519
533
|
'fooooo and barrrrr'
|
520
534
|
end
|
521
535
|
end
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
Client.new.
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
536
|
+
|
537
|
+
################################
|
538
|
+
# Like above! #
|
539
|
+
# No re-injection == No change #
|
540
|
+
################################
|
541
|
+
|
542
|
+
Client.new.meth.should == :meth
|
543
|
+
|
544
|
+
################################
|
545
|
+
# Different!!! #
|
546
|
+
# No re-injection == Change #
|
547
|
+
# . Thanks to define_method #
|
548
|
+
################################
|
549
|
+
|
550
|
+
Client.new.foo_bar.should == 'fooooo and barrrrr'
|
551
|
+
|
552
|
+
|
553
|
+
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.
|
554
|
+
|
555
|
+
### Trait/Injector introspection
|
556
|
+
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
|
+
|
558
|
+
trait :Function do
|
539
559
|
def far
|
540
560
|
end
|
541
561
|
def close
|
542
562
|
end
|
543
563
|
end
|
544
564
|
|
545
|
-
|
565
|
+
trait :Style do
|
546
566
|
def pretty
|
547
567
|
end
|
548
568
|
end
|
549
569
|
|
550
|
-
class
|
551
|
-
inject Function()
|
570
|
+
class Parent
|
571
|
+
inject Function()
|
552
572
|
end
|
553
573
|
|
554
|
-
|
574
|
+
class Child < Parent
|
575
|
+
inject Style()
|
576
|
+
end
|
577
|
+
|
578
|
+
# a trait's class
|
555
579
|
|
556
580
|
Function().class.should == Injector
|
557
581
|
Style().class.should == Injector
|
558
582
|
|
559
|
-
|
560
|
-
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:
|
583
|
+
Injector == Trait
|
561
584
|
|
562
|
-
#
|
585
|
+
# traits methods
|
563
586
|
|
564
|
-
|
565
|
-
|
587
|
+
Function().instance_methods.should == [:far, :close]
|
588
|
+
Style().instance_methods.should == [:pretty]
|
589
|
+
|
590
|
+
# later on...
|
591
|
+
|
592
|
+
Child.eject *Child.traits
|
593
|
+
|
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 sub-mini API. An example use goes like this:
|
596
|
+
|
597
|
+
#traits --(in this target)
|
566
598
|
|
567
|
-
|
599
|
+
Child.traits
|
600
|
+
=> [(|Style|:#942460)]
|
568
601
|
|
569
|
-
|
570
|
-
=> [(#944120:|Function|)] # same as #injectors.collect_by_name :name
|
571
|
-
|
572
|
-
Target.injectors :all # all injectors in this class's hierarchy
|
573
|
-
(see section on Inheritance)
|
574
|
-
|
575
|
-
The method also extends into a minuscule API:
|
602
|
+
#traits :all --(all traits in hierarchy)
|
576
603
|
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
#
|
604
|
+
Child.traits :all
|
605
|
+
=> [(|Function|:#944120), (|Style|:#942460)]
|
606
|
+
|
607
|
+
#traits *sym
|
581
608
|
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
609
|
+
c = Child.new.extend Style()
|
610
|
+
c.traits :Style
|
611
|
+
=> [(|Style|:#942460), (|Style|:#890234)]
|
612
|
+
|
586
613
|
|
587
|
-
|
588
|
-
=> (#944120:|Function|)
|
589
|
-
# ...
|
590
|
-
Target.injectors(:all).find_by_name :name
|
591
|
-
# aliased to last_by_sym
|
614
|
+
#traits.by\_name *sym --(names only)
|
592
615
|
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
# later on...
|
597
|
-
|
598
|
-
# eject all injectors in target
|
599
|
-
Target.injectors.each{ |j| Target.eject j }
|
616
|
+
Child.traits.by_name.should == [:Style]
|
617
|
+
Child.traits(:all).by_name.should == [:Function, :Style]
|
618
|
+
# also aliased to :sym_list
|
600
619
|
|
601
|
-
#
|
620
|
+
#traits.collect\_by\_name *sym --(all #traits of :name, same as #traits *sym from above)
|
602
621
|
|
603
|
-
|
622
|
+
Child.traits.collect_by_name :Style
|
623
|
+
=> [(|Style|:#942460)]
|
624
|
+
|
625
|
+
Child.traits :Style
|
626
|
+
=> [(|Style|:#942460)]
|
627
|
+
|
628
|
+
Child.traits :Function
|
629
|
+
=> nil
|
630
|
+
|
631
|
+
Child.traits :all, :Function
|
632
|
+
=> [(|Function|:#944120)]
|
633
|
+
|
634
|
+
Child.traits(:all).collect_by_name :Function
|
635
|
+
=> [(|Function|:#944120)]
|
636
|
+
# also aliased to :all_by_sym
|
637
|
+
|
638
|
+
#traits.find\_by\_name *sym --(highest ranking trait by :name)
|
604
639
|
|
640
|
+
Child.traits.find_by_name :Style # last one in first out
|
641
|
+
=> (|Style|:#942460)
|
642
|
+
|
643
|
+
Child.traits.find_by_name :Function
|
644
|
+
=>nil
|
645
|
+
|
646
|
+
Child.traits(:all).find_by_name :Function
|
647
|
+
=> (|Function|:#944120)
|
648
|
+
# aliased to last_by_sym
|
649
|
+
|
605
650
|
#### #history alias #versions
|
606
|
-
This method returns a trace of all the
|
651
|
+
This method returns a trace of all the hosted Trait Injectors which is ordered based on the order in which they are created. It also includes the pseudo-hosted 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:
|
607
652
|
|
608
|
-
# create our
|
609
|
-
injector :HistorySample
|
610
|
-
|
653
|
+
# create our trait and
|
611
654
|
# host it a couple of times
|
612
|
-
|
655
|
+
|
656
|
+
trait :HistorySample
|
657
|
+
extend HistorySample(), HistorySample()
|
613
658
|
|
614
659
|
# expect the following
|
615
|
-
|
660
|
+
|
616
661
|
expect(HistorySample().history.size).to eq(2)
|
662
|
+
expect(traits).to eq(HistorySample().history)
|
663
|
+
expect(HistorySample().history.last).to eql(traits.last)
|
617
664
|
expect(HistorySample().history.last).to eql(HistorySample())
|
618
|
-
expect(HistorySample().history.last).to_not eq(HistorySample().spec)
|
619
665
|
|
620
|
-
# create a tag
|
666
|
+
# create a hard tag
|
667
|
+
|
621
668
|
HistorySampleTag = HistorySample()
|
622
669
|
|
623
670
|
expect(HistorySample().history.size).to eq(3)
|
624
671
|
expect(HistorySample().history.last).to equal(HistorySampleTag)
|
625
672
|
|
626
|
-
|
627
|
-
This method traces the tags only. Here is the code:
|
628
|
-
|
629
|
-
# at this point from the above
|
630
|
-
expect(HistorySample().tags.size).to eq(1)
|
673
|
+
# create a soft tags
|
631
674
|
|
632
675
|
HistorySample(:tag) do
|
633
676
|
# some definitions
|
634
677
|
end
|
635
678
|
|
636
|
-
|
637
|
-
expect(HistorySample().
|
679
|
+
expect(HistorySample().history.size).to eq(4)
|
680
|
+
expect(HistorySample().history.last).to equal(HistorySample())
|
681
|
+
|
682
|
+
#### #tags
|
683
|
+
This method traces the tags only. The method also extends into a sub-mini API returning hard and soft tags independently. Here is the code:
|
638
684
|
|
639
|
-
|
685
|
+
# at this point from the above...
|
686
|
+
|
687
|
+
#tags
|
688
|
+
|
689
|
+
expect(HistorySample().tags.size).to eq(3)
|
690
|
+
|
691
|
+
#tags.hard
|
692
|
+
|
693
|
+
expect(HistorySample().tags.hard.size).to eq(1)
|
694
|
+
HistorySample().tags.hard
|
695
|
+
=> [(HistorySampleTag:|HistorySample|)]
|
696
|
+
|
697
|
+
#tags.soft
|
698
|
+
|
699
|
+
expect(HistorySample().tags.soft.size).to eq(2)
|
700
|
+
HistorySample().tags.hard
|
701
|
+
=> [(|HistorySample|:#234435),(|HistorySample|:#876679)]
|
702
|
+
|
703
|
+
The reason for hard tags is related to inheritance while that of soft tags is connected to composition. For more on this take a look at the Solutions Pattern below for an application of soft tags and at JITI for 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.
|
640
704
|
|
641
705
|
#### #precedent and #progenitor (alias #pre, #pro)
|
642
706
|
The #pre method gets the previous element in the history. Here is the code:
|
643
707
|
|
644
|
-
# create the
|
645
|
-
|
708
|
+
# create the trait
|
709
|
+
trait :HistorySample
|
646
710
|
|
647
711
|
# create some history
|
648
712
|
extend HistorySample(), HistorySample()
|
@@ -650,13 +714,14 @@ The #pre method gets the previous element in the history. Here is the code:
|
|
650
714
|
# expect the following
|
651
715
|
expect(HistorySample().history.last.precedent).to equal(HistorySample().history.first)
|
652
716
|
|
653
|
-
The #pro method
|
717
|
+
The #pro method gets the version from which a particular trait was generated. This may not necessarily be the precedent. Take a look at the following code.
|
654
718
|
|
655
|
-
# create the
|
656
|
-
|
719
|
+
# create the trait
|
720
|
+
trait :Progample
|
657
721
|
|
658
722
|
# expect the following
|
659
723
|
expect(Progample().history).to be_empty
|
724
|
+
expect(Progample().precedent).to equal(Progample().spec)
|
660
725
|
expect(Progample().progenitor).to equal(Progample().spec)
|
661
726
|
|
662
727
|
# create some history
|
@@ -665,93 +730,215 @@ The #pro method is a little different. It gets the version from which a particu
|
|
665
730
|
# expect the following
|
666
731
|
expect(Progample().history.size).to eq(2)
|
667
732
|
expect(Progample().history.first.progenitor).to equal(Progample().spec)
|
668
|
-
expect(Progample().history.last.
|
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:
|
669
738
|
|
739
|
+
Tag = Progample()
|
740
|
+
expect(Tag.pro).to equal(Progample().spec)
|
741
|
+
|
742
|
+
class A
|
743
|
+
inject Tag
|
744
|
+
end
|
745
|
+
|
746
|
+
expect(A.traits.first.pro).to equal(Tag)
|
747
|
+
|
670
748
|
For more on this see the rspec files.
|
671
749
|
|
672
|
-
### Injector Equality and Difference
|
750
|
+
### Trait/Injector Equality and Difference
|
673
751
|
|
674
|
-
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
|
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 on the complement, finds the actual delta between traits and returns and array with those differences.
|
753
|
+
|
754
|
+
Here is how equality is defined:
|
675
755
|
|
676
756
|
# equality
|
677
757
|
######################################
|
678
|
-
E().should == E()
|
679
|
-
E().should == E().spec
|
680
758
|
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
759
|
+
E().should == E()
|
760
|
+
E().should == E().spec
|
761
|
+
E().should == E().pre
|
762
|
+
|
763
|
+
# if
|
764
|
+
ETag1 = E() # with no definitions
|
765
|
+
# then
|
766
|
+
E().should == ETag1 # same thing
|
767
|
+
|
768
|
+
# if
|
769
|
+
extend E() # with no definitions
|
770
|
+
# then
|
771
|
+
injectors.first.should == E() # same
|
772
|
+
|
773
|
+
# but
|
774
|
+
E().should == E() # always
|
775
|
+
E().should == E().spec
|
776
|
+
E(:tag).should == E()
|
777
|
+
|
778
|
+
|
779
|
+
|
780
|
+
Inequality is based on a trait's methods. Once you add method definitions to a trait, that trait tests as inequality to it precedent or progenitor provided this is not the original trait. The original trait is the #pre and #pro to all others. It always tests as equal to its handle, but versions past or since do not. A different trait with the same methods is also not equal to the trait.
|
781
|
+
|
782
|
+
Here is how inequality is defined:
|
687
783
|
|
784
|
+
# inequality
|
785
|
+
######################################
|
786
|
+
|
787
|
+
E().should_not == F()
|
688
788
|
|
689
|
-
#
|
789
|
+
# if some E () definitions **
|
690
790
|
E() do
|
691
791
|
def foo
|
692
792
|
end
|
693
793
|
end
|
694
|
-
# ** definition **
|
695
794
|
|
795
|
+
# then (from above)
|
796
|
+
ETag1.should_not == E()
|
696
797
|
|
697
|
-
#
|
698
|
-
|
699
|
-
E().should == E()
|
700
|
-
ETag1.should_not == E()
|
701
|
-
injectors.first.should_not == E()
|
798
|
+
# furthermore
|
799
|
+
traits.first.should_not == E()
|
702
800
|
|
703
|
-
|
704
|
-
E().should_not ==
|
801
|
+
# and
|
802
|
+
E().should_not == E().pre
|
705
803
|
|
804
|
+
# but
|
805
|
+
E().should == E() # always
|
806
|
+
E().should == E().spec
|
706
807
|
|
707
|
-
Here is how difference is defined:
|
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. The method also extends into a sub-mini API. Furthermore, the elements of the array which are arrays themselves also return a partial trait from their payload which can be used in further trait injection. Here is how difference is defined:
|
708
809
|
|
709
|
-
|
710
|
-
######################################
|
711
|
-
E().diff.class.should be(Array)
|
810
|
+
#### #trait.diff ver=nil
|
712
811
|
|
812
|
+
# difference
|
813
|
+
##################################
|
713
814
|
|
714
|
-
#
|
715
|
-
|
716
|
-
|
815
|
+
#diff(ver=nil) --( The argument ver=nil defaults to the previous version )
|
816
|
+
|
817
|
+
E().diff.class.should be(Array)
|
717
818
|
|
718
|
-
# because
|
719
|
-
E().should == E() # like above
|
720
819
|
|
820
|
+
#diff.empty? --( Is the delta empty? The join could still exist (see below). )
|
821
|
+
|
822
|
+
E().diff.should be_empty
|
823
|
+
|
721
824
|
|
722
|
-
|
723
|
-
|
724
|
-
|
825
|
+
# because
|
826
|
+
E().diff.delta.should be_empty
|
827
|
+
E().diff.join.should be_empty
|
828
|
+
|
829
|
+
|
830
|
+
# a tag to compare
|
831
|
+
ETag3 = E()
|
725
832
|
|
726
|
-
# because
|
727
|
-
E().diff.should == E().diff(E().pre)
|
728
|
-
E().pre.should equal( E().spec )
|
729
|
-
# and
|
730
|
-
E().should == E().spec # like above
|
731
833
|
|
834
|
+
# if some E() definitions **
|
835
|
+
E do
|
836
|
+
def foo
|
837
|
+
end
|
838
|
+
end
|
732
839
|
|
733
|
-
# unless there is a delta it cannot be loaded?
|
734
|
-
######################################
|
735
|
-
E().diff.should_not be_loaded
|
736
840
|
|
737
|
-
|
738
|
-
|
841
|
+
# E is changed so...
|
842
|
+
E().diff(ETag3).should_not be_empty
|
843
|
+
|
844
|
+
|
845
|
+
# because (like above)
|
846
|
+
ETag3.should_not == E()
|
847
|
+
|
848
|
+
# and
|
849
|
+
|
850
|
+
|
851
|
+
#diff.delta --( The difference in methods )
|
852
|
+
|
853
|
+
E().diff(ETag3).delta.should == [:foo]
|
854
|
+
|
855
|
+
|
856
|
+
#diff.loaded? --( Is there both a join and a delta? )
|
857
|
+
|
858
|
+
E().diff(ETag3).should_not be_loaded
|
859
|
+
|
860
|
+
# because
|
861
|
+
|
862
|
+
|
863
|
+
#diff.join --( The methods common to both )
|
864
|
+
|
865
|
+
E().diff(ETag3).join.should == []
|
866
|
+
|
867
|
+
|
868
|
+
# even though
|
869
|
+
E().diff(ETag3).delta.should == [:foo]
|
870
|
+
|
871
|
+
|
872
|
+
# furthermore
|
873
|
+
E().diff.should == [[], [:foo]]
|
874
|
+
|
875
|
+
|
876
|
+
# being that
|
877
|
+
E().diff.should eq( E().diff(E().precedent) )
|
878
|
+
# and
|
879
|
+
E().progenitor.should equal(E().spec)
|
880
|
+
|
881
|
+
|
882
|
+
#diff.join.injector
|
883
|
+
#diff.delta.injector
|
884
|
+
|
885
|
+
# a tag as precedent
|
886
|
+
ETag5 = E()
|
887
|
+
|
888
|
+
|
889
|
+
# if E() definitions **
|
890
|
+
E do
|
891
|
+
def foo
|
892
|
+
:foo
|
893
|
+
end
|
894
|
+
def bar
|
895
|
+
:bar
|
896
|
+
end
|
897
|
+
end
|
898
|
+
|
899
|
+
|
900
|
+
# then
|
901
|
+
E().diff.should_not be_empty
|
902
|
+
|
903
|
+
# being that
|
904
|
+
E().diff.join.should be_empty
|
905
|
+
E().diff.delta.should_not be_empty
|
906
|
+
# as for
|
907
|
+
E().diff.delta.injector.instance_methods.should == [:foo, :bar]
|
908
|
+
# and
|
909
|
+
E().diff.delta.injector.should_not eq(E().diff.join.injector)
|
910
|
+
|
911
|
+
# being that
|
912
|
+
E().diff.join.injector.instance_methods.should be_empty
|
913
|
+
E().diff.delta.injector.instance_methods.should_not be_empty
|
739
914
|
|
740
|
-
|
915
|
+
# allows the following
|
916
|
+
class Incomplete
|
917
|
+
inject E().diff.delta.injector
|
918
|
+
end
|
919
|
+
# and
|
920
|
+
Incomplete.new.foo.should eq(:foo)
|
921
|
+
|
922
|
+
# being that
|
923
|
+
E().diff.delta.injector.should be_instance_of(Injector)
|
924
|
+
E().diff.delta.injector.should be_instance_of(Trait)
|
925
|
+
|
741
926
|
|
927
|
+
|
928
|
+
The version argument can have the following forms: 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.
|
742
929
|
|
743
930
|
Again, for more on this see the rspec files.
|
744
931
|
|
745
|
-
### Injector composition
|
746
|
-
The composition of multiple
|
932
|
+
### Trait/Injector composition
|
933
|
+
The composition of multiple traits into an object can be specified as follows:
|
747
934
|
|
748
935
|
include Injectors
|
749
936
|
|
750
|
-
# declare
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
937
|
+
# declare traits
|
938
|
+
trait :FuelSystem # capitalized methods
|
939
|
+
trait :Engines
|
940
|
+
trait :Capsule
|
941
|
+
trait :Landing
|
755
942
|
|
756
943
|
# compose the object
|
757
944
|
class SpaceShip
|
@@ -759,12 +946,12 @@ The composition of multiple injectors into an object can be specified as follows
|
|
759
946
|
inject FuelSystem(), Engines(), Capsule(), Langing() # capitalized method use
|
760
947
|
|
761
948
|
def launch
|
762
|
-
gas_tank fuel_lines burners ignition :go
|
949
|
+
gas_tank fuel_lines burners ignition :go # call through the VMC
|
763
950
|
self
|
764
951
|
end
|
765
952
|
end
|
766
953
|
|
767
|
-
Spaceship.
|
954
|
+
Spaceship.traits.by_name == [:FuelSystem, :Engines, :Capsule, :Landing]
|
768
955
|
|
769
956
|
# define functionality
|
770
957
|
FuelSystem do
|
@@ -789,25 +976,30 @@ The composition of multiple injectors into an object can be specified as follows
|
|
789
976
|
var = 'wheels'
|
790
977
|
|
791
978
|
Landing do
|
792
|
-
define_method :gear do # a
|
979
|
+
define_method :gear do # a closure of surrounding context
|
793
980
|
var
|
794
981
|
end
|
795
982
|
end
|
983
|
+
|
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 traits and allows customizing traits to their targets.l
|
796
985
|
|
797
986
|
### Inheritance
|
798
|
-
The
|
987
|
+
Inheritance with traits comes in two forms. 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 inherits all the function from its progenitor allowing customization of only the parts needed for the application at hand but cannot call upon previous versions of itself. With JITI this later dimension of access to its ancestry is possible but we must be aware of colluding ancestry when creating decorators. Jackbox itself warns you of this however.
|
988
|
+
|
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 achieved with the use of the :all directive on the #trait/#injectors method. This behavior is partially specified by what follows:
|
990
|
+
|
991
|
+
trait :j
|
799
992
|
|
800
|
-
injector :j
|
801
|
-
|
802
993
|
class C
|
803
994
|
end
|
995
|
+
|
804
996
|
C.inject j { #foo pre-defined at time of injection
|
805
997
|
def foo
|
806
998
|
'foo'
|
807
999
|
end
|
808
1000
|
}
|
809
|
-
C.
|
810
|
-
C.new.
|
1001
|
+
C.traits.by_name.should == [:j]
|
1002
|
+
C.new.traits.by_name.should == [:j]
|
811
1003
|
|
812
1004
|
C.new.foo.should == 'foo'
|
813
1005
|
|
@@ -815,29 +1007,30 @@ The behavior of Injectors under inheritance is partially specified by what follo
|
|
815
1007
|
|
816
1008
|
class D < C # methods are inherited from j
|
817
1009
|
end
|
818
|
-
D.
|
819
|
-
D.
|
1010
|
+
D.traits.by_name.should == []
|
1011
|
+
D.traits(:all).by_name == [:j]
|
820
1012
|
|
821
1013
|
# New Objects
|
822
1014
|
C.new.foo.should == 'foo'
|
823
1015
|
D.new.foo.should == 'foo'
|
824
1016
|
|
1017
|
+
For mote on this as also see the rspec files.
|
825
1018
|
|
826
|
-
More importantly though is the following:
|
1019
|
+
More importantly though is the following example of trait inheritance due to versioning. As previously stated, the concept of tag/naming also plays an important role with inheritance, as illustrated in the following code:
|
827
1020
|
|
828
|
-
|
1021
|
+
trait :player do
|
829
1022
|
def sound
|
830
1023
|
'Lets make some music'
|
831
1024
|
end
|
832
1025
|
end
|
833
1026
|
|
834
|
-
TapePlayer = player do # TapePlayer
|
1027
|
+
TapePlayer = player do # TapePlayer version tag
|
835
1028
|
def play # --inherirts #sound
|
836
1029
|
return 'Tape playing...' + sound()
|
837
1030
|
end
|
838
1031
|
end
|
839
1032
|
|
840
|
-
CDPlayer = player do # CDPlayer
|
1033
|
+
CDPlayer = player do # CDPlayer version tag
|
841
1034
|
def play # --also inherits #sound
|
842
1035
|
return 'CD playing...' + sound()
|
843
1036
|
end
|
@@ -851,19 +1044,21 @@ More importantly though is the following:
|
|
851
1044
|
end
|
852
1045
|
end
|
853
1046
|
|
854
|
-
|
1047
|
+
class JukeBox < BoomBox
|
1048
|
+
inject CDPlayer
|
1049
|
+
end
|
855
1050
|
|
856
|
-
|
857
|
-
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.
|
858
1051
|
|
859
|
-
|
1052
|
+
The different versions inherit all of the pre-existing methods from the current trait and freeze 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 trait until the time an update is made. Of course, there is always #define\_method and the VMC. For more on all this see, the Rspec examples.
|
860
1053
|
|
861
|
-
|
1054
|
+
### Just-In-Time Inheritance (JITI)
|
1055
|
+
|
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 classes. In addition to the inheritance resulting from versioning, JITI presents a more complete scenario adding color to the inheritance picture painted by trait injectors. The key takeaway here is that traits are a form of mix-in that share an enhanced but similar inheritance model with classes. 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:
|
862
1057
|
|
863
1058
|
#
|
864
|
-
# Our
|
1059
|
+
# Our Trait
|
865
1060
|
#
|
866
|
-
Tag1 =
|
1061
|
+
Tag1 = trait :Functionality do
|
867
1062
|
def m1
|
868
1063
|
1
|
869
1064
|
end
|
@@ -874,19 +1069,19 @@ This flavor of the inheritance model allows our modular closures to have similar
|
|
874
1069
|
end
|
875
1070
|
|
876
1071
|
#
|
877
|
-
# Normal
|
1072
|
+
# Normal versioned inheritance
|
878
1073
|
#
|
879
|
-
|
1074
|
+
Functionality do
|
880
1075
|
def other
|
881
1076
|
'other' # -- same ancestors as before
|
882
1077
|
end
|
883
1078
|
end
|
884
1079
|
|
885
|
-
expect(
|
1080
|
+
expect(Functionality().ancestors).to eql( [Functionality()] )
|
886
1081
|
|
887
1082
|
# test it
|
888
1083
|
|
889
|
-
o = Object.new.extend(
|
1084
|
+
o = Object.new.extend(Functionality())
|
890
1085
|
|
891
1086
|
# inherited
|
892
1087
|
o.m1.should == 1
|
@@ -897,10 +1092,10 @@ This flavor of the inheritance model allows our modular closures to have similar
|
|
897
1092
|
|
898
1093
|
|
899
1094
|
#
|
900
|
-
#
|
1095
|
+
# JITI
|
901
1096
|
#
|
902
|
-
Tag2 =
|
903
|
-
def m1 # The :m1 override invokes JIT
|
1097
|
+
Tag2 = Functionality do
|
1098
|
+
def m1 # The :m1 override invokes JIT Inheritance
|
904
1099
|
super + 1 # -- Tag1 is summoned into ancestor chain
|
905
1100
|
end # -- allows the use of super
|
906
1101
|
|
@@ -921,70 +1116,84 @@ This flavor of the inheritance model allows our modular closures to have similar
|
|
921
1116
|
p.m3.should == 'em3'
|
922
1117
|
p.other.should == 'other'
|
923
1118
|
|
924
|
-
expect(
|
1119
|
+
expect(Functionality().ancestors).to eql( [Functionality(), Tag1] )
|
925
1120
|
expect(Tag2.ancestors).to eql( [Tag2, Tag1] )
|
926
1121
|
|
927
|
-
|
1122
|
+
### The Rules of JITI
|
928
1123
|
|
929
|
-
|
930
|
-
|
1124
|
+
JITI (Just-In-Time Inheritance) is governed by a set of rules framing its behavior. Here are these rules and their descriptions:
|
1125
|
+
|
1126
|
+
1. JITI works like class inheritance but as a 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 under object extension. It works under class inclusion.
|
1127
|
+
2. The trait handle is always in sync with the last hard tag until purposefully changed. This also means the handle definitions use the last hard tag as a departing base for any further changes.
|
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. But, It forces internal basing once applied. Definitions internal to the trait always take precedence over external definitions by the same signature. This blocks external ancestor intrusion enforcing internal trait consistency.
|
1130
|
+
5. It keeps the VMC in proper working order. Like all traits, the VMC is always available as a cache of methods available globally to all versions of the trait.
|
1131
|
+
6. Directives are allowed. Also like all traits, JITI traits respond to normal trait injector directives.
|
1132
|
+
|
1133
|
+
For more on this please see the rspec files in the project, or on the gem itself, and also visit our blog at http://jackbox.us
|
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.
|
931
1136
|
|
932
1137
|
---
|
933
1138
|
|
934
1139
|
|
935
1140
|
### The GOF Decorator Pattern:
|
936
|
-
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.
|
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.
|
937
1142
|
|
938
|
-
Here is the code
|
1143
|
+
Here is the code:
|
939
1144
|
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
1145
|
+
class Coffee
|
1146
|
+
def cost
|
1147
|
+
1.50
|
1148
|
+
end
|
1149
|
+
end
|
945
1150
|
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
cup = Coffee.new.enrich(milk).enrich(vanilla)
|
958
|
-
cup.should be_instance_of(Coffee)
|
1151
|
+
trait :milk do
|
1152
|
+
def cost
|
1153
|
+
super() + 0.30
|
1154
|
+
end
|
1155
|
+
end
|
1156
|
+
trait :vanilla do
|
1157
|
+
def cost
|
1158
|
+
super() + 0.15
|
1159
|
+
end
|
1160
|
+
end
|
959
1161
|
|
960
|
-
|
1162
|
+
cup = Coffee.new.enrich(milk).enrich(vanilla)
|
1163
|
+
cup.should be_instance_of(Coffee)
|
961
1164
|
|
1165
|
+
cup.cost.should == 1.95
|
962
1166
|
|
963
|
-
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:
|
964
1167
|
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
1168
|
+
Additionally, these same decorators can then be re-applied MULTIPLE TIMES to the same receiver. This is something that is normally not possible with the regular Ruby base language. Here is the code:
|
1169
|
+
|
1170
|
+
cup = Coffee.new.enrich(milk).enrich(vanilla).enrich(vanilla)
|
1171
|
+
|
1172
|
+
# or even...
|
1173
|
+
|
1174
|
+
cup = Coffee.new.enrich milk, vanilla, vanilla
|
970
1175
|
|
971
|
-
|
972
|
-
|
973
|
-
|
1176
|
+
cup.cost.should == 2.10
|
1177
|
+
cup.should be_instance_of(Coffee)
|
1178
|
+
cup.traits.should == [:milk, :vanilla, :vanilla]
|
1179
|
+
|
1180
|
+
# Important Note:
|
1181
|
+
# vanilla and coffe can be div tags
|
1182
|
+
# and other markup in html or...
|
974
1183
|
|
975
1184
|
|
976
|
-
### Other Capabilities of Injectors
|
1185
|
+
### Other Capabilities of Trait Injectors
|
977
1186
|
|
978
|
-
The functionality of Injectors can be removed from individual targets
|
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.
|
979
1188
|
|
980
|
-
Here
|
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:
|
981
1190
|
|
982
1191
|
class Coffee
|
983
1192
|
def cost
|
984
1193
|
1.00
|
985
1194
|
end
|
986
1195
|
end
|
987
|
-
|
1196
|
+
trait :milk do
|
988
1197
|
def cost
|
989
1198
|
super() + 0.50
|
990
1199
|
end
|
@@ -996,7 +1205,7 @@ Here is an Injector removed after an #enrich to individual instance:
|
|
996
1205
|
cup.cost.should == 1.50
|
997
1206
|
friends_cup.cost.should == 1.50
|
998
1207
|
|
999
|
-
cup.
|
1208
|
+
cup.cancel :milk
|
1000
1209
|
|
1001
1210
|
cup.cost.should == 1.00
|
1002
1211
|
|
@@ -1007,7 +1216,7 @@ Here it is removed after an #inject at the class level:
|
|
1007
1216
|
|
1008
1217
|
# create the injection
|
1009
1218
|
class Home
|
1010
|
-
|
1219
|
+
trait :layout do
|
1011
1220
|
def fractal
|
1012
1221
|
end
|
1013
1222
|
end
|
@@ -1030,20 +1239,20 @@ Here it is removed after an #inject at the class level:
|
|
1030
1239
|
expect{Home.new.fractal}.to raise_error
|
1031
1240
|
|
1032
1241
|
|
1033
|
-
The code for these examples makes use of the #eject method which
|
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.
|
1034
1243
|
|
1035
|
-
#### #
|
1036
|
-
This method ejects
|
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.
|
1037
1246
|
|
1038
1247
|
### Injector Directives
|
1039
|
-
Once you have
|
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.
|
1040
1249
|
|
1041
|
-
#### :collapse directive
|
1042
|
-
This description produces similar results to the one for
|
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:
|
1043
1252
|
|
1044
|
-
The case with multiple
|
1253
|
+
The case with multiple object instances:
|
1045
1254
|
|
1046
|
-
|
1255
|
+
trait :copiable do
|
1047
1256
|
def object_copy
|
1048
1257
|
'a dubious copy'
|
1049
1258
|
end
|
@@ -1064,7 +1273,7 @@ The case with multiple objects
|
|
1064
1273
|
The case with a class receiver:
|
1065
1274
|
|
1066
1275
|
class SomeClass
|
1067
|
-
|
1276
|
+
trait :code do
|
1068
1277
|
def tester
|
1069
1278
|
'boo'
|
1070
1279
|
end
|
@@ -1082,19 +1291,13 @@ The case with a class receiver:
|
|
1082
1291
|
a.tester.should == nil
|
1083
1292
|
b.tester.should == nil
|
1084
1293
|
|
1085
|
-
# further
|
1086
|
-
SomeClass.eject :code
|
1087
|
-
expect{ a.tester }.to raise_error
|
1088
|
-
expect{ b.tester }.to raise_error
|
1089
|
-
|
1090
|
-
|
1091
1294
|
|
1092
|
-
#### :rebuild directive
|
1093
|
-
Injectors that have been collapsed can at a later point
|
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:
|
1094
1297
|
|
1095
1298
|
The case with multiple object receivers:
|
1096
1299
|
|
1097
|
-
|
1300
|
+
trait :reenforcer do
|
1098
1301
|
def thick_walls
|
1099
1302
|
'=====|||====='
|
1100
1303
|
end
|
@@ -1117,7 +1320,7 @@ The case with multiple object receivers:
|
|
1117
1320
|
The case with a class receiver:
|
1118
1321
|
|
1119
1322
|
class SomeBloatedObject
|
1120
|
-
|
1323
|
+
trait :ThinFunction do
|
1121
1324
|
def perform
|
1122
1325
|
'do the deed'
|
1123
1326
|
end
|
@@ -1132,9 +1335,11 @@ The case with a class receiver:
|
|
1132
1335
|
SomeBloatedObject.ThinFunction :active # alias to :rebuild
|
1133
1336
|
tester.perform.should == 'do the deed'
|
1134
1337
|
|
1338
|
+
#### :tag/:version directive
|
1339
|
+
This directive creates a soft tagged version of a trait. For more on this see Soft Tags below.
|
1135
1340
|
|
1136
1341
|
#### :implode directive
|
1137
|
-
This directive totally destroys the
|
1342
|
+
This directive totally destroys the trait including the handle to it. Use it carefully!
|
1138
1343
|
|
1139
1344
|
class Model
|
1140
1345
|
def feature
|
@@ -1142,7 +1347,7 @@ This directive totally destroys the injector including the handle to it. Use it
|
|
1142
1347
|
end
|
1143
1348
|
end
|
1144
1349
|
|
1145
|
-
|
1350
|
+
trait :extras do
|
1146
1351
|
def feature
|
1147
1352
|
super() + ' plus some extras'
|
1148
1353
|
end
|
@@ -1166,7 +1371,7 @@ This directive totally destroys the injector including the handle to it. Use it
|
|
1166
1371
|
}.to raise_error(NameError, /extras/)
|
1167
1372
|
|
1168
1373
|
### The GOF Strategy Pattern:
|
1169
|
-
Another pattern that Jackbox helps with is the GOF Strategy Pattern. This is a pattern
|
1374
|
+
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.
|
1170
1375
|
|
1171
1376
|
Here are a couple alternate implementations:
|
1172
1377
|
|
@@ -1189,7 +1394,7 @@ Here are a couple alternate implementations:
|
|
1189
1394
|
cup.strategy.should == 'normal'
|
1190
1395
|
|
1191
1396
|
|
1192
|
-
|
1397
|
+
trait :sweedish do
|
1193
1398
|
def brew
|
1194
1399
|
@strategy = 'sweedish'
|
1195
1400
|
end
|
@@ -1199,11 +1404,10 @@ Here are a couple alternate implementations:
|
|
1199
1404
|
cup.brew
|
1200
1405
|
cup.strategy.should == ('sweedish')
|
1201
1406
|
|
1202
|
-
|
1203
|
-
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:
|
1407
|
+
But, with #cancel/#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:
|
1204
1408
|
|
1205
1409
|
class Tea < Coffee # Tea is a type of coffee!! ;~Q)
|
1206
|
-
|
1410
|
+
trait :SpecialStrategy do
|
1207
1411
|
def brew
|
1208
1412
|
@strategy = 'special'
|
1209
1413
|
end
|
@@ -1230,19 +1434,20 @@ Just like hard tags above but a name is not needed:
|
|
1230
1434
|
:foo
|
1231
1435
|
end
|
1232
1436
|
end
|
1437
|
+
SomeJack :tag # Unnamed version
|
1233
1438
|
|
1234
|
-
SomeJack(:tag) do
|
1439
|
+
SomeJack(:tag) do # New unnamed version
|
1235
1440
|
def foo
|
1236
1441
|
:foooooooo
|
1237
1442
|
end
|
1238
1443
|
end
|
1239
1444
|
|
1240
|
-
Accessible through
|
1445
|
+
Accessible through trait#tags (an Array). Also available **trait#tags.hard** and **trait#tags.soft**. See introspection above.
|
1241
1446
|
|
1242
1447
|
---
|
1243
1448
|
### Patterns of a Different Flavor
|
1244
1449
|
|
1245
|
-
|
1450
|
+
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:
|
1246
1451
|
|
1247
1452
|
__1) Late Decorator.-__ Another flow that also benefits from #define\_method in an interesting way is the following:
|
1248
1453
|
|
@@ -1253,7 +1458,7 @@ __1) Late Decorator.-__ Another flow that also benefits from #define\_method in
|
|
1253
1458
|
end
|
1254
1459
|
w = Widget.new
|
1255
1460
|
|
1256
|
-
|
1461
|
+
trait :decorator
|
1257
1462
|
|
1258
1463
|
w.enrich decorator, decorator, decorator, decorator
|
1259
1464
|
|
@@ -1261,18 +1466,18 @@ __1) Late Decorator.-__ Another flow that also benefits from #define\_method in
|
|
1261
1466
|
bid = 3.5
|
1262
1467
|
|
1263
1468
|
decorator do
|
1264
|
-
define_method :cost do # defines function on all
|
1469
|
+
define_method :cost do # defines function on all traits of the class
|
1265
1470
|
super() + bid
|
1266
1471
|
end
|
1267
1472
|
end
|
1268
1473
|
|
1269
1474
|
w.cost.should == 15
|
1270
1475
|
|
1271
|
-
The actual
|
1476
|
+
The actual decorating trait function is late bound and defined only after some other data is available.
|
1272
1477
|
|
1273
1478
|
__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:
|
1274
1479
|
|
1275
|
-
|
1480
|
+
trait :Superb
|
1276
1481
|
|
1277
1482
|
Superb do
|
1278
1483
|
def process string, additives, index
|
@@ -1285,7 +1490,7 @@ __2) The Super Pattern.-__ No. This is not a superlative kind of pattern. Simp
|
|
1285
1490
|
Superb().process( 'food ', 'aeiu', 0 ).should == 'fuud fiid feed faad '
|
1286
1491
|
Superb(:implode)
|
1287
1492
|
|
1288
|
-
__3) The
|
1493
|
+
__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:
|
1289
1494
|
|
1290
1495
|
jack :Solution
|
1291
1496
|
|
@@ -1329,7 +1534,7 @@ __3) The Transformer Pattern.-__ For a specific example of what can be accompli
|
|
1329
1534
|
|
1330
1535
|
Client.solve
|
1331
1536
|
|
1332
|
-
__4) The
|
1537
|
+
__4) The Class Constructor Pattern.-__ Our base method #lets has one more interesting use which allows for an alternative way to refine classes. We have originally termed this Re-Classing but after further consideration and user input we have refocused the intent of this pattern and we now define it as class constructors. Look at the following code:
|
1333
1538
|
|
1334
1539
|
module Work
|
1335
1540
|
lets String do
|
@@ -1357,7 +1562,7 @@ __4) The Re-Classing Pattern.-__ Our base method #lets has one more interesting
|
|
1357
1562
|
str = 'Men-At-Work'
|
1358
1563
|
|
1359
1564
|
|
1360
|
-
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
|
1565
|
+
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 class's methods.
|
1361
1566
|
|
1362
1567
|
jack :Log do
|
1363
1568
|
require 'logger'
|
@@ -1383,7 +1588,7 @@ For more on this see, the rspec files and the Jackbox blog at <a href="http://ja
|
|
1383
1588
|
|
1384
1589
|
#### #reclass?(klass)
|
1385
1590
|
|
1386
|
-
This helper verifies a certain re-class exists within the current namespace. It returns a boolean.
|
1591
|
+
This helper verifies a certain re-class exists within the current namespace. It returns a boolean. Example:
|
1387
1592
|
|
1388
1593
|
module One
|
1389
1594
|
if reclass? String
|
@@ -1392,8 +1597,7 @@ This helper verifies a certain re-class exists within the current namespace. It
|
|
1392
1597
|
end
|
1393
1598
|
|
1394
1599
|
|
1395
|
-
|
1396
|
-
For more information and additional examples see the rspec examples on this project. There you'll find a long list of over __200__ rspec examples and code showcasing some additional features of Jackbox Injectors along with some additional descriptions.
|
1600
|
+
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 Injectors along with some additional descriptions.
|
1397
1601
|
|
1398
1602
|
---
|
1399
1603
|
## Additional Tools
|