Module: VirtualBox::AbstractModel::Relatable
- Includes:
- VersionMatcher
- Included in:
- VirtualBox::AbstractModel
- Defined in:
- lib/virtualbox/abstract_model/relatable.rb
Overview
Provides simple relationship features to any class. These relationships can be anything, since this module makes no assumptions and doesn’t differentiate between “has many” or “belongs to” or any of that.
The way it works is simple:
-
Relationships are defined with a relationship name and a class of the relationship objects.
-
When #populate_relationships is called, ‘populate_relationship` is called on each relationship class (example: StorageController.populate_relationship). This is expected to return the relationship, which can be any object.
-
When #save_relationships is called, ‘save_relationship` is
called on each relationship class, which manages saving its own relationship.
-
When #destroy_relationships is called, ‘destroy_relationship` is
called on each relationship class, which manages destroying its own relationship.
Be sure to read ClassMethods for complete documentation of methods.
# Defining Relationships
Every relationship has two mandatory parameters: the name and the class.
relationship :bacons, Bacon
In this case, there is a relationship ‘bacons` which refers to the `Bacon` class.
# Accessing Relationships
Relatable offers up dynamically generated accessors for every relationship which simply returns the relationship data.
relationship :bacons, Bacon
# Accessing through an instance "instance"
instance.bacons # => whatever Bacon.populate_relationship created
# Settable Relationships
It is often convenient that relationships become “settable.” That is, for a relationship ‘foos`, there would exist a `foos=` method. This is possible by implementing the `set_relationship` method on the relationship class. Consider the following relationship:
relationship :foos, Foo
If ‘Foo` has the `set_relationship` method, then it will be called by `foos=`. It is expected to return the new value for the relationship. To facilitate this need, the `set_relationship` method is given three parameters: caller, old value, and new value. An example implementation, albeit a silly one, is below:
class Foo
def self.set_relationship(caller, old_value, new_value)
return "Changed to: #{new_value}"
end
end
In this case, the following behavior would occur:
instance.foos # => assume "foo"
instance.foos = "bar"
instance.foos # => "Changed to: bar"
If the relationship class _does not implement_ the ‘set_relationship` method, then a Exceptions::NonSettableRelationshipException will be raised if a user attempts to set that relationship.
# Dependent Relationships
By setting ‘:dependent => :destroy` on relationships, VirtualBox::AbstractModel will automatically call #destroy_relationships when #destroy is called.
This is not a feature built-in to Relatable but figured it should be mentioned here.
# Lazy Relationships
Often, relationships are pretty heavy things to load. Data may have to be retrieved, classes instantiated, etc. If a class has many relationships, or many relationships within many relationships, the time and memory required for relationships really begins to add up. To address this issue, _lazy relationships_ are available. Lazy relationships defer loading their content until the last possible moment, or rather, when a user requests the data. By specifing the ‘:lazy => true` option to relationships, relationships will not be loaded immediately. Instead, when they’re first requested, ‘load_relationship` will be called on the model, with the name of the relationship given as a parameter. It is up to this method to call #populate_relationship at some point with the data to setup the relationship. An example follows:
class SomeModel
include VirtualBox::AbstractModel::Relatable
relationship :foos, Foo, :lazy => true
def load_relationship(name)
if name == :foos
populate_relationship(name, get_data_for_a_long_time)
end
end
end
Using the above class, we can use it like so:
model = SomeModel.new
# This initial load takes awhile as it loads...
model.foos
# Instant! (Just a hash lookup. No load necessary)
model.foos
One catch: If a model attempts to destroy a lazy relationship, it will first load the relationship, since destroy typically depends on some data of the relationship.
Defined Under Namespace
Modules: ClassMethods
Class Method Summary collapse
Instance Method Summary collapse
-
#destroy_relationship(name, *args) ⇒ Object
Destroys only a single relationship.
-
#destroy_relationships(*args) ⇒ Object
Calls ‘destroy_relationship` on each of the relationships.
-
#has_relationship?(key) ⇒ Boolean
Returns boolean denoting if a relationship exists.
-
#lazy_relationship?(key) ⇒ Boolean
Returns boolean denoting if a relationship is to be lazy loaded.
-
#loaded_relationship?(key) ⇒ Boolean
Returns boolean denoting if a relationship has been loaded.
-
#populate_relationship(name, data) ⇒ Object
Populate a single relationship.
-
#populate_relationships(data) ⇒ Object
The equivalent to Attributable#populate_attributes, but with relationships.
-
#read_relationship(name) ⇒ Object
Reads a relationship.
-
#relationship_class(key) ⇒ Class
Returns the class for a given relationship.
-
#relationship_data ⇒ Hash
Hash to data associated with relationships.
-
#save_relationship(name, *args) ⇒ Object
Saves a single relationship.
-
#save_relationships(*args) ⇒ Object
Saves the model, calls save_relationship on all relations.
-
#set_relationship(key, value) ⇒ Object
Sets a relationship to the given value.
Methods included from VersionMatcher
#assert_version_match, #split_version, #version_match?
Class Method Details
.included(base) ⇒ Object
126 127 128 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 126 def self.included(base) base.extend ClassMethods end |
Instance Method Details
#destroy_relationship(name, *args) ⇒ Object
Destroys only a single relationship. Any arbitrary args may be added to the end and they will be pushed through to the class’s ‘destroy_relationship` method.
259 260 261 262 263 264 265 266 267 268 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 259 def destroy_relationship(name, *args) = self.class.relationships_hash[name] return unless && relationship_class(name).respond_to?(:destroy_relationship) # Read relationship, which forces lazy relationships to load, which is # probably necessary for destroying read_relationship(name) relationship_class(name).destroy_relationship(self, relationship_data[name], *args) end |
#destroy_relationships(*args) ⇒ Object
Calls ‘destroy_relationship` on each of the relationships. Any arbitrary args may be added and they will be forarded to the relationship’s ‘destroy_relationship` method.
248 249 250 251 252 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 248 def destroy_relationships(*args) self.class.relationships.each do |name, | destroy_relationship(name, *args) end end |
#has_relationship?(key) ⇒ Boolean
Returns boolean denoting if a relationship exists.
281 282 283 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 281 def has_relationship?(key) self.class.has_relationship?(key.to_sym) end |
#lazy_relationship?(key) ⇒ Boolean
Returns boolean denoting if a relationship is to be lazy loaded.
288 289 290 291 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 288 def lazy_relationship?(key) = self.class.relationships_hash[key.to_sym] !.nil? && [:lazy] end |
#loaded_relationship?(key) ⇒ Boolean
Returns boolean denoting if a relationship has been loaded.
294 295 296 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 294 def loaded_relationship?(key) relationship_data.has_key?(key) end |
#populate_relationship(name, data) ⇒ Object
Populate a single relationship.
238 239 240 241 242 243 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 238 def populate_relationship(name, data) = self.class.relationships_hash[name] return unless relationship_class(name).respond_to?(:populate_relationship) return if [:version] && !version_match?([:version], VirtualBox.version) relationship_data[name] = relationship_class(name).populate_relationship(self, data) end |
#populate_relationships(data) ⇒ Object
The equivalent to Attributable#populate_attributes, but with relationships.
231 232 233 234 235 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 231 def populate_relationships(data) self.class.relationships.each do |name, | populate_relationship(name, data) unless lazy_relationship?(name) end end |
#read_relationship(name) ⇒ Object
Reads a relationship. This is equivalent to Attributable#read_attribute, but for relationships.
187 188 189 190 191 192 193 194 195 196 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 187 def read_relationship(name) = self.class.relationships_hash[name.to_sym] assert_version_match([:version], VirtualBox.version) if [:version] if lazy_relationship?(name) && !loaded_relationship?(name) load_relationship(name) end relationship_data[name.to_sym] end |
#relationship_class(key) ⇒ Class
Returns the class for a given relationship. This method handles converting a string/symbol into the proper class.
302 303 304 305 306 307 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 302 def relationship_class(key) = self.class.relationships_hash[key.to_sym] klass = [:klass] klass = Object.module_eval("#{klass}") unless klass.is_a?(Class) klass end |
#relationship_data ⇒ Hash
Hash to data associated with relationships. You should instead use the accessors created by Relatable.
274 275 276 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 274 def relationship_data @relationship_data ||= {} end |
#save_relationship(name, *args) ⇒ Object
Saves a single relationship. It is up to the relationship class to determine whether anything changed and how saving is implemented. Simply calls ‘save_relationship` on the relationship class.
221 222 223 224 225 226 227 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 221 def save_relationship(name, *args) = self.class.relationships_hash[name] return if lazy_relationship?(name) && !loaded_relationship?(name) return if [:version] && !version_match?([:version], VirtualBox.version) return unless relationship_class(name).respond_to?(:save_relationship) relationship_class(name).save_relationship(self, relationship_data[name], *args) end |
#save_relationships(*args) ⇒ Object
Saves the model, calls save_relationship on all relations. It is up to the relation to determine whether anything changed, etc. Simply calls ‘save_relationship` on each relationship class passing in the following parameters:
-
caller - The class which is calling save
-
data - The data associated with the relationship
In addition to those two args, any arbitrary args may be tacked on to the end and they’ll be pushed through to the ‘save_relationship` method.
208 209 210 211 212 213 214 215 216 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 208 def save_relationships(*args) # Can't use `all?` here since it short circuits results = self.class.relationships.collect do |data| name, = data !!save_relationship(name, *args) end !results.include?(false) end |
#set_relationship(key, value) ⇒ Object
Sets a relationship to the given value. This is not guaranteed to do anything, since “set_relationship” will be called on the class that the relationship is associated with and its expected to return the resulting relationship to set.
If the relationship class doesn’t respond to the set_relationship method, then an exception Exceptions::NonSettableRelationshipException will be raised.
This method is called by the “magic” method of ‘relationship=`.
322 323 324 325 326 327 328 329 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 322 def set_relationship(key, value) key = key.to_sym relationship = self.class.relationships_hash[key] return unless relationship raise Exceptions::NonSettableRelationshipException.new unless relationship_class(key).respond_to?(:set_relationship) relationship_data[key] = relationship_class(key).set_relationship(self, relationship_data[key], value) end |