traits 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  $__TRAIT_DEBUG__ = ENV['__TRAIT_DEBUG__'] || ENV['TRAIT_DEBUG'] || ENV['DEBUG']
2
- $__TRAIT_VERSION__ = "0.9.0"
2
+ $__TRAIT_VERSION__ = "0.9.1"
3
3
 
4
4
  class Object
5
5
  #--{{{
@@ -670,18 +670,44 @@ end
670
670
 
671
671
  module TraitInit
672
672
  #--{{{
673
- def trait_init opts = {}
674
- #--{{{
675
- opts.each do |k,v|
676
- k = "#{ k }"
677
- if respond_to? k
678
- send k, v
679
- else
680
- raise ArgumentError, "invalid trait -- #{ self.class }##{ k }"
673
+ module InstaceMethods
674
+ def trait_init *argv
675
+ #--{{{
676
+ args, opts = argv.partition{|arg| not Hash === arg}
677
+ args.flatten!
678
+ opts = opts.inject({}){|h,h2| h.update h2}
679
+ msgs = r_traits
680
+ args.each{|arg| send msgs.shift, v}
681
+ opts.each do |k,v|
682
+ k = "#{ k }"
683
+ if respond_to? k
684
+ send k, v
685
+ else
686
+ raise ArgumentError, "invalid trait -- #{ self.class }##{ k }"
687
+ end
681
688
  end
689
+ #--}}}
682
690
  end
691
+ alias_method "traitinit", "trait_init"
692
+ end
693
+ module ClassMethods
694
+ def trait_initialize *a, &b
695
+ traits *a unless a.empty?
696
+ module_eval{
697
+ def initialize(*a, &b)
698
+ super() if defined? super
699
+ trait_init *a
700
+ end
701
+ }
702
+ end
703
+ alias_method "traitinitialize", "trait_initialize"
704
+ end
705
+ def self.included other
706
+ #--{{{
707
+ other.extend ClassMethods
708
+ other.module_eval{ include InstaceMethods }
709
+ super
683
710
  #--}}}
684
711
  end
685
- alias_method "traitinit", "trait_init"
686
712
  #--}}}
687
713
  end
@@ -0,0 +1,22 @@
1
+ require 'traits'
2
+ #
3
+ # defining a trait is like attr_accessor in the simple case
4
+ #
5
+ class C
6
+ trait :t
7
+ end
8
+
9
+ o = C::new
10
+ o.t = 42
11
+ p o.t
12
+
13
+ #
14
+ # and can be made even shorter
15
+ #
16
+
17
+ class B; has :x; end
18
+
19
+ o = B::new
20
+ o.x = 42
21
+ p o.x
22
+
@@ -0,0 +1,14 @@
1
+ require 'traits'
2
+ #
3
+ # multiple traits can be defined at once using a list/array of string/sybmol
4
+ # arguments
5
+ #
6
+ class C
7
+ has :t0, :t1
8
+ has %w( t2 t3 )
9
+ end
10
+
11
+ obj = C::new
12
+ obj.t0 = 4
13
+ obj.t3 = 2
14
+ print obj.t0, obj.t3, "\n"
@@ -0,0 +1,49 @@
1
+ require 'traits'
2
+ #
3
+ # a hash argument can be used to specify default values
4
+ #
5
+ class C
6
+ has 'a' => 4, :b => 2
7
+ end
8
+
9
+ o = C::new
10
+ print o.a, o.b, "\n"
11
+
12
+ #
13
+ # and these traits are smartly inherited
14
+ #
15
+ class K < C; end
16
+
17
+ o = K::new
18
+ o.a = 40
19
+ p( o.a + o.b ) # note that we pick up a default b from C class here since it
20
+ # has not been set
21
+
22
+ o.a = 42
23
+ o.b = nil
24
+ p( o.b || o.a ) # but not here since we've explicitly set it to nil
25
+
26
+ #
27
+ # if a block is specifed as the default the initialization of the default value
28
+ # is deferred until needed which makes for quite natural trait definitions. the
29
+ # block is passed 'self' so references to the current object can be made. (if
30
+ # this were not done 'self' in the block would be bound to the class!)
31
+ #
32
+
33
+ class C
34
+ class << self
35
+ has('classname'){ name.upcase }
36
+ end
37
+
38
+ has('classname'){ self.class.classname.downcase }
39
+ end
40
+
41
+ class B < C; end
42
+
43
+ o = C::new
44
+ p C::classname
45
+ p o.classname
46
+
47
+ o = B::new
48
+ p B::classname
49
+ p o.classname
@@ -0,0 +1,30 @@
1
+ require 'traits'
2
+ #
3
+ # all behaviours work within class scope (metal/singleton-class) to define
4
+ # class methods
5
+ #
6
+ class C
7
+ class << self
8
+ traits 'a' => 4, 'b' => 2
9
+ end
10
+ end
11
+
12
+ print C::a, C::b, "\n"
13
+
14
+ #
15
+ # singleton methods can even be defined on objects
16
+ #
17
+
18
+ class << (a = %w[dog cat ostrich])
19
+ has 'category' => 'pets'
20
+ end
21
+ p a.category
22
+
23
+ #
24
+ # and modules
25
+ #
26
+ module Mmmm
27
+ class << self; trait 'good' => 'bacon'; end
28
+ end
29
+
30
+ p Mmmm.good
@@ -0,0 +1,10 @@
1
+ require 'traits'
2
+ #
3
+ # shorhands exit to enter 'class << self' in order to define class traits
4
+ #
5
+ class C
6
+ class_trait 'a' => 4
7
+ c_has :b => 2
8
+ end
9
+
10
+ print C::a, C::b, "\n"
@@ -0,0 +1,25 @@
1
+ require 'traits'
2
+ #
3
+ # as traits are defined they are remembered and can be accessed
4
+ #
5
+ class C
6
+ class_trait :first_class_method
7
+ trait :first_instance_method
8
+ end
9
+
10
+ class C
11
+ class_trait :second_class_method
12
+ trait :second_instance_method
13
+ end
14
+
15
+ #
16
+ # readers and writers are remembered separatedly
17
+ #
18
+ p C::class_reader_traits
19
+ p C::instance_writer_traits
20
+
21
+ #
22
+ # and can be gotten together at class or instance level
23
+ #
24
+ p C::class_traits
25
+ p C::traits
@@ -0,0 +1,16 @@
1
+ require 'traits'
2
+ #
3
+ # another neat feature is that they are remembered per hierarchy
4
+ #
5
+ class C
6
+ class_traits :base_class_method
7
+ trait :base_instance_method
8
+ end
9
+
10
+ class K < C
11
+ class_traits :derived_class_method
12
+ trait :derived_instance_method
13
+ end
14
+
15
+ p C::class_traits
16
+ p K::class_traits
@@ -0,0 +1,17 @@
1
+ require 'traits'
2
+ #
3
+ # a depth first search path is used to find defaults
4
+ #
5
+ class C
6
+ has 'a' => 42
7
+ end
8
+ class K < C; end
9
+
10
+ k = K::new
11
+ p k.a
12
+
13
+ #
14
+ # once assigned this is short-circuited
15
+ #
16
+ k.a = 'forty-two'
17
+ p k.a
@@ -0,0 +1,36 @@
1
+ require 'traits'
2
+ #
3
+ # getters and setters can be defined separately
4
+ #
5
+ class C
6
+ has_r :r
7
+ end
8
+ class D
9
+ has_w :w
10
+ end
11
+
12
+ #
13
+ # defining a reader trait still defines __public__ query and __private__ writer
14
+ # methods
15
+ #
16
+ class C
17
+ def using_private_writer_and_query
18
+ p r?
19
+ self.r = 42
20
+ p r
21
+ end
22
+ end
23
+ C::new.using_private_writer_and_query
24
+
25
+ #
26
+ # defining a writer trait still defines __private__ query and __private__ reader
27
+ # methods
28
+ #
29
+ class D
30
+ def using_private_reader
31
+ p w?
32
+ self.w = 'forty-two'
33
+ p w
34
+ end
35
+ end
36
+ D::new.using_private_reader
@@ -0,0 +1,23 @@
1
+ require 'traits'
2
+ #
3
+ # getters delegate to setters iff called with arguments
4
+ #
5
+ class AbstractWidget
6
+ class_trait 'color' => 'pinky-green'
7
+ class_trait 'size' => 42
8
+ class_trait 'shape' => 'square'
9
+
10
+ # we define instance traits which get their default from the class
11
+ %w( color size shape ).each{|t| trait(t){self.class.send t}}
12
+
13
+ def inspect
14
+ "color <#{ color }> size <#{ size }> shape <#{ shape }>"
15
+ end
16
+ end
17
+
18
+ class BlueWidget < AbstractWidget
19
+ color 'blue'
20
+ size 420
21
+ end
22
+
23
+ p BlueWidget::new
@@ -0,0 +1,36 @@
1
+ require 'traits'
2
+ #
3
+ # the rememberance of traits can make generic intializers pretty slick
4
+ #
5
+ class C
6
+ #
7
+ # define class traits with defaults
8
+ #
9
+ class_traits(
10
+ 'a' => 40,
11
+ 'b' => 1,
12
+ 'c' => 0
13
+ )
14
+
15
+ #
16
+ # define instance traits whose defaults come from readable class ones
17
+ #
18
+ class_rtraits.each{|ct| instance_trait ct => send(ct)}
19
+
20
+ #
21
+ # any option we respond_to? clobbers defaults
22
+ #
23
+ def initialize opts = {}
24
+ opts.each{|k,v| send(k,v) if respond_to? k}
25
+ end
26
+
27
+ #
28
+ # show anything we can read
29
+ #
30
+ def inspect
31
+ self.class.rtraits.inject(0){|n,t| n += send(t)}
32
+ end
33
+ end
34
+
35
+ c = C::new 'c' => 1
36
+ p c
@@ -0,0 +1,15 @@
1
+ require 'traits'
2
+ #
3
+ # even defining single methods on object behaves
4
+ #
5
+ a = []
6
+
7
+ class << a
8
+ trait 'singleton_class' => class << self;self;end
9
+
10
+ class << self
11
+ class_trait 'x' => 42
12
+ end
13
+ end
14
+
15
+ p a.singleton_class.x
@@ -0,0 +1,24 @@
1
+ require 'traits'
2
+ #
3
+ # pre and post hooks can be passed a proc or the name of a method, the arity is
4
+ # detected and the proc/method sent either the value, or the name/value pair
5
+ #
6
+
7
+ class C
8
+ HOOK_A = lambda{|value| puts "HOOK_A : #{ value }"}
9
+ HOOK_B = lambda{|name, value| puts "HOOK_B : #{ name } = #{ value }"}
10
+
11
+ def hook_a value
12
+ puts "hook_a : #{ value }"
13
+ end
14
+ def hook_b name, value
15
+ puts "hook_b : #{ name } = #{ value }"
16
+ end
17
+
18
+ trait 'x', 'pre' => HOOK_A, 'post' => 'hook_b'
19
+ trait 'y', 'pre' => HOOK_B, 'post' => 'hook_a'
20
+ end
21
+
22
+ c = C::new
23
+ c.x = 42
24
+ c.y = 'forty-two'
@@ -0,0 +1,37 @@
1
+ require 'traits'
2
+ #
3
+ # two kinds of in-place modifications are supported : casting and munging.
4
+ # casting is a hook that requires either a proc or the name of a method that
5
+ # will be used to convert the objects type. munging is similar execpt the
6
+ # method is called on the object itself. like all hooks, lists may be provided
7
+ # instead of a single argument
8
+ #
9
+ # you'll notice that the hooks and methods defined here are not strictly needed,
10
+ # but are for illustration purposes only. note that all hooks operate in the
11
+ # context of self - they have access to instance vars, etc., like instance_eval
12
+ #
13
+
14
+ class C
15
+ INT = lambda{|i| int i}
16
+ def int i
17
+ Integer i
18
+ end
19
+ trait 'a', 'cast' => 'int'
20
+ trait 'b', 'cast' => INT
21
+ trait 'c', 'munge' => 'to_i'
22
+ trait 'd', 'cast' => 'Integer'
23
+ trait 'e', 'munge' => %w( to_i abs )
24
+ end
25
+
26
+ c = C::new
27
+
28
+ c.a = '42'
29
+ p c.a
30
+ c.b = '42'
31
+ p c.b
32
+ c.c = '42'
33
+ p c.c
34
+ c.d = '42'
35
+ p c.d
36
+ c.e = '-42'
37
+ p c.e
@@ -0,0 +1,23 @@
1
+ require 'traits'
2
+ #
3
+ # the TraitInit module provide a simple method for initializing an object's
4
+ # traits from an options hash
5
+ #
6
+
7
+ class C
8
+ include TraitInit
9
+
10
+ LIST_OF_INTS = lambda{|a| Array === a and a.map{|i| Integer === i}.all?}
11
+ LIST_OF_STRINGS = lambda{|a| Array === a and a.map{|s| String === s}.all?}
12
+
13
+ trait :li, :validate => LIST_OF_INTS
14
+ trait :ls, :validate => LIST_OF_STRINGS
15
+
16
+ def initialize opts = {}
17
+ trait_init opts
18
+ end
19
+ end
20
+
21
+ c = C::new "li" => [4, 2], "ls" => %w[4 2]
22
+ p c.li.join
23
+ p c.ls.join