From: "Ara.T.Howard" Date: 2006-08-22T00:24:19+09:00 Subject: [ANN] traits-0.9.2 - better living through metaprogramming URLS http://rubyforge.org/projects/codeforpeople/ http://codeforpeople.com/lib/ruby/traits ABOUT traits.rb is set of attr_* like methods on steroids, caffeine, and botox. it encourages better living through meta-programming and uniform access priciples. traits.rb supports smart inheritence of class attributes and a fistful of hooks for veryifying and munging attr values. VERSION 0.9.2 HISTORY 0.9.2 - fixed a bug where list traits, for example trait 'letters' => %w[ a b c ] were flattened in a way that exploded trait initialization - streamlined TraitInit module - added OpenTraits class and otraits method conf = otraits{ port 42 host 'forty-two' } p conf.port #=> 42 p conf.host #=> 'forty-two' conf.username 'zaphod' p conf #=> {"username"=>"zaphod", "port"=>42, "host"=>"forty-two"} AUTHOR ara [dot] t [dot] howard [at] noaa [dot] gov SAMPLES <========< sample/a.rb >========> ~ > cat sample/a.rb require 'traits' # # defining a trait is like attr_accessor in the simple case # class C trait :t end o = C::new o.t = 42 p o.t # # and can be made even shorter # class B; has :x; end o = B::new o.x = 42 p o.x ~ > ruby sample/a.rb 42 42 <========< sample/b.rb >========> ~ > cat sample/b.rb require 'traits' # # multiple traits can be defined at once using a list/array of string/sybmol # arguments # class C has :t0, :t1 has %w( t2 t3 ) end obj = C::new obj.t0 = 4 obj.t3 = 2 print obj.t0, obj.t3, "\n" ~ > ruby sample/b.rb 42 <========< sample/c.rb >========> ~ > cat sample/c.rb require 'traits' # # a hash argument can be used to specify default values # class C has 'a' => 4, :b => 2 end o = C::new print o.a, o.b, "\n" # # and these traits are smartly inherited # class K < C; end o = K::new o.a = 40 p( o.a + o.b ) # note that we pick up a default b from C class here since it # has not been set o.a = 42 o.b = nil p( o.b || o.a ) # but not here since we've explicitly set it to nil # # if a block is specifed as the default the initialization of the default value # is deferred until needed which makes for quite natural trait definitions. the # block is passed 'self' so references to the current object can be made. (if # this were not done 'self' in the block would be bound to the class!) # class C class << self has('classname'){ name.upcase } end has('classname'){ self.class.classname.downcase } end class B < C; end o = C::new p C::classname p o.classname o = B::new p B::classname p o.classname ~ > ruby sample/c.rb 42 42 42 "C" "c" "B" "b" <========< sample/d.rb >========> ~ > cat sample/d.rb require 'traits' # # all behaviours work within class scope (metal/singleton-class) to define # class methods # class C class << self traits 'a' => 4, 'b' => 2 end end print C::a, C::b, "\n" # # singleton methods can even be defined on objects # class << (a = %w[dog cat ostrich]) has 'category' => 'pets' end p a.category # # and modules # module Mmmm class << self; trait 'good' => 'bacon'; end end p Mmmm.good ~ > ruby sample/d.rb 42 "pets" "bacon" <========< sample/e.rb >========> ~ > cat sample/e.rb require 'traits' # # shorhands exit to enter 'class << self' in order to define class traits # class C class_trait 'a' => 4 c_has :b => 2 end print C::a, C::b, "\n" ~ > ruby sample/e.rb 42 <========< sample/f.rb >========> ~ > cat sample/f.rb require 'traits' # # as traits are defined they are remembered and can be accessed # class C class_trait :first_class_method trait :first_instance_method end class C class_trait :second_class_method trait :second_instance_method end # # readers and writers are remembered separatedly # p C::class_reader_traits p C::instance_writer_traits # # and can be gotten together at class or instance level # p C::class_traits p C::traits ~ > ruby sample/f.rb ["first_class_method", "second_class_method"] ["first_instance_method=", "second_instance_method="] [["first_class_method", "first_class_method="], ["second_class_method", "second_class_method="]] [["first_instance_method", "first_instance_method="], ["second_instance_method", "second_instance_method="]] <========< sample/g.rb >========> ~ > cat sample/g.rb require 'traits' # # another neat feature is that they are remembered per hierarchy # class C class_traits :base_class_method trait :base_instance_method end class K < C class_traits :derived_class_method trait :derived_instance_method end p C::class_traits p K::class_traits ~ > ruby sample/g.rb [["base_class_method", "base_class_method="]] [["derived_class_method", "derived_class_method="], ["base_class_method", "base_class_method="]] <========< sample/h.rb >========> ~ > cat sample/h.rb require 'traits' # # a depth first search path is used to find defaults # class C has 'a' => 42 end class K < C; end k = K::new p k.a # # once assigned this is short-circuited # k.a = 'forty-two' p k.a ~ > ruby sample/h.rb 42 "forty-two" <========< sample/i.rb >========> ~ > cat sample/i.rb require 'traits' # # getters and setters can be defined separately # class C has_r :r end class D has_w :w end # # defining a reader trait still defines __public__ query and __private__ writer # methods # class C def using_private_writer_and_query p r? self.r = 42 p r end end C::new.using_private_writer_and_query # # defining a writer trait still defines __private__ query and __private__ reader # methods # class D def using_private_reader p w? self.w = 'forty-two' p w end end D::new.using_private_reader ~ > ruby sample/i.rb false 42 false "forty-two" <========< sample/j.rb >========> ~ > cat sample/j.rb require 'traits' # # getters delegate to setters iff called with arguments # class AbstractWidget class_trait 'color' => 'pinky-green' class_trait 'size' => 42 class_trait 'shape' => 'square' # we define instance traits which get their default from the class %w( color size shape ).each{|t| trait(t){self.class.send t}} def inspect "color <#{ color }> size <#{ size }> shape <#{ shape }>" end end class BlueWidget < AbstractWidget color 'blue' size 420 end p BlueWidget::new ~ > ruby sample/j.rb color size <420> shape <========< sample/k.rb >========> ~ > cat sample/k.rb require 'traits' # # the rememberance of traits can make generic intializers pretty slick # class C # # define class traits with defaults # class_traits( 'a' => 40, 'b' => 1, 'c' => 0 ) # # define instance traits whose defaults come from readable class ones # class_rtraits.each{|ct| instance_trait ct => send(ct)} # # any option we respond_to? clobbers defaults # def initialize opts = {} opts.each{|k,v| send(k,v) if respond_to? k} end # # show anything we can read # def inspect self.class.rtraits.inject(0){|n,t| n += send(t)} end end c = C::new 'c' => 1 p c ~ > ruby sample/k.rb 42 <========< sample/l.rb >========> ~ > cat sample/l.rb require 'traits' # # even defining single methods on object behaves # a = [] class << a trait 'singleton_class' => class << self;self;end class << self class_trait 'x' => 42 end end p a.singleton_class.x ~ > ruby sample/l.rb 42 <========< sample/m.rb >========> ~ > cat sample/m.rb require 'traits' # # pre and post hooks can be passed a proc or the name of a method, the arity is # detected and the proc/method sent either the value, or the name/value pair # class C HOOK_A = lambda{|value| puts "HOOK_A : #{ value }"} HOOK_B = lambda{|name, value| puts "HOOK_B : #{ name } = #{ value }"} def hook_a value puts "hook_a : #{ value }" end def hook_b name, value puts "hook_b : #{ name } = #{ value }" end trait 'x', 'pre' => HOOK_A, 'post' => 'hook_b' trait 'y', 'pre' => HOOK_B, 'post' => 'hook_a' end c = C::new c.x = 42 c.y = 'forty-two' ~ > ruby sample/m.rb HOOK_A : 42 hook_b : x = 42 HOOK_B : y = forty-two hook_a : forty-two <========< sample/n.rb >========> ~ > cat sample/n.rb require 'traits' # # two kinds of in-place modifications are supported : casting and munging. # casting is a hook that requires either a proc or the name of a method that # will be used to convert the objects type. munging is similar execpt the # method is called on the object itself. like all hooks, lists may be provided # instead of a single argument # # you'll notice that the hooks and methods defined here are not strictly needed, # but are for illustration purposes only. note that all hooks operate in the # context of self - they have access to instance vars, etc., like instance_eval # class C INT = lambda{|i| int i} def int i Integer i end trait 'a', 'cast' => 'int' trait 'b', 'cast' => INT trait 'c', 'munge' => 'to_i' trait 'd', 'cast' => 'Integer' trait 'e', 'munge' => %w( to_i abs ) end c = C::new c.a = '42' p c.a c.b = '42' p c.b c.c = '42' p c.c c.d = '42' p c.d c.e = '-42' p c.e ~ > ruby sample/n.rb 42 42 42 42 42 <========< sample/p.rb >========> ~ > cat sample/p.rb require 'traits' # # the TraitInit module provide a simple method for initializing an object's # traits from an options hash # class C include TraitInit LIST_OF_INTS = lambda{|a| Array === a and a.map{|i| Integer === i}.all?} LIST_OF_STRINGS = lambda{|a| Array === a and a.map{|s| String === s}.all?} trait :li, :validate => LIST_OF_INTS trait :ls, :validate => LIST_OF_STRINGS def initialize opts = {} trait_init opts end end c = C::new "li" => [4, 2], "ls" => %w[4 2] p c.li p c.ls ~ > ruby sample/p.rb [4, 2] ["4", "2"] <========< sample/q.rb >========> ~ > cat sample/q.rb require 'traits' # # the OpenTraits class is similar to an OpenStruct but, imho, easier to use. # the otraits shorthand can be used to contruct one # # # options passed as args dynamically create and init traits # config = otraits :port => 42 p config.port # # any passed block does the same but, via a method missing hood and traits # getter/setters, the syntax is very clean # config = otraits{ port 42 host 'forty-two' } p config.port p config.host config.username 'zaphod' p config ~ > ruby sample/q.rb 42 42 "forty-two" {"username"=>"zaphod", "port"=>42, "host"=>"forty-two"} CAVEATS this library is experimental and subject to change - though it has not for several versions and much of my code hinges is on it now so you can expect the interface to be stable in the near future - the only changes planned are those that fix bugs or add features. LICENSE same as ruby's -a -- to foster inner awareness, introspection, and reasoning is more efficient than meditation and prayer. - h.h. the 14th dali lama