Module: VirtualBox::AbstractModel::Attributable

Includes:
VersionMatcher
Included in:
VirtualBox::AbstractModel
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`.

## Attributes for a Specific VirtualBox Version

Attributes change with different VirtualBox versions. One way to provide version-specific behavior is to specify the version which the attribute applies to.

attribute :name, :version => "3.2"
attribute :age, :version => "3.1"

These versions only apply to major and minor releases of VirtualBox. Patch releases can't be specified (such as "3.2.4")

## 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 collapse

Instance Method Summary collapse

Methods included from VersionMatcher

#assert_version_match, #split_version, #version_match?

Class Method Details

.included(base) ⇒ Object



137
138
139
# File 'lib/virtualbox/abstract_model/attributable.rb', line 137

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

Instance Method Details

#attributesObject

Returns a hash of all attributes and their options.



260
261
262
# File 'lib/virtualbox/abstract_model/attributable.rb', line 260

def attributes
  @attribute_values ||= {}
end

#has_attribute?(name) ⇒ Boolean

Returns boolean value denoting if an attribute exists.

Returns:

  • (Boolean)


265
266
267
# File 'lib/virtualbox/abstract_model/attributable.rb', line 265

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

#lazy_attribute?(name) ⇒ Boolean

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

Returns:

  • (Boolean)


270
271
272
# File 'lib/virtualbox/abstract_model/attributable.rb', line 270

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

#loaded_attribute?(name) ⇒ Boolean

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

Returns:

  • (Boolean)


276
277
278
# File 'lib/virtualbox/abstract_model/attributable.rb', line 276

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

#populate_attributes(attribs) ⇒ Object

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.



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

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

#read_attribute(name) ⇒ Object

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.



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/virtualbox/abstract_model/attributable.rb', line 241

def read_attribute(name)
  if has_attribute?(name)
    options = self.class.attributes[name]

    assert_version_match(options[:version], VirtualBox.version) if options[:version]
    if lazy_attribute?(name) && !loaded_attribute?(name)
      # Load the lazy attribute
      load_attribute(name.to_sym)
    end

    if attributes[name].nil?
      options[:default]
    else
      attributes[name]
    end
  end
end

#readonly_attribute?(name) ⇒ Boolean

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)


284
285
286
287
# File 'lib/virtualbox/abstract_model/attributable.rb', line 284

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

#write_attribute(name, value) ⇒ Object

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=`.



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

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