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 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.
-
#reset_relationships ⇒ Object
Resets a relationship.
-
#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.
270 271 272 273 274 275 276 277 278 279 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 270 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.
259 260 261 262 263 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 259 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.
292 293 294 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 292 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.
299 300 301 302 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 299 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.
305 306 307 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 305 def loaded_relationship?(key) relationship_data.has_key?(key) end |
#populate_relationship(name, data) ⇒ Object
Populate a single relationship.
249 250 251 252 253 254 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 249 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.
242 243 244 245 246 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 242 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.
198 199 200 201 202 203 204 205 206 207 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 198 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.
313 314 315 316 317 318 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 313 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.
285 286 287 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 285 def relationship_data @relationship_data ||= {} end |
#reset_relationships ⇒ Object
Resets a relationship. This resets all lazy relationships.
186 187 188 189 190 191 192 193 194 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 186 def reset_relationships to_delete = [] relationship_data.each do |key, value| to_delete << key if lazy_relationship?(key) end # Delete them! to_delete.each { |key| relationship_data.delete(key) } 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.
232 233 234 235 236 237 238 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 232 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.
219 220 221 222 223 224 225 226 227 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 219 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=`.
333 334 335 336 337 338 339 340 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 333 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 |