Class: Valuable
- Inherits:
-
Object
- Object
- Valuable
- Defined in:
- lib/valuable.rb
Overview
Valuable is the class from which all classes (who are so inclined) should inherit.
Example:
class Bus < Valuable
has_value :number, :klass => :integer
has_value :color, :default => 'yellow'
has_collection :riders, :alias => 'Passengers'
end
>> Bus.attributes
=> [:number, :color, :riders]
>> bus = Bus.new(:number => '3', :Passengers => ['GOF', 'Fowler', 'Mort']
>> bus.attributes
=> {:number => 3, :riders => ['GOF', 'Fowler', 'Mort'], :color => 'yellow'}
Defined Under Namespace
Modules: Utils
Class Method Summary collapse
- ._attributes ⇒ Object
-
.acts_as_permissive ⇒ Object
Instructs the class NOT to complain if any attributes are set that haven’t been declared.
-
.attributes ⇒ Object
Returns an array of the attributes available on this object.
-
.create_accessor_for(name, extensions) ⇒ Object
creates an accessor method named after the attribute…
-
.create_negative_question_for(name, negative) ⇒ Object
In some situations, the opposite of a value may be just as interesting.
-
.create_question_for(name) ⇒ Object
In addition to the normal getter and setter, boolean attributes get a method appended with a ?.
-
.create_setter_for(attribute, options) ⇒ Object
Creates the method that sets the value of an attribute.
-
.defaults ⇒ Object
Returns a name/value set of the values that will be used on instanciation unless new values are provided.
-
.has_collection(name, options = {}) ⇒ Object
this is a more intuitive way of marking an attribute as holding a collection.
-
.has_value(name, options = {}) ⇒ Object
Decorator method that lets you specify the attributes for your model.
- .permissive_constructor=(value) ⇒ Object
- .permissive_constructor? ⇒ Boolean
-
.register_formatter(name, &block) ⇒ Object
Register custom formatters.
- .sudo_alias(alias_name, method_name) ⇒ Object
Instance Method Summary collapse
-
#attributes ⇒ Object
Returns a Hash representing all known values.
-
#initialize(atts = nil) ⇒ Valuable
constructor
accepts an optional hash that will be used to populate the predefined attributes for this class.
-
#initialize_attributes ⇒ Object
Returns a Hash representing all known values.
- #method_missing(method_name, *args) ⇒ Object
- #permissive? ⇒ Boolean
-
#update_attributes(atts) ⇒ Object
mass assign attributes.
- #write_attribute(name, value) ⇒ Object
Constructor Details
#initialize(atts = nil) ⇒ Valuable
accepts an optional hash that will be used to populate the predefined attributes for this class.
Note: You are free to overwrite the constructor, but you should call initialize_attributes OR make sure at least one value is stored.
54 55 56 57 |
# File 'lib/valuable.rb', line 54 def initialize(atts = nil) initialize_attributes self.update_attributes(atts || {}) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_name, *args) ⇒ Object
89 90 91 92 93 94 95 |
# File 'lib/valuable.rb', line 89 def method_missing(method_name, *args) if method_name.to_s =~ /(\w+)=/ raise( ArgumentError, "#{self.class.to_s} does not have an attribute or alias '#{$1}'", caller) unless self.permissive? else super end end |
Class Method Details
._attributes ⇒ Object
114 115 116 |
# File 'lib/valuable.rb', line 114 def _attributes @_attributes ||= {} end |
.acts_as_permissive ⇒ Object
Instructs the class NOT to complain if any attributes are set that haven’t been declared.
class Sphere < Valuable
has_value :material
end
>> Sphere.new(:radius => 3, :material => ‘water’) EXCEPTION! OH NOS!
class Box < Valuable
acts_as_permissive
has_value :material
end
>> box = Box.new(:material => ‘wood’, :size => ‘36 x 40’) >> box.attributes
> => ‘wood’
353 354 355 |
# File 'lib/valuable.rb', line 353 def acts_as_permissive self.permissive_constructor=true end |
.attributes ⇒ Object
Returns an array of the attributes available on this object.
110 111 112 |
# File 'lib/valuable.rb', line 110 def attributes _attributes.keys end |
.create_accessor_for(name, extensions) ⇒ Object
creates an accessor method named after the attribute… can be used as a chained setter, as in:
whitehouse.windows(5).doors(4).oval_rooms(1)
If NOT used as a setter, returns the value, extended by the modules listed in the second parameter.
217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/valuable.rb', line 217 def create_accessor_for(name, extensions) define_method name do |*args| if args.length == 0 attributes[name].tap do |out| extensions.each do |extension| out.extend( extension ) end end else send("#{name}=", *args) self end end end |
.create_negative_question_for(name, negative) ⇒ Object
In some situations, the opposite of a value may be just as interesting.
class Coder < Valuable
has_value :agilist, :klass => Boolean, :negative => :waterfaller
end
monkey = Coder.new(:agilist => false)
>> monkey.waterfaller?
=> true
257 258 259 260 261 |
# File 'lib/valuable.rb', line 257 def create_negative_question_for(name, negative) define_method "#{negative}?" do !attributes[name] end end |
.create_question_for(name) ⇒ Object
In addition to the normal getter and setter, boolean attributes get a method appended with a ?.
class Player < Valuable
has_value :free_agent, :klass => Boolean
end
juan = Player.new(:free_agent => true)
>> juan.free_agent?
=> true
242 243 244 245 246 |
# File 'lib/valuable.rb', line 242 def create_question_for(name) define_method "#{name}?" do attributes[name] end end |
.create_setter_for(attribute, options) ⇒ Object
Creates the method that sets the value of an attribute. The setter calls write_attribute, which handles typicification. It is called by the constructor (rather than using write attribute, which would render any custom setters ineffective.)
Setting values via the attributes hash avoids typification, ie: >> player.phone = “8778675309” >> player.phone
> “(877) 867-5309”
>> player.attributes = “8778675309” >> player.phone
> “8778675309”
192 193 194 195 196 197 198 199 200 |
# File 'lib/valuable.rb', line 192 def create_setter_for(attribute, ) setter_method = "#{attribute}=" define_method setter_method do |value| if [:allow_blank] || value != "" write_attribute(attribute, value) end end end |
.defaults ⇒ Object
Returns a name/value set of the values that will be used on instanciation unless new values are provided.
>> Bus.defaults
=> {:color => 'yellow'}
123 124 125 126 127 |
# File 'lib/valuable.rb', line 123 def defaults out = {} _attributes.each{|n, atts| out[n] = atts[:default] unless atts[:default].nil?} out end |
.has_collection(name, options = {}) ⇒ Object
this is a more intuitive way of marking an attribute as holding a collection.
class Bus < Valuable
has_value :riders, :default => [] # meh...
has_collection :riders # better!
end
>> bus = Bus.new
>> bus.riders << 'jack'
>> bus.riders
=> ['jack']
class Person
has_collection :phone_numbers, :klass => PhoneNumber
end
>> jenny = Person.new(:phone_numbers => ['8008675309'] )
>> jenny.phone_numbers.first.class
=> PhoneNumber
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/valuable.rb', line 283 def has_collection(name, = {}) Utils.( self.class.name, name, ) name = name.to_sym [:item_klass] = [:klass] if [:klass] [:klass] = :collection [:default] ||= [] [:extend] = [[:extend]].flatten.compact _attributes[name] = create_accessor_for(name, [:extend]) create_setter_for(name, allow_blank: false) sudo_alias [:alias], name if [:alias] sudo_alias "#{[:alias]}=", "#{name}=" if [:alias] end |
.has_value(name, options = {}) ⇒ Object
Decorator method that lets you specify the attributes for your model. It accepts an attribute name (a symbol) and an options hash. Valid options are :default, :klass and (when :klass is Boolean) :negative.
:default - for the given attribute, use this value if no other
is provided.
:klass - light weight type casting. Use :integer, :string or
:boolean. Alternately, supply a class.
:alias - creates an alias for getter and setter with the new name.
When a :klassified attribute is set to some new value, if the value is not nil and is not already of that class, the value will be cast to the specified klass. In the case of :integer, it wil be done via .to_i. In the case of a random other class, it will be done via Class.new(value). If the value is nil, it will not be cast.
A good example: PhoneNumber < String is useful if you want numbers to come out the other end properly formatted, when your input may come in as an integer, or string without formatting, or string with bad formatting.
IMPORTANT EXCEPTION
Due to the way Rails handles checkboxes, ‘0’ resolves to FALSE, though it would normally resolve to TRUE.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/valuable.rb', line 157 def has_value(name, ={}) Valuable::Utils.(self.class.name, name, ) [:extend] = [[:extend]].flatten.compact [:allow_blank] = .has_key?(:allow_blank) ? [:allow_blank] : true name = name.to_sym _attributes[name] = create_accessor_for(name, [:extend]) create_question_for(name) if [:klass] == :boolean create_negative_question_for(name, [:negative]) if [:klass] == :boolean && [:negative] create_setter_for(name, allow_blank: [:allow_blank] ) sudo_alias [:alias], name if [:alias] sudo_alias "#{[:alias]}=", "#{name}=" if [:alias] end |
.permissive_constructor=(value) ⇒ Object
357 358 359 |
# File 'lib/valuable.rb', line 357 def permissive_constructor=(value) @_permissive_constructor = value end |
.permissive_constructor? ⇒ Boolean
361 362 363 |
# File 'lib/valuable.rb', line 361 def permissive_constructor? !!(@_permissive_constructor ||= false) end |
.register_formatter(name, &block) ⇒ Object
Register custom formatters. Not happy with the default behavior? Custom formatters override all pre-defined formatters. However, remember that formatters are defined globally, rather than per-class.
Valuable.register_formatter(:orientation) do |value|
case value
case Numeric
value
when 'N', 'North'
0
when 'E', 'East'
90
when 'S', 'South'
180
when 'W', 'West'
270
else
nil
end
end
class MarsRover < Valuable
has_value :orientation, :klass => :orientation
end
>> curiosity = MarsRover.new(:orientation => ‘S’) >> curiosity.orientation
> 180
329 330 331 |
# File 'lib/valuable.rb', line 329 def register_formatter(name, &block) Valuable::Utils.formatters[name] = block end |
.sudo_alias(alias_name, method_name) ⇒ Object
202 203 204 205 206 |
# File 'lib/valuable.rb', line 202 def sudo_alias( alias_name, method_name ) define_method alias_name do |*atts| send(method_name, *atts) end end |
Instance Method Details
#attributes ⇒ Object
Returns a Hash representing all known values. Values are set four ways:
(1) Default values are set on instanciation, ie Person.new
(2) they were passed to the constructor
Bus.new(:color => 'green')
(3) they were set via their namesake setter or alias setter
bus.color = 'green'
bus.Passengers = ['bill', 'steve']
(4) the write_attributes(key, value) method
Values that have not been set and have no default not appear in this collection. Their namesake attribute methods will respond with nil. Always use symbols to access these values, ie:
Person.attributes[:color]
not
Person.attributes['color']
basic usage:
>> bus = Bus.new(:number => 16) # color has default value 'yellow'
>> bus.attributes
=> {:color => 'yellow', :number => 16}
43 44 45 |
# File 'lib/valuable.rb', line 43 def attributes @attributes ||= Valuable::Utils.initial_copy_of_attributes(self.class.defaults) end |
#initialize_attributes ⇒ Object
Returns a Hash representing all known values. Values are set four ways:
(1) Default values are set on instanciation, ie Person.new
(2) they were passed to the constructor
Bus.new(:color => 'green')
(3) they were set via their namesake setter or alias setter
bus.color = 'green'
bus.Passengers = ['bill', 'steve']
(4) the write_attributes(key, value) method
Values that have not been set and have no default not appear in this collection. Their namesake attribute methods will respond with nil. Always use symbols to access these values, ie:
Person.attributes[:color]
not
Person.attributes['color']
basic usage:
>> bus = Bus.new(:number => 16) # color has default value 'yellow'
>> bus.attributes
=> {:color => 'yellow', :number => 16}
46 47 48 |
# File 'lib/valuable.rb', line 46 def attributes @attributes ||= Valuable::Utils.initial_copy_of_attributes(self.class.defaults) end |
#permissive? ⇒ Boolean
85 86 87 |
# File 'lib/valuable.rb', line 85 def permissive? self.class.permissive_constructor? end |
#update_attributes(atts) ⇒ Object
mass assign attributes. This method will not clear any existing attributes.
class Shoe
has_value :size
has_value :owner
has_value :color, :default => 'red'
def big_feet?
size && size > 15
end
end
>> shoe = Shoe.new >> shoe.update_attributes(:size => 16, :owner => ‘MJ’) >> shoe.attributes
> => 16, :owner => ‘MJ’, :color => ‘red’
can be method-chained
>> Shoe.new.update_attributes(:size => 16).big_feet?
> true
80 81 82 83 |
# File 'lib/valuable.rb', line 80 def update_attributes(atts) atts.each{|name, value| __send__("#{name}=", value )} self end |
#write_attribute(name, value) ⇒ Object
97 98 99 100 101 102 103 104 105 |
# File 'lib/valuable.rb', line 97 def write_attribute(name, value) attribute = Valuable::Utils.find_attribute_for( name, self.class._attributes ) if attribute self.attributes[attribute] = Valuable::Utils.format(attribute, value, self.class._attributes) else raise( ArgumentError, "#{self.class.to_s} does not have an attribute or alias '#{name}'", caller) unless self.permissive? end end |