traits 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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
+