NAME attributes.rb

INSTALL gem install attributes

URIS http://codeforpeople.com/lib/ruby http://rubyforge.org/projects/codeforpeople/ http://codeforpeople.rubyforge.org/svn/

SYNOPSIS attributes.rb provides a set of attr_* like method with several user friendly additions. attributes.rb is similar to the traits.rb package but sacrifices a few features for simplicity of implementation.

the implementation of attributes.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 the original traits lib

key features provided by attributes 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 attributes they've defined and this information is
 available to client code

- a whole suite of methods is defined by calls to #attributes including
 getter, setter, query (var?) and banger (var! - which forces
 re-initialization from the default value)

- ability to define multiple attributes at once using key => value pairs

- fast lookup of whether or not a class has defined a certain attribute

- attributes can be defined on objects on a per singleton basis as well

- getters acts as setters if an argument is given to them

- block caching, calling an attribute with a block sets the instance
  variable to that block

all this in < 100 lines of code

HISTORY 5.0.1 - removed wrong dep on pervasives from gemspec.rb

5.0.0
- added support for block caching. for example

    - simple block caching:

        class Filter 
          attribute :process
        end

        filter = Filter.new

        filter.process{|line| line.upcase}

        lines.each do |line|
          p filter.process.call(line)
        end

    - using block caching to delay block evaluation/class-factory:

        module MigrationDSL
          attribute :migration

          def migration_class
            model = self

            Class.new(::ActiveRecord::Migration) do
              singleton_class =
                class << self
                  self
                end
              singleton_class.module_eval{ attribute :model => model }
              singleton_class.module_eval &model.migration
            end
          end
        end

        class Table < ActiveRecord::Base
          extend MigrationDSL
        end

        class Jobs < Table
          migration do
            def up
              create_table model.table_name, :primary_key => model.primary_key do |t|
                t.column 'vinyl_shoes', :text
              end
            end

            def down
              create_table model.table_name
            end
          end
        end

        ...

        JobsMigration = Jobs.migration_class

4.1.0
- 4.0.0 introduced a bug where a query (foo?) would not initialize a var -
  4.1.0 fixes that

4.0.0
- removed dependancy on, and bundle of, pervasives
- faster.  as fast as normal method definition.
- faster lookup for MyClass.attributes.include?('foobar')

3.7.0
small patch to use 'instance_variable_defined?' instead of defined?
keyword

3.5.0
migrated to a pervasives based impl to attributes should work on any
object - even blankslate objects

3.3.0

moved to an instance variable-less model using an module level closure for
the attributes list

SAMPLES

<========< samples/a.rb >========>

~ > cat samples/a.rb

#
# basic usage is like attr, but note that attribute defines a suite of methods
#
  require 'attributes'

  class C
    attribute 'a'
  end

  c = C.new

  c.a = 42
  p c.a                 #=> 42
  p 'forty-two' if c.a? #=> 'forty-two'

#
# attributes works on object too 
#
  o = Object.new
  o.attribute '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 'attributes'

  class C
    attribute :a => 42
    attribute(: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 values may by given, plain names and key/val pairs may be mixed.
#
  require 'attributes'

  class C
    attributes 'x', 'y' => 0b101000, 'z' => 0b10
  end

  c = C.new
  c.x = c.y + c.z
  p c.x #=> 42

~ > ruby samples/c.rb

42


<========< samples/d.rb >========>

~ > cat samples/d.rb

#
# a nice feature is that all attributes 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
# attribute traversal is very easy.
#
  require 'attributes'

  class C
    attributes %w( x y z )

    def attributes
      self.class.attributes
    end

    def initialize
      attributes.each_with_index{|a,i| send a, i}
    end

    def to_hash
      attributes.inject({}){|h,a| h.update a => send(a)}
    end

    def inspect
      to_hash.inspect
    end
  end

  c = C.new
  p c.attributes 
  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 attributes is that getters can also be setters.
# this allows incredibly clean looking code like
#
  require 'attributes'

  class Config
    attributes %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

#<Config:0x1fb58 @port=80, @host="codeforpeople.org">


<========< samples/f.rb >========>

~ > cat samples/f.rb

#
# of course attributes works as well at class/module level as at instance
# level
#
  require 'attributes'

  module Logging 
    Level_names = {
      0 => 'INFO',
      # ...
      42 => 'DEBUG',
    }

    class << self
      attribute 'level' => 42
      attribute('level_name'){ Level_names[level] }
    end
  end

p Logging.level
p Logging.level_name

~ > ruby samples/f.rb

42
"DEBUG"