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 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
+