fattr
fattr copied to clipboard
fattr.rb is a "fatter attr" for ruby and borrows heavily from the metakoans.rb ruby quiz
NAME fattr.rb
INSTALL gem install fattrs
URIS http://github.com/ahoward/fattr http://rubyforge.org/projects/codeforpeople/ http://codeforpeople.com/
SYNOPSIS fattr.rb is a "fatter attr" for ruby
the implementation of fattr.rb borrows many of the best ideas from the metakoans.rb ruby quiz
http://www.rubyquiz.com/quiz67.html
in particular the solutions of Christian Neukirchen and Florian Gross along with concepts from my original traits.rb lib
key features provided by fattrs are
- ability to specify default values for attrs and definition time. values
can be literal objects or blocks, which are evaluated in the context of
self to initialize the variable
- classes remember which fattrs they've defined and this information is
available to client code
- a whole suite of methods is defined by calls to #fattrs including
getter, setter, query (var?) and banger (var! - which forces
re-initialization from the default value/block)
- ability to define multiple fattrs at once using key => value pairs
- fast lookup of whether or not a class has defined a certain fattr
- fattrs can be defined on objects on a per singleton basis
- getters acts as setters if an argument is given to them
- block caching, calling an fattr with a block sets the instance
variable to that block
- shortcuts for adding class/module level fattrs
- class inheritable attributes
all this in 156 lines of code
SAMPLES
<========< samples/a.rb >========>
~ > cat samples/a.rb
#
# basic usage is like attr, but note that fattr defines a suite of methods
#
require 'fattr'
class C
fattr 'a'
end
c = C.new
c.a = 42
p c.a #=> 42
p 'forty-two' if c.a? #=> 'forty-two'
#
# fattrs works on object too
#
o = Object.new
o.fattr 'answer' => 42
p o.answer #=> 42
~ > ruby samples/a.rb
42
"forty-two"
42
<========< samples/b.rb >========>
~ > cat samples/b.rb
#
# default values may be given either directly or as a block which will be
# evaluated in the context of self. in both cases (value or block) the
# default is set only once and only if needed - it's a lazy evaluation. the
# 'banger' method can be used to re-initialize a variable at any point whether
# or not it's already been initialized.
#
require 'fattr'
class C
fattr :a => 42
fattr(:b){ Float a }
end
c = C.new
p c.a #=> 42
p c.b #=> 42.0
c.a = 43
p c.a #=> 43
c.a!
p c.a #=> 42
~ > ruby samples/b.rb
42
42.0
43
42
<========< samples/c.rb >========>
~ > cat samples/c.rb
#
# multiple name=>default pairs can be given
#
require 'fattr'
class C
fattrs 'x' => 0b101000, 'y' => 0b10
end
c = C.new
z = c.x + c.y
p z #=> 42
~ > ruby samples/c.rb
42
<========< samples/d.rb >========>
~ > cat samples/d.rb
#
# a nice feature is that all fattrs are enumerated in the class. this,
# combined with the fact that the getter method is defined so as to delegate
# to the setter when an argument is given, means bulk initialization and/or
# fattr traversal is very easy.
#
require 'fattr'
class C
fattrs %w( x y z )
def fattrs
self.class.fattrs
end
def initialize
fattrs.each_with_index{|a,i| send a, i}
end
def to_hash
fattrs.inject({}){|h,a| h.update a => send(a)}
end
def inspect
to_hash.inspect
end
end
c = C.new
p c.fattrs
p c
c.x 'forty-two'
p c.x
~ > ruby samples/d.rb
["x", "y", "z"]
{"x"=>0, "y"=>1, "z"=>2}
"forty-two"
<========< samples/e.rb >========>
~ > cat samples/e.rb
#
# my favourite element of fattrs is that getters can also be setters.
# this allows incredibly clean looking code like
#
require 'fattr'
class Config
fattrs %w( host port)
def initialize(&block) instance_eval &block end
end
conf = Config.new{
host 'codeforpeople.org'
port 80
}
p conf
~ > ruby samples/e.rb
samples/e.rb:7: Use RbConfig instead of obsolete and deprecated Config.
samples/e.rb:7:in `<main>': Config is not a class (TypeError)
<========< samples/f.rb >========>
~ > cat samples/f.rb
#
# of course fattrs works as well at class/module level as at instance
# level
#
require 'fattr'
module Logging
Level_names = {
0 => 'INFO',
# ...
42 => 'DEBUG',
}
class << Logging
fattr 'level' => 42
fattr('level_name'){ Level_names[level] }
end
end
p Logging.level
p Logging.level_name
~ > ruby samples/f.rb
42
"DEBUG"
<========< samples/g.rb >========>
~ > cat samples/g.rb
#
# you can add class/module fattrs the 'normal' way or using the provided
# shortcut method
#
require 'fattr'
class C
class << C
fattr 'a' => 4
end
Fattr 'b' => 2
end
p [ C.a, C.b ].join
~ > ruby samples/g.rb
"42"
<========< samples/h.rb >========>
~ > cat samples/h.rb
#
# class variable inheritance is supported simply
#
require 'fattr'
class A
Fattr :x, :default => 42, :inheritable => true
end
class B < A
end
class C < B
end
p C.x #=> 42
A.x = 42.0
B.x = 'forty-two'
p A.x #=> 42.0
p B.x #=> 'forty-two'
p C.x #=> 42
C.x! # re-initialize from closest ancestor (B)
p A.x #=> 42.0
p B.x #=> 'forty-two'
p C.x #=> 'forty-two'
~ > ruby samples/h.rb
42
42.0
"forty-two"
42
42.0
"forty-two"
"forty-two"
<========< samples/i.rb >========>
~ > cat samples/i.rb
#
# you can retrieve all fattrs as a list, or a hash with values included
#
require 'fattr'
class C
fattr(:a)
fattr(:b){ a.to_f }
end
o = C.new
o.fattr(:c)
o.fattr(:d){ self.c.upcase }
o.a = 42
o.c = 'forty-two'
p o.fattrs.to_hash #=> {"a"=>42, "b"=>42.0, "c"=>"forty-two", "d"=>"FORTY-TWO"}
p o.fattrs #=> ["c", "d"]
~ > ruby samples/i.rb
{"a"=>42, "b"=>42.0, "c"=>"forty-two", "d"=>"FORTY-TWO"}
["c", "d"]
HISTORY 2.3.0 support for "object.fattrs.to_hash"
2.0.0: support class/module inheritable attributes
1.1.0: ruby19 testing. move to github.
1.0.2: added Fattr shortcut for adding class/module level fattrs
class C
Fattr 'children' => []
def C.inherited other
(children << other).uniq!
super
end
end
class B < C
end
p C.children #=> B
1.0.0: port from attributes.rb retaining all the same features of that version of attributes.rb