module Locomotive module Extensions module Page module Tree extend ActiveSupport::Concern included do include ::Mongoid::Acts::Tree ## fields ## field :position, :type => Integer ## indexes ## index :position index [[:depth, :asc], [:position, :asc]] ## behaviours ## acts_as_tree :order => ['position', 'asc'] ## callbacks ## before_validation :reset_parent before_save { |p| p.send(:write_attribute, :parent_id, nil) if p.parent_id.blank? } before_save :change_parent before_create { |p| p.send(:fix_position, false) } before_create :add_to_list_bottom before_destroy :remove_from_list # Fixme (Didier L.): Instances methods are defined before the include itself alias :fix_position :hacked_fix_position alias :descendants :hacked_descendants end module ClassMethods # Warning: should be used only in read-only def quick_tree(site, minimal_attributes = true) pages = (minimal_attributes ? site.pages.minimal_attributes : site.pages).order_by([[:depth, :asc], [:position, :asc]]).to_a tmp = [] while !pages.empty? tmp << _quick_tree(pages.delete_at(0), pages) end tmp end def _quick_tree(current_page, pages) i, children = 0, [] while !pages.empty? page = pages[i] break if page.nil? if page.parent_id == current_page.id page = pages.delete_at(i) children << _quick_tree(page, pages) else i += 1 end end current_page.instance_eval do def children=(list); @children = list; end def children; @children || []; end end current_page.children = children current_page end end module InstanceMethods def children? self.class.where(self.parent_id_field => self.id).count end def children_with_minimal_attributes self.class.where(self.parent_id_field => self.id). order_by(self.tree_order). minimal_attributes end def sort_children!(ids) ids.each_with_index do |id, position| child = self.children.detect { |p| p._id == BSON::ObjectId(id) } child.position = position child.save end end def parent=(owner) # missing in acts_as_tree @_parent = owner self.fix_position(false) self.instance_variable_set :@_will_move, true end def hacked_descendants return [] if new_record? self.class.all_in(path_field => [self._id]).order_by tree_order end protected def change_parent if self.parent_id_changed? self.fix_position(false) unless self.parent_id_was.nil? self.position = nil # make it move to bottom self.add_to_list_bottom end self.instance_variable_set :@_will_move, true end end def hacked_fix_position(perform_save = true) if parent.nil? self.write_attribute parent_id_field, nil self[path_field] = [] self[depth_field] = 0 else self.write_attribute parent_id_field, parent._id self[path_field] = parent[path_field] + [parent._id] self[depth_field] = parent[depth_field] + 1 self.save if perform_save end end def reset_parent if self.parent_id_changed? @_parent = nil end end def add_to_list_bottom self.position ||= (::Page.where(:_id.ne => self._id).and(:parent_id => self.parent_id).max(:position) || 0) + 1 end def remove_from_list return if (self.site rescue nil).nil? ::Page.where(:parent_id => self.parent_id).and(:position.gt => self.position).each do |p| p.position -= 1 p.save end end end end end end end