traits 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +686 -0
- data/README.tmpl +136 -0
- data/gemspec.rb +23 -0
- data/gen_readme.rb +29 -0
- data/install.rb +201 -0
- data/lib/{traits-0.9.0.rb → traits-0.9.1.rb} +36 -10
- data/lib/traits.rb +36 -10
- data/sample/a.rb +22 -0
- data/sample/b.rb +14 -0
- data/sample/c.rb +49 -0
- data/sample/d.rb +30 -0
- data/sample/e.rb +10 -0
- data/sample/f.rb +25 -0
- data/sample/g.rb +16 -0
- data/sample/h.rb +17 -0
- data/sample/i.rb +36 -0
- data/sample/j.rb +23 -0
- data/sample/k.rb +36 -0
- data/sample/l.rb +15 -0
- data/sample/m.rb +24 -0
- data/sample/n.rb +37 -0
- data/sample/p.rb +23 -0
- metadata +25 -3
data/README
ADDED
@@ -0,0 +1,686 @@
|
|
1
|
+
URLS
|
2
|
+
|
3
|
+
http://rubyforge.org/projects/codeforpeople/
|
4
|
+
http://codeforpeople.com/lib/ruby/traits
|
5
|
+
|
6
|
+
ABOUT
|
7
|
+
|
8
|
+
traits.rb is set of attr_* like methods on steroids, caffeine, and botox. it
|
9
|
+
encourages better living through meta-programming and uniform access
|
10
|
+
priciples. traits.rb supports smart inheritence of class attributes and a
|
11
|
+
fistful of hooks for veryifying and munging attr values.
|
12
|
+
|
13
|
+
VERSION
|
14
|
+
|
15
|
+
0.9.1
|
16
|
+
|
17
|
+
HISTORY
|
18
|
+
|
19
|
+
0.9.0
|
20
|
+
- luke kaines made quite a few suggestions and bug reports that enabled this
|
21
|
+
release including making a few methods indifferent about string/symbol
|
22
|
+
args/keys and the introduction of a simple method 'trait_init' that can be
|
23
|
+
used to create keyword based initializers, eg:
|
24
|
+
|
25
|
+
require 'traits'
|
26
|
+
|
27
|
+
class C
|
28
|
+
include TraitInit
|
29
|
+
|
30
|
+
trait :a, :type => Integer
|
31
|
+
trait :b, :type => Integer
|
32
|
+
|
33
|
+
def initialize opts = {}
|
34
|
+
trait_init opts
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
C::new :a => 4, :b => 2
|
39
|
+
|
40
|
+
0.8.0
|
41
|
+
- traits now supports a whole slew of hooks that can be registered to fire
|
42
|
+
pre or post setting an attribute, to cast a value to another type, to
|
43
|
+
munge a value destructively, to require only certain types, to require a
|
44
|
+
certain ducktype signature, and to validate arguments passed. check out
|
45
|
+
sample/m.rb, sample/n.rb, or sample.o.rb to see it in action. the
|
46
|
+
mechanism is quite flexible allowing method names, lambdas of varying
|
47
|
+
arity, and lists of either/or to be passed to any hook.
|
48
|
+
|
49
|
+
- you can find a gem for trais on codeforpeople - but i've still not coded
|
50
|
+
up automated updating from codeforpeople to rubyforge so it won't show up
|
51
|
+
as a remote gem yet.
|
52
|
+
|
53
|
+
0.7.0
|
54
|
+
- patched in the support i had written eariler for 'hooks' to be called
|
55
|
+
pre/post setting a trait. plus shortcut to 'validate' traits which simply
|
56
|
+
sets up a 'pre' hook which is used as a predicate. eg:
|
57
|
+
|
58
|
+
class C; trait 'number', 'validate' => proc{|n| Numeric === n}
|
59
|
+
|
60
|
+
pre and post hooks are used in the same way, eg:
|
61
|
+
|
62
|
+
class C
|
63
|
+
trait 'a',
|
64
|
+
'pre' => proc{|val| p "#{ val } to set with"},
|
65
|
+
'post' => proc{|val| p "#{ val } set"},
|
66
|
+
end
|
67
|
+
|
68
|
+
but the really cool thing is that all of these blocks are both passed the
|
69
|
+
value in question but also evaluate with 'self' set appropriately. eg
|
70
|
+
|
71
|
+
class Car
|
72
|
+
positive_int = lambda{|n| Fixnum === n and n > 0}
|
73
|
+
legal = proc{|s| s < speed_limit}
|
74
|
+
|
75
|
+
trait 'speed_limit', 'validate' => positive_int, 'default' => 42
|
76
|
+
trait 'speed', 'validate' => legal
|
77
|
+
end
|
78
|
+
|
79
|
+
c = Car::new
|
80
|
+
c.speed = 115
|
81
|
+
|
82
|
+
works as you'd expect:
|
83
|
+
|
84
|
+
(eval):14:in `speed=': validation of speed=(115) failed! (ArgumentError)
|
85
|
+
from a.rb:13
|
86
|
+
|
87
|
+
0.6.0
|
88
|
+
- fixed bug in where a default trait given as an empty array, eg:
|
89
|
+
|
90
|
+
class C; has 'a' => []; end
|
91
|
+
|
92
|
+
was exploded into the empty list when passed to the setter to initialize
|
93
|
+
the default value.
|
94
|
+
|
95
|
+
0.5.0
|
96
|
+
- general code cleanup
|
97
|
+
|
98
|
+
0.4.0
|
99
|
+
- tweaked writer code so multiple values can be passed to setters
|
100
|
+
- tweaked method of running blocks to use instance_eval so explicit 'this'
|
101
|
+
arg is no longer needed (though it can still be used)
|
102
|
+
|
103
|
+
0.3.0
|
104
|
+
added ability of default values to be specified with block for deferred
|
105
|
+
context sensitive initialization (see sample/c.rb)
|
106
|
+
|
107
|
+
0.1.0
|
108
|
+
|
109
|
+
completely reworked impl so NO parsing of inspect strings is required -
|
110
|
+
it's all straight methods (albeit quite confusing ones) now. the
|
111
|
+
interface is unchanged.
|
112
|
+
|
113
|
+
0.0.0
|
114
|
+
|
115
|
+
initial version
|
116
|
+
|
117
|
+
|
118
|
+
AUTHOR
|
119
|
+
|
120
|
+
ara [dot] t [dot] howard [at] noaa [dot] gov
|
121
|
+
|
122
|
+
SAMPLES
|
123
|
+
|
124
|
+
<========< sample/a.rb >========>
|
125
|
+
|
126
|
+
~ > cat sample/a.rb
|
127
|
+
|
128
|
+
require 'traits'
|
129
|
+
#
|
130
|
+
# defining a trait is like attr_accessor in the simple case
|
131
|
+
#
|
132
|
+
class C
|
133
|
+
trait :t
|
134
|
+
end
|
135
|
+
|
136
|
+
o = C::new
|
137
|
+
o.t = 42
|
138
|
+
p o.t
|
139
|
+
|
140
|
+
#
|
141
|
+
# and can be made even shorter
|
142
|
+
#
|
143
|
+
|
144
|
+
class B; has :x; end
|
145
|
+
|
146
|
+
o = B::new
|
147
|
+
o.x = 42
|
148
|
+
p o.x
|
149
|
+
|
150
|
+
|
151
|
+
~ > ruby sample/a.rb
|
152
|
+
|
153
|
+
42
|
154
|
+
42
|
155
|
+
|
156
|
+
|
157
|
+
<========< sample/b.rb >========>
|
158
|
+
|
159
|
+
~ > cat sample/b.rb
|
160
|
+
|
161
|
+
require 'traits'
|
162
|
+
#
|
163
|
+
# multiple traits can be defined at once using a list/array of string/sybmol
|
164
|
+
# arguments
|
165
|
+
#
|
166
|
+
class C
|
167
|
+
has :t0, :t1
|
168
|
+
has %w( t2 t3 )
|
169
|
+
end
|
170
|
+
|
171
|
+
obj = C::new
|
172
|
+
obj.t0 = 4
|
173
|
+
obj.t3 = 2
|
174
|
+
print obj.t0, obj.t3, "\n"
|
175
|
+
|
176
|
+
~ > ruby sample/b.rb
|
177
|
+
|
178
|
+
42
|
179
|
+
|
180
|
+
|
181
|
+
<========< sample/c.rb >========>
|
182
|
+
|
183
|
+
~ > cat sample/c.rb
|
184
|
+
|
185
|
+
require 'traits'
|
186
|
+
#
|
187
|
+
# a hash argument can be used to specify default values
|
188
|
+
#
|
189
|
+
class C
|
190
|
+
has 'a' => 4, :b => 2
|
191
|
+
end
|
192
|
+
|
193
|
+
o = C::new
|
194
|
+
print o.a, o.b, "\n"
|
195
|
+
|
196
|
+
#
|
197
|
+
# and these traits are smartly inherited
|
198
|
+
#
|
199
|
+
class K < C; end
|
200
|
+
|
201
|
+
o = K::new
|
202
|
+
o.a = 40
|
203
|
+
p( o.a + o.b ) # note that we pick up a default b from C class here since it
|
204
|
+
# has not been set
|
205
|
+
|
206
|
+
o.a = 42
|
207
|
+
o.b = nil
|
208
|
+
p( o.b || o.a ) # but not here since we've explicitly set it to nil
|
209
|
+
|
210
|
+
#
|
211
|
+
# if a block is specifed as the default the initialization of the default value
|
212
|
+
# is deferred until needed which makes for quite natural trait definitions. the
|
213
|
+
# block is passed 'self' so references to the current object can be made. (if
|
214
|
+
# this were not done 'self' in the block would be bound to the class!)
|
215
|
+
#
|
216
|
+
|
217
|
+
class C
|
218
|
+
class << self
|
219
|
+
has('classname'){ name.upcase }
|
220
|
+
end
|
221
|
+
|
222
|
+
has('classname'){ self.class.classname.downcase }
|
223
|
+
end
|
224
|
+
|
225
|
+
class B < C; end
|
226
|
+
|
227
|
+
o = C::new
|
228
|
+
p C::classname
|
229
|
+
p o.classname
|
230
|
+
|
231
|
+
o = B::new
|
232
|
+
p B::classname
|
233
|
+
p o.classname
|
234
|
+
|
235
|
+
~ > ruby sample/c.rb
|
236
|
+
|
237
|
+
42
|
238
|
+
42
|
239
|
+
42
|
240
|
+
"C"
|
241
|
+
"c"
|
242
|
+
"B"
|
243
|
+
"b"
|
244
|
+
|
245
|
+
|
246
|
+
<========< sample/d.rb >========>
|
247
|
+
|
248
|
+
~ > cat sample/d.rb
|
249
|
+
|
250
|
+
require 'traits'
|
251
|
+
#
|
252
|
+
# all behaviours work within class scope (metal/singleton-class) to define
|
253
|
+
# class methods
|
254
|
+
#
|
255
|
+
class C
|
256
|
+
class << self
|
257
|
+
traits 'a' => 4, 'b' => 2
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
print C::a, C::b, "\n"
|
262
|
+
|
263
|
+
#
|
264
|
+
# singleton methods can even be defined on objects
|
265
|
+
#
|
266
|
+
|
267
|
+
class << (a = %w[dog cat ostrich])
|
268
|
+
has 'category' => 'pets'
|
269
|
+
end
|
270
|
+
p a.category
|
271
|
+
|
272
|
+
#
|
273
|
+
# and modules
|
274
|
+
#
|
275
|
+
module Mmmm
|
276
|
+
class << self; trait 'good' => 'bacon'; end
|
277
|
+
end
|
278
|
+
|
279
|
+
p Mmmm.good
|
280
|
+
|
281
|
+
~ > ruby sample/d.rb
|
282
|
+
|
283
|
+
42
|
284
|
+
"pets"
|
285
|
+
"bacon"
|
286
|
+
|
287
|
+
|
288
|
+
<========< sample/e.rb >========>
|
289
|
+
|
290
|
+
~ > cat sample/e.rb
|
291
|
+
|
292
|
+
require 'traits'
|
293
|
+
#
|
294
|
+
# shorhands exit to enter 'class << self' in order to define class traits
|
295
|
+
#
|
296
|
+
class C
|
297
|
+
class_trait 'a' => 4
|
298
|
+
c_has :b => 2
|
299
|
+
end
|
300
|
+
|
301
|
+
print C::a, C::b, "\n"
|
302
|
+
|
303
|
+
~ > ruby sample/e.rb
|
304
|
+
|
305
|
+
42
|
306
|
+
|
307
|
+
|
308
|
+
<========< sample/f.rb >========>
|
309
|
+
|
310
|
+
~ > cat sample/f.rb
|
311
|
+
|
312
|
+
require 'traits'
|
313
|
+
#
|
314
|
+
# as traits are defined they are remembered and can be accessed
|
315
|
+
#
|
316
|
+
class C
|
317
|
+
class_trait :first_class_method
|
318
|
+
trait :first_instance_method
|
319
|
+
end
|
320
|
+
|
321
|
+
class C
|
322
|
+
class_trait :second_class_method
|
323
|
+
trait :second_instance_method
|
324
|
+
end
|
325
|
+
|
326
|
+
#
|
327
|
+
# readers and writers are remembered separatedly
|
328
|
+
#
|
329
|
+
p C::class_reader_traits
|
330
|
+
p C::instance_writer_traits
|
331
|
+
|
332
|
+
#
|
333
|
+
# and can be gotten together at class or instance level
|
334
|
+
#
|
335
|
+
p C::class_traits
|
336
|
+
p C::traits
|
337
|
+
|
338
|
+
~ > ruby sample/f.rb
|
339
|
+
|
340
|
+
["first_class_method", "second_class_method"]
|
341
|
+
["first_instance_method=", "second_instance_method="]
|
342
|
+
[["first_class_method", "first_class_method="], ["second_class_method", "second_class_method="]]
|
343
|
+
[["first_instance_method", "first_instance_method="], ["second_instance_method", "second_instance_method="]]
|
344
|
+
|
345
|
+
|
346
|
+
<========< sample/g.rb >========>
|
347
|
+
|
348
|
+
~ > cat sample/g.rb
|
349
|
+
|
350
|
+
require 'traits'
|
351
|
+
#
|
352
|
+
# another neat feature is that they are remembered per hierarchy
|
353
|
+
#
|
354
|
+
class C
|
355
|
+
class_traits :base_class_method
|
356
|
+
trait :base_instance_method
|
357
|
+
end
|
358
|
+
|
359
|
+
class K < C
|
360
|
+
class_traits :derived_class_method
|
361
|
+
trait :derived_instance_method
|
362
|
+
end
|
363
|
+
|
364
|
+
p C::class_traits
|
365
|
+
p K::class_traits
|
366
|
+
|
367
|
+
~ > ruby sample/g.rb
|
368
|
+
|
369
|
+
[["base_class_method", "base_class_method="]]
|
370
|
+
[["derived_class_method", "derived_class_method="], ["base_class_method", "base_class_method="]]
|
371
|
+
|
372
|
+
|
373
|
+
<========< sample/h.rb >========>
|
374
|
+
|
375
|
+
~ > cat sample/h.rb
|
376
|
+
|
377
|
+
require 'traits'
|
378
|
+
#
|
379
|
+
# a depth first search path is used to find defaults
|
380
|
+
#
|
381
|
+
class C
|
382
|
+
has 'a' => 42
|
383
|
+
end
|
384
|
+
class K < C; end
|
385
|
+
|
386
|
+
k = K::new
|
387
|
+
p k.a
|
388
|
+
|
389
|
+
#
|
390
|
+
# once assigned this is short-circuited
|
391
|
+
#
|
392
|
+
k.a = 'forty-two'
|
393
|
+
p k.a
|
394
|
+
|
395
|
+
~ > ruby sample/h.rb
|
396
|
+
|
397
|
+
42
|
398
|
+
"forty-two"
|
399
|
+
|
400
|
+
|
401
|
+
<========< sample/i.rb >========>
|
402
|
+
|
403
|
+
~ > cat sample/i.rb
|
404
|
+
|
405
|
+
require 'traits'
|
406
|
+
#
|
407
|
+
# getters and setters can be defined separately
|
408
|
+
#
|
409
|
+
class C
|
410
|
+
has_r :r
|
411
|
+
end
|
412
|
+
class D
|
413
|
+
has_w :w
|
414
|
+
end
|
415
|
+
|
416
|
+
#
|
417
|
+
# defining a reader trait still defines __public__ query and __private__ writer
|
418
|
+
# methods
|
419
|
+
#
|
420
|
+
class C
|
421
|
+
def using_private_writer_and_query
|
422
|
+
p r?
|
423
|
+
self.r = 42
|
424
|
+
p r
|
425
|
+
end
|
426
|
+
end
|
427
|
+
C::new.using_private_writer_and_query
|
428
|
+
|
429
|
+
#
|
430
|
+
# defining a writer trait still defines __private__ query and __private__ reader
|
431
|
+
# methods
|
432
|
+
#
|
433
|
+
class D
|
434
|
+
def using_private_reader
|
435
|
+
p w?
|
436
|
+
self.w = 'forty-two'
|
437
|
+
p w
|
438
|
+
end
|
439
|
+
end
|
440
|
+
D::new.using_private_reader
|
441
|
+
|
442
|
+
~ > ruby sample/i.rb
|
443
|
+
|
444
|
+
false
|
445
|
+
42
|
446
|
+
false
|
447
|
+
"forty-two"
|
448
|
+
|
449
|
+
|
450
|
+
<========< sample/j.rb >========>
|
451
|
+
|
452
|
+
~ > cat sample/j.rb
|
453
|
+
|
454
|
+
require 'traits'
|
455
|
+
#
|
456
|
+
# getters delegate to setters iff called with arguments
|
457
|
+
#
|
458
|
+
class AbstractWidget
|
459
|
+
class_trait 'color' => 'pinky-green'
|
460
|
+
class_trait 'size' => 42
|
461
|
+
class_trait 'shape' => 'square'
|
462
|
+
|
463
|
+
# we define instance traits which get their default from the class
|
464
|
+
%w( color size shape ).each{|t| trait(t){self.class.send t}}
|
465
|
+
|
466
|
+
def inspect
|
467
|
+
"color <#{ color }> size <#{ size }> shape <#{ shape }>"
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
class BlueWidget < AbstractWidget
|
472
|
+
color 'blue'
|
473
|
+
size 420
|
474
|
+
end
|
475
|
+
|
476
|
+
p BlueWidget::new
|
477
|
+
|
478
|
+
~ > ruby sample/j.rb
|
479
|
+
|
480
|
+
color <blue> size <420> shape <square>
|
481
|
+
|
482
|
+
|
483
|
+
<========< sample/k.rb >========>
|
484
|
+
|
485
|
+
~ > cat sample/k.rb
|
486
|
+
|
487
|
+
require 'traits'
|
488
|
+
#
|
489
|
+
# the rememberance of traits can make generic intializers pretty slick
|
490
|
+
#
|
491
|
+
class C
|
492
|
+
#
|
493
|
+
# define class traits with defaults
|
494
|
+
#
|
495
|
+
class_traits(
|
496
|
+
'a' => 40,
|
497
|
+
'b' => 1,
|
498
|
+
'c' => 0
|
499
|
+
)
|
500
|
+
|
501
|
+
#
|
502
|
+
# define instance traits whose defaults come from readable class ones
|
503
|
+
#
|
504
|
+
class_rtraits.each{|ct| instance_trait ct => send(ct)}
|
505
|
+
|
506
|
+
#
|
507
|
+
# any option we respond_to? clobbers defaults
|
508
|
+
#
|
509
|
+
def initialize opts = {}
|
510
|
+
opts.each{|k,v| send(k,v) if respond_to? k}
|
511
|
+
end
|
512
|
+
|
513
|
+
#
|
514
|
+
# show anything we can read
|
515
|
+
#
|
516
|
+
def inspect
|
517
|
+
self.class.rtraits.inject(0){|n,t| n += send(t)}
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
c = C::new 'c' => 1
|
522
|
+
p c
|
523
|
+
|
524
|
+
~ > ruby sample/k.rb
|
525
|
+
|
526
|
+
42
|
527
|
+
|
528
|
+
|
529
|
+
<========< sample/l.rb >========>
|
530
|
+
|
531
|
+
~ > cat sample/l.rb
|
532
|
+
|
533
|
+
require 'traits'
|
534
|
+
#
|
535
|
+
# even defining single methods on object behaves
|
536
|
+
#
|
537
|
+
a = []
|
538
|
+
|
539
|
+
class << a
|
540
|
+
trait 'singleton_class' => class << self;self;end
|
541
|
+
|
542
|
+
class << self
|
543
|
+
class_trait 'x' => 42
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
p a.singleton_class.x
|
548
|
+
|
549
|
+
~ > ruby sample/l.rb
|
550
|
+
|
551
|
+
42
|
552
|
+
|
553
|
+
|
554
|
+
<========< sample/m.rb >========>
|
555
|
+
|
556
|
+
~ > cat sample/m.rb
|
557
|
+
|
558
|
+
require 'traits'
|
559
|
+
#
|
560
|
+
# pre and post hooks can be passed a proc or the name of a method, the arity is
|
561
|
+
# detected and the proc/method sent either the value, or the name/value pair
|
562
|
+
#
|
563
|
+
|
564
|
+
class C
|
565
|
+
HOOK_A = lambda{|value| puts "HOOK_A : #{ value }"}
|
566
|
+
HOOK_B = lambda{|name, value| puts "HOOK_B : #{ name } = #{ value }"}
|
567
|
+
|
568
|
+
def hook_a value
|
569
|
+
puts "hook_a : #{ value }"
|
570
|
+
end
|
571
|
+
def hook_b name, value
|
572
|
+
puts "hook_b : #{ name } = #{ value }"
|
573
|
+
end
|
574
|
+
|
575
|
+
trait 'x', 'pre' => HOOK_A, 'post' => 'hook_b'
|
576
|
+
trait 'y', 'pre' => HOOK_B, 'post' => 'hook_a'
|
577
|
+
end
|
578
|
+
|
579
|
+
c = C::new
|
580
|
+
c.x = 42
|
581
|
+
c.y = 'forty-two'
|
582
|
+
|
583
|
+
~ > ruby sample/m.rb
|
584
|
+
|
585
|
+
HOOK_A : 42
|
586
|
+
hook_b : x = 42
|
587
|
+
HOOK_B : y = forty-two
|
588
|
+
hook_a : forty-two
|
589
|
+
|
590
|
+
|
591
|
+
<========< sample/n.rb >========>
|
592
|
+
|
593
|
+
~ > cat sample/n.rb
|
594
|
+
|
595
|
+
require 'traits'
|
596
|
+
#
|
597
|
+
# two kinds of in-place modifications are supported : casting and munging.
|
598
|
+
# casting is a hook that requires either a proc or the name of a method that
|
599
|
+
# will be used to convert the objects type. munging is similar execpt the
|
600
|
+
# method is called on the object itself. like all hooks, lists may be provided
|
601
|
+
# instead of a single argument
|
602
|
+
#
|
603
|
+
# you'll notice that the hooks and methods defined here are not strictly needed,
|
604
|
+
# but are for illustration purposes only. note that all hooks operate in the
|
605
|
+
# context of self - they have access to instance vars, etc., like instance_eval
|
606
|
+
#
|
607
|
+
|
608
|
+
class C
|
609
|
+
INT = lambda{|i| int i}
|
610
|
+
def int i
|
611
|
+
Integer i
|
612
|
+
end
|
613
|
+
trait 'a', 'cast' => 'int'
|
614
|
+
trait 'b', 'cast' => INT
|
615
|
+
trait 'c', 'munge' => 'to_i'
|
616
|
+
trait 'd', 'cast' => 'Integer'
|
617
|
+
trait 'e', 'munge' => %w( to_i abs )
|
618
|
+
end
|
619
|
+
|
620
|
+
c = C::new
|
621
|
+
|
622
|
+
c.a = '42'
|
623
|
+
p c.a
|
624
|
+
c.b = '42'
|
625
|
+
p c.b
|
626
|
+
c.c = '42'
|
627
|
+
p c.c
|
628
|
+
c.d = '42'
|
629
|
+
p c.d
|
630
|
+
c.e = '-42'
|
631
|
+
p c.e
|
632
|
+
|
633
|
+
~ > ruby sample/n.rb
|
634
|
+
|
635
|
+
42
|
636
|
+
42
|
637
|
+
42
|
638
|
+
42
|
639
|
+
42
|
640
|
+
|
641
|
+
|
642
|
+
<========< sample/p.rb >========>
|
643
|
+
|
644
|
+
~ > cat sample/p.rb
|
645
|
+
|
646
|
+
require 'traits'
|
647
|
+
#
|
648
|
+
# the TraitInit module provide a simple method for initializing an object's
|
649
|
+
# traits from an options hash
|
650
|
+
#
|
651
|
+
|
652
|
+
class C
|
653
|
+
include TraitInit
|
654
|
+
|
655
|
+
LIST_OF_INTS = lambda{|a| Array === a and a.map{|i| Integer === i}.all?}
|
656
|
+
LIST_OF_STRINGS = lambda{|a| Array === a and a.map{|s| String === s}.all?}
|
657
|
+
|
658
|
+
trait :li, :validate => LIST_OF_INTS
|
659
|
+
trait :ls, :validate => LIST_OF_STRINGS
|
660
|
+
|
661
|
+
def initialize opts = {}
|
662
|
+
trait_init opts
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
c = C::new "li" => [4, 2], "ls" => %w[4 2]
|
667
|
+
p c.li.join
|
668
|
+
p c.ls.join
|
669
|
+
|
670
|
+
~ > ruby sample/p.rb
|
671
|
+
|
672
|
+
"42"
|
673
|
+
"42"
|
674
|
+
|
675
|
+
|
676
|
+
CAVEATS
|
677
|
+
|
678
|
+
this library is experimental and subject to change - though it has not for
|
679
|
+
several versions and much of my code hinges is on it now so you can expect the
|
680
|
+
interface to be stable in the near future - the only changes planned are those
|
681
|
+
that fix bugs or add features.
|
682
|
+
|
683
|
+
LICENSE
|
684
|
+
|
685
|
+
same as ruby's
|
686
|
+
|