Introducing Valuable
Valuable enables quick modeling... it's attr_accessor on steroids. It intends to use a simple and intuitive interface, allowing you easily create models without hassles, so you can get on with the logic specific to your application. I find myself using it in sort of a presenter capacity, when I have to pull data from non-standard data sources, and to handle temporary data during imports.
Valuable provides DRY decoration like attr_accessor, but includes default values, light weight type casting and a constructor that accepts an attributes hash. It provides a class-level list of attributes, an instance-level attributes hash, and more.
Examples
basic syntax
class Fruit
has_value :name
has_collection :vitamins
end
constructor accepts an attributes hash
>> apple = Fruit.new(:name => 'Apple')
>> apple.name
=> 'Apple'
>> apple.vitamins
=> []
default values
class Developer
has_value :name
has_value :nickname, :default => 'mort'
end
>> dev = Developer.new(:name => 'zk')
>> dev.name
=> 'zk'
>> dev.nickname
=> 'mort'
setting a value to nil overrides the default.
>> Developer.new(:name => 'KDD', :nickname => nil).nickname
=> nil
light weight type casting
class BaseballPlayer < Valuable
has_value :at_bats, :klass => :integer
has_value :hits, :klass => :integer
def average
hits/at_bats.to_f if hits && at_bats
end
end
>> joe = BaseballPlayer.new(:hits => '5', :at_bats => '20', :on_drugs => '0' == '1')
>> joe.at_bats
=> 20
>> joe.average
=> 0.25
aliases
# This example requires active_support because of Hash.from_xml
class Software < Valuable
has_value :name, :alias => 'Title'
end
>> xml = '<software><Title>Windows XP</Title></software>'
>> xp = Software.new(:Title => Hash.from_xml(xml)['software'])
>> xp.name
=> "Windows XP"
I find myself using classes to format things... ( PhoneNumber is provided in /examples
)
class School < Valuable
has_value :name
has_value :phone, :klass => PhoneNumber
end
>> School.new(:name => 'Vanderbilt', :phone => '3332223333').phone
=> '(333) 222-3333'
as a presenter in Rails
class CalenderPresenter < Valuable
has_value :month, :klass => Integer, :default => Time.now.month
has_value :year, :klass => Integer, :default => Time.now.year
def start_date
Date.civil( year, month, 1)
end
def end_date
Date.civil( year, month, -1) #strange I know
end
def events
Event.find(:all, :conditions => event_conditions)
end
def event_conditions
['starts_at between ? and ?', start_date, end_date]
end
end
this class might appear in a controller like this:
class CalendarController < ApplicationController
def show
@presenter = CalendarPresenter.new(params[:calendar])
end
end
but it's easier to understand like this:
>> @presenter = CalendarPresenter.new({}) # first pageload
>> @presenter.start_date
=> Tue, 01 Dec 2009
>> @presenter.end_date
=> Thu, 31 Dec 2009
>> # User selects some other month and year; the next request looks like...
>> @presenter = CalendarPresenter.new({:month => '2', :year => '2002'})
>> @presenter.start_date
=> Fri, 01 Feb 2002
>> @presenter.end_date
=> Thu, 28 Feb 2002
...
So, if you're reading this, you're probably thinking, "I could have done that!" Yes, it's true. I'll happily agree that it's a relatively simple tool if you'll agree that it lets you model a calendar with an intuitive syntax, prevents you from writing yet another obvious constructor, and allows you to keep your brain focused on your app.
you can access the attributes via the attributes hash. Only default and specified attributes will have entries here.
class Person < Valuable
has_value :name
has_value :is_developer, :default => false
has_value :ssn
end
>> elvis = Person.new(:name => 'The King')
>> elvis.attributes
=> {:name=>"The King", :is_developer=>false}
>> elvis.attributes[:name]
=> "The King"
>> elvis.ssn
=> nil
also, you can get a list of all the defined attributes from the class
>> Person.attributes
=> [:name, :is_developer, :ssn]
Default Values
Default values are used when no value is provided to the constructor. If the value nil is provided, nil will be used instead of the default.
When a default value and a klass are specified, the default value will NOT be cast to type klass -- you must do it.
If a value having a default is set to null after it is constructed, it will NOT be set to the default.
If there is no default value, the result will be nil, EVEN if type casting is provided. Thus, a field typically cast as an Integer can be nil. See calculation of average.
KLASS-ification
:integer
, :string
and :boolean
use to_i
, to_s
and !!
respectively. All other klasses use klass.new(value)
unless the value is_a?(klass)
, in which case it is unmolested. Nils are never klassified. In the example above, hits, which is an integer, is nil
if not set, rather than nil.to_i = 0
.