Module: VirtualBox::AbstractModel::Attributable

Defined in:
lib/virtualbox/abstract_model/attributable.rb

Overview

Module which can be included into any other class which allows that class to have attributes using the "attribute" class method. This creates the reader/writer for the attribute but also provides other useful options such as readonly attributes, default values, and more.

Make sure to also see the ClassMethods.

Defining a Basic Attribute

attribute :name

The example above would put the "name" attribute on the class. This would give the class the following abilities

instance.name = "Harry!"
puts instance.name # => "Harry!"

Basic attributes alone are not different than ruby's built-in attr_* methods.

Defining a Readonly Attribute

attribute :age, :readonly => true

The example above allows age to be read, but not written to via the age= method. The attribute is still able to written using #write_attribute but this is generally only for inter-class use, and not for users of it.

Defining Default Values

attribute :format, :default => "VDI"

The example above applies a default value to format. So if no value is ever given to it, format would return VDI.

Populating Multiple Attributes

Attributes can be mass populated using #populate_attributes. Below is an example of the use.

class Person
  include Attributable

  attribute :name
  attribute :age, :readonly => true

  def initialize
    populate_attributes({
      :name => "Steven",
      :age => 27
    })
  end
end

Note: Populating attributes is not the same as mass-updating attributes. #populate_attributes is meant to do initial population only. There is currently no method for mass assignment for updating.

Custom Populate Keys

Sometimes the attribute names don't match the keys of the hash that will be used to populate it. For this purpose, you can define a custom populate_key. Example:

attribute :path, :populate_key => :location

def initialize
  populate_attributes(:location => "Home")
  puts path # => "Home"
end

Lazy Loading Attributes

While most attributes are fairly trivial to calculate and populate, sometimes attributes may have an expensive cost to populate, and are generally not worth populating unless a user of the class requests that attribute. This is known as lazy loading the attributes. This is possibly by specifying the :lazy option on the attribute. In this case, the first time (and only the first time) the attribute is requested, load_attribute will be called with the name of the attribute as the parameter. This method is then expected to call write_attribute on that attribute to give it a value.

class ExpensiveAttributeModel
  include VirtualBox::AbstractModel::Attributable
  attribute :expensive_attribute, :lazy => true

  def load_attribute(name)
    if name == :expensive_attribute
      write_attribute(name, perform_expensive_calculation)
    end
  end
end

Using the above definition, we could use the class like so:

# Initializing is fast, since no attribute population is done
model = ExpensiveAttributeModel.new

# But this is slow, since it has to calculate.
puts model.expensive_attribute

# But ONLY THE FIRST TIME. This time is FAST!
puts model.expensive_attribute

In addition to calling load_attribute on initial read, write_attribute when performed on a lazy loaded attribute will mark it as "loaded" so there will be no load called on the first request. Example, using the above class once again:

model = ExpensiveAttributeModel.new
model.write_attribute(:expensive_attribute, 42)

# This is FAST, since "load_attribute" is not called
puts model.expensive_attribute # => 42

Defined Under Namespace

Modules: ClassMethods

Class Method Summary

Instance Method Summary

Class Method Details

+ (Object) included(base)



121
122
123
# File 'lib/virtualbox/abstract_model/attributable.rb', line 121

def self.included(base)
  base.extend ClassMethods
end

Instance Method Details

- (Object) attributes

Returns a hash of all attributes and their options.



222
223
224
# File 'lib/virtualbox/abstract_model/attributable.rb', line 222

def attributes
  @attribute_values ||= {}
end

- (Boolean) has_attribute?(name)

Returns boolean value denoting if an attribute exists.

Returns:

  • (Boolean)


227
228
229
# File 'lib/virtualbox/abstract_model/attributable.rb', line 227

def has_attribute?(name)
  self.class.attributes.has_key?(name.to_sym)
end

- (Boolean) lazy_attribute?(name)

Returns boolean value denoting if an attribute is "lazy loaded"

Returns:

  • (Boolean)


232
233
234
# File 'lib/virtualbox/abstract_model/attributable.rb', line 232

def lazy_attribute?(name)
  has_attribute?(name) && self.class.attributes[name.to_sym][:lazy]
end

- (Boolean) loaded_attribute?(name)

Returns boolean value denoting if an attribute has been loaded yet.

Returns:

  • (Boolean)


238
239
240
# File 'lib/virtualbox/abstract_model/attributable.rb', line 238

def loaded_attribute?(name)
  attributes.has_key?(name)
end

- (Object) populate_attributes(attribs)

Does the initial population of the various attributes. It will ignore attributes which are not defined or have no value in the hash.

Population uses the attributes populate_key if present to determine which value to take. Example:

attribute :name, :populate_key => :namae
attribute :age

def initialize
  populate_attributes(:namae => "Henry", :age => 27)
end

The above example would set name to Henry since that is the populate_key. If a populate_key is not present, the attribute name is used.



190
191
192
193
194
195
# File 'lib/virtualbox/abstract_model/attributable.rb', line 190

def populate_attributes(attribs)
  self.class.attributes.each do |key, options|
    value_key = options[:populate_key] || key
    write_attribute(key, attribs[value_key]) if attribs.has_key?(value_key)
  end
end

- (Object) read_attribute(name)

Reads an attribute. This method will return nil if the attribute doesn't exist. If the attribute does exist but doesn't have a value set, it'll use the default value if specified.



210
211
212
213
214
215
216
217
218
219
# File 'lib/virtualbox/abstract_model/attributable.rb', line 210

def read_attribute(name)
  if has_attribute?(name)
    if lazy_attribute?(name) && !loaded_attribute?(name)
      # Load the lazy attribute
      load_attribute(name.to_sym)
    end

    attributes[name] || self.class.attributes[name][:default]
  end
end

- (Boolean) readonly_attribute?(name)

Returns a boolean value denoting if an attribute is readonly. This method also returns false for nonexistent attributes so it should be used in conjunction with #has_attribute? if existence is important.

Returns:

  • (Boolean)


246
247
248
249
# File 'lib/virtualbox/abstract_model/attributable.rb', line 246

def readonly_attribute?(name)
  name = name.to_sym
  has_attribute?(name) && self.class.attributes[name][:readonly]
end

- (Object) write_attribute(name, value)

Writes an attribute. This method ignores the readonly option on attribute definitions. This method is mostly meant for internal use on setting attributes (including readonly attributes), whereas users of a class which includes this module should use the accessor methods, such as name=.



202
203
204
# File 'lib/virtualbox/abstract_model/attributable.rb', line 202

def write_attribute(name, value)
  attributes[name] = value
end