Module: Mongoid::SleepingKingStudios::HasTree::CacheAncestry

Extended by:
ActiveSupport::Concern, Concern
Defined in:
lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry.rb,
lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry/metadata.rb

Overview

Adds #ancestors and #descendents methods for accessing ancestors and subtrees with a single read operation. Do not include this module directly; rather, add a :cache_ancestry => true options to the call to ::has_tree.

Examples:

Setting up a tree with ancestry cache:

class SluggableDocument
  include Mongoid::Document
  include Mongoid::SleepingKingStudios::Tree

  has_tree :cache_ancestry => true
end # class

Since:

  • 0.5.0

Defined Under Namespace

Classes: Metadata

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Concern

characterize, relate, valid_options, validate_options

Instance Attribute Details

#ancestor_idsArray (readonly)

Stores the ids of the object's ancestors, starting from the root object of the current subtree to the object's current parent. If the object has no parent, returns an empty array.

Returns:

  • (Array)

    The ancestors' ids.

Since:

  • 0.5.0


# File 'lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry.rb', line 162

Class Method Details

.apply(base, metadata) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Sets up the ancestry caching concern.

Parameters:

  • base (Class)

    The base class into which the concern is mixed in.

  • options (Hash)

    The options for the relation.

Since:

  • 0.6.0


35
36
37
38
39
40
41
42
# File 'lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry.rb', line 35

def self.apply base, 
  name = :has_tree_cache_ancestry
  validate_options name, .properties

  define_fields    base, 
  define_accessors base, 
  define_helpers   base, 
end

.define_accessors(base, metadata) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Overwrites the parent id setter to update the ancestor ids field, and defines the ancestors and descendents methods.

Parameters:

  • base (Class)

    The base class into which the concern is mixed in.

  • metadata (Metadata)

    The metadata for the relation.

Since:

  • 0.6.0


53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry.rb', line 53

def self.define_accessors base, 
  parent_id_writer = base.instance_method .parent_foreign_key_writer

  base.re_define_method .parent_foreign_key_writer do |value|
    old_ancestor_ids = send(.foreign_key).dup

    parent_id_writer.bind(self).call value
    new_ancestor_ids = send(.parent_name) ?
      send(.parent_name).send(.foreign_key) + [send(.parent_name).id] :
      []

    descendents.each do |descendent|
      ary = descendent.send(.foreign_key).dup
      ary[0..old_ancestor_ids.count] = new_ancestor_ids + [id]
      descendent.update_attributes .foreign_key => ary
    end # each
    
    send :"#{.foreign_key}=", new_ancestor_ids
  end # method

  base.send :define_method, .relation_name do
    begin
      self.class.find(send(.foreign_key))
    rescue Mongoid::Errors::DocumentNotFound, Mongoid::Errors::InvalidFind
      raise Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor.new .relation_name, send(.foreign_key)
    end # begin-rescue
  end # method

  base.send :define_method, :descendents do
    criteria = self.class.all

    send(.foreign_key).each_with_index do |ancestor_id, index|
      criteria = criteria.where(:"#{.foreign_key}.#{index}" => ancestor_id)
    end # each

    criteria.where(:"#{.foreign_key}.#{send(.foreign_key).count}" => id)
  end # method descendents
end

.define_fields(base, metadata) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Defines the foreign key field on the base class.

Parameters:

  • base (Class)

    The base class into which the concern is mixed in.

  • metadata (Metadata)

    The metadata for the relation.

Since:

  • 0.6.0


100
101
102
# File 'lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry.rb', line 100

def self.define_fields base, 
  base.send :field, .foreign_key, :type => Array, :default => []
end

.define_helpers(base, metadata) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Defines the rebuild and validate ancestry helper methods.

Parameters:

  • base (Class)

    The base class into which the concern is mixed in.

  • metadata (Metadata)

    The metadata for the relation.

Since:

  • 0.6.0


112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry.rb', line 112

def self.define_helpers base, 
  base.send :define_method, :rebuild_ancestry! do
    begin
      ary, object = [], self
      while object.send(.parent_name)
        ary.unshift object.send(.parent_name).id
        object = object.send(.parent_name)
      end # while
      self.send :"#{.foreign_key}=", ary
    rescue Mongoid::Errors::DocumentNotFound
      raise Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor.new "#{relation_name}", object.send(.parent_foreign_key)
    end # begin-rescue
  end # method rebuild_ancestry!

  base.send :define_method, :validate_ancestry! do
    return if send(.foreign_key).empty?

    ancestors = []
    send(.foreign_key).each_with_index do |ancestor_id, index|
      begin
        ancestor = self.class.find(ancestor_id)
        ancestors << ancestor

        if index > 0 && ancestor.send(.parent_foreign_key) != send(.foreign_key)[index - 1]
          # If the ancestor's parent is not the same as the previous
          # ancestor.
          raise Mongoid::SleepingKingStudios::HasTree::Errors::UnexpectedAncestor.new "#{.relation_name}", ancestor.send(.parent_foreign_key), send(.foreign_key)[index - 1]
        end # if
      rescue Mongoid::Errors::InvalidFind, Mongoid::Errors::DocumentNotFound
        # If the ancestor id is nil, or the ancestor does not exist.
        raise Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor.new "#{.relation_name}", ancestor_id
      end # begin-rescue
    end # each with index
  end # method validate_ancestry!
end

.valid_optionsArray<Symbol>

Get the valid options allowed with this concern.

Returns:

  • (Array<Symbol>)

    The valid options.

Since:

  • 0.5.1


153
154
155
156
157
158
159
160
# File 'lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry.rb', line 153

def self.valid_options
  %i(
    children_name # Internal; do not set directly.
    foreign_key
    parent_name   # Internal; do not set directly.
    relation_name
  ) # end Array
end

Instance Method Details

#ancestorsArray

Returns an array of the current object's ancestors, from the root object to the current parent. If the object has no parent, returns an empty array. If an error is raised, consider calling #rebuild_ancestry!

Returns:

  • (Array)

    The objects' ancestors

Raises:


# File 'lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry.rb', line 171

#descendentsMongoid::Criteria

Returns a scope for all of the descendents of the current object, i.e. all objects that have the current object as an ancestor.

Returns:

  • (Mongoid::Criteria)

    The criteria for finding the descendents.


# File 'lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry.rb', line 184

#rebuild_ancestry!Object

Travels up the tree using the #parent method and saves the ancestors to the :ancestor_ids field. This overwrites the value of :ancestor_ids on the current object, but not on any of its ancestors.

Raises:


# File 'lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry.rb', line 190

#validate_ancestry!Object

Travels up the tree using the :ancestor_ids and ensures that each ancestor exists and is persisted to the database, and that the object's parent correctly matches the last value in its own :ancestor_ids field.

Raises:


# File 'lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry.rb', line 198