class Relationship
Summay:
This class stores the relationship between any two objects. It will store one of the objects
as :base_type and :base_id, and the other object as :related_type and :related_id. When you create a new relationship it will sort the objects first by type (class name) then by ID, this allows us to validate the uniqueness of the relationship and improves certain types of lookups.
The two main methods for looking up related objects are `related_objects` and `grouped_related_objects`.
You can see the comments above them for more details, but what they return are a collection of RelatedObject objects. I was returning hashes but creating a simple wrapper class better defines what gets returned. Those methods will first grab the relationships for a given object, then convert them into RelatedObjects, then load the actual object they reference into each RelatedObject.
Example of what a RelatedObject is relationship.related_object(object_a) => <RelatedObject object_type = "related_type", object_id = "related_id", relationships = <Relationship> object = <object_b>
Constants
- CLEAN_TYPES
Attributes
Public Class Methods
Lookup a relationship from two given objects
# File lib/buweb/relationship.rb, line 75 def self.find_by_objects(base_object, related_object) return nil if base_object.blank? || related_object.blank? self.where({ base_type: base_object.class.to_s, base_id: base_object.id.to_s, related_type: related_object.class.to_s, related_id: related_object.id.to_s }).first end
Returns an array of Relationships grouped by type Example {
"PageEdition": [<Relationship>]
}
# File lib/buweb/relationship.rb, line 113 def self.grouped_relationships(relationships: [], group_by: :related_type) Hash.new.tap do |group| # First group all the relationships by type and index by id relationships.each do |relationship| if group_by == :related_type type_key, id_key = [relationship.related_type, relationship.related_id] elsif group_by == :base_type type_key, id_key = [relationship.base_type, relationship.base_id] else raise "group_by is not valid - #{group_by}" end group[type_key] ||= {} group[type_key][id_key] ||= [] group[type_key][id_key].push(relationship) end # Then we want to load the real objects into each related_object. # We lookup all the objects for each type by their ID, then loop through # each result and assign the object to their related_object in `group`. # Going into it `group` will look something like this # { # "PageEdition": { # "558c4b507275622fcf000002": [<Relationship>] # }, # "Department" : {...} # } group.each do |type, id_groupings| if klass = type.safe_constantize klass.where({id: { '$in' => id_groupings.keys }}).each do |real_object| Array(group[type][real_object.id.to_s]).each do |relationship| if group_by == :related_type relationship.related_object = real_object elsif group_by == :base_type relationship.base_object = real_object end end end end end # Remove the id indexing so there is just an array of relationships # for each type group.each do |type, id_groupings| group[type] = id_groupings.values.flatten end end end
By default will load all the related objects into the relationships and return arrays of relationships grouped by related_type Example:
{
"PageEdition": [<Relationship>, <Relationship>], "Department": [<Relationship>]
}
# File lib/buweb/relationship.rb, line 93 def self.load_objects(group_by: :related_type) grouped_relationships(relationships: scoped, group_by: group_by) end
Will create a new relationship between two given objects.
# File lib/buweb/relationship.rb, line 64 def self.new_from_objects(base_object, related_object) return nil if base_object.blank? || related_object.blank? self.new({ base_type: base_object.class.to_s, base_id: base_object.id.to_s, related_type: related_object.class.to_s, related_id: related_object.id.to_s }) end
Public Instance Methods
# File lib/buweb/relationship.rb, line 104 def base_object @base_object ||= object!(base_type, base_id) end
Private Instance Methods
# File lib/buweb/relationship.rb, line 164 def object!(klass, id) klass = CLEAN_TYPES.find { |k| k == klass }.try(:safe_constantize) return unless id && klass klass.where(id: id).first end
This will also ensure that base_object
and related_object
aren't the same object.
# File lib/buweb/relationship.rb, line 187 def validate_different_objects return unless base_type == related_type && base_id == related_id errors.add(:base, 'Cannot relate an object to itself') false end
# File lib/buweb/relationship.rb, line 170 def validate_objects_exist return if base_object && related_object errors.add(:base, 'Related object doesn\'t exist') false end
# File lib/buweb/relationship.rb, line 176 def validate_relationship_unique return unless Relationship.where( base_type: base_type, base_id: base_id, related_type: related_type, related_id: related_id ).present? errors.add(:base, 'Relationship already exists') false end