Module: VirtualBox::AbstractModel::Relatable
- 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, AbstractModel
will automatically call #destroy_relationships when AbstractModel#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
Instance Method Summary
- - (Object) destroy_relationship(name, *args) Destroys only a single relationship.
-
- (Object) destroy_relationships(*args)
Calls
destroy_relationship
on each of the relationships. - - (Boolean) has_relationship?(key) Returns boolean denoting if a relationship exists.
- - (Boolean) lazy_relationship?(key) Returns boolean denoting if a relationship is to be lazy loaded.
- - (Boolean) loaded_relationship?(key) Returns boolean denoting if a relationship has been loaded.
- - (Object) populate_relationship(name, data) Populate a single relationship.
- - (Object) populate_relationships(data) The equivalent to Attributable#populate_attributes, but with relationships.
- - (Object) read_relationship(name) Reads a relationship.
- - (Hash) relationship_data Hash to data associated with relationships.
- - (Object) save_relationship(name, *args) Saves a single relationship.
- - (Object) save_relationships(*args) Saves the model, calls save_relationship on all relations.
- - (Object) set_relationship(key, value) Sets a relationship to the given value.
Class Method Details
+ (Object) included(base)
122 123 124 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 122 def self.included(base) base.extend ClassMethods end |
Instance Method Details
- (Object) destroy_relationship(name, *args)
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.
245 246 247 248 249 250 251 252 253 254 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 245 def destroy_relationship(name, *args) = self.class.relationships_hash[name] return unless && [:klass].respond_to?(:destroy_relationship) # Read relationship, which forces lazy relationships to load, which is # probably necessary for destroying read_relationship(name) [:klass].destroy_relationship(self, relationship_data[name], *args) end |
- (Object) destroy_relationships(*args)
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.
234 235 236 237 238 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 234 def destroy_relationships(*args) self.class.relationships.each do |name, | destroy_relationship(name, *args) end end |
- (Boolean) has_relationship?(key)
Returns boolean denoting if a relationship exists.
267 268 269 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 267 def has_relationship?(key) self.class.has_relationship?(key.to_sym) end |
- (Boolean) lazy_relationship?(key)
Returns boolean denoting if a relationship is to be lazy loaded.
274 275 276 277 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 274 def lazy_relationship?(key) = self.class.relationships_hash[key.to_sym] !.nil? && [:lazy] end |
- (Boolean) loaded_relationship?(key)
Returns boolean denoting if a relationship has been loaded.
280 281 282 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 280 def loaded_relationship?(key) relationship_data.has_key?(key) end |
- (Object) populate_relationship(name, data)
Populate a single relationship.
225 226 227 228 229 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 225 def populate_relationship(name, data) = self.class.relationships_hash[name] return unless [:klass].respond_to?(:populate_relationship) relationship_data[name] = [:klass].populate_relationship(self, data) end |
- (Object) populate_relationships(data)
The equivalent to Attributable#populate_attributes, but with relationships.
218 219 220 221 222 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 218 def populate_relationships(data) self.class.relationships.each do |name, | populate_relationship(name, data) unless lazy_relationship?(name) end end |
- (Object) read_relationship(name)
Reads a relationship. This is equivalent to Attributable#read_attribute, but for relationships.
183 184 185 186 187 188 189 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 183 def read_relationship(name) if lazy_relationship?(name) && !loaded_relationship?(name) load_relationship(name) end relationship_data[name.to_sym] end |
- (Hash) relationship_data
Hash to data associated with relationships. You should instead use the accessors created by Relatable.
260 261 262 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 260 def relationship_data @relationship_data ||= {} end |
- (Object) save_relationship(name, *args)
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.
210 211 212 213 214 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 210 def save_relationship(name, *args) = self.class.relationships_hash[name] return unless [:klass].respond_to?(:save_relationship) [:klass].save_relationship(self, relationship_data[name], *args) end |
- (Object) save_relationships(*args)
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.
201 202 203 204 205 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 201 def save_relationships(*args) self.class.relationships.each do |name, | save_relationship(name, *args) end end |
- (Object) set_relationship(key, value)
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=
.
297 298 299 300 301 302 303 304 |
# File 'lib/virtualbox/abstract_model/relatable.rb', line 297 def set_relationship(key, value) key = key.to_sym relationship = self.class.relationships_hash[key] return unless relationship raise Exceptions::NonSettableRelationshipException.new unless relationship[:klass].respond_to?(:set_relationship) relationship_data[key] = relationship[:klass].set_relationship(self, relationship_data[key], value) end |