Module: PathTree

Includes:
Patterns
Defined in:
lib/path_tree.rb,
lib/path_tree/version.rb,
lib/path_tree/patterns.rb

Overview

This module implements a tree structure by using a convention of converting a name into a path. Paths created by normalizing a name attribute and then separating levels with periods with the lowest level coming last.

In order to use this module, the model must respond to the first and all methods like ActiveRecord, have support for after_destroy and after_save callbacks, validates_* macros and include attributes for name, node_path, path, and parent_path.

Defined Under Namespace

Modules: ClassMethods, Patterns

Constant Summary collapse

VERSION =
'1.1.0'.freeze

Constants included from Patterns

Patterns::LOWER_AE_PATTERN, Patterns::LOWER_A_PATTERN, Patterns::LOWER_C_PATTERN, Patterns::LOWER_E_PATTERN, Patterns::LOWER_I_PATTERN, Patterns::LOWER_N_PATTERN, Patterns::LOWER_O_PATTERN, Patterns::LOWER_U_PATTERN, Patterns::LOWER_Y_PATTERN, Patterns::SS_PATTERN, Patterns::UPPER_AE_PATTERN, Patterns::UPPER_A_PATTERN, Patterns::UPPER_C_PATTERN, Patterns::UPPER_D_PATTERN, Patterns::UPPER_E_PATTERN, Patterns::UPPER_I_PATTERN, Patterns::UPPER_N_PATTERN, Patterns::UPPER_O_PATTERN, Patterns::UPPER_U_PATTERN, Patterns::UPPER_Y_PATTERN

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/path_tree.rb', line 13

def self.included (base)
  base.extend(ClassMethods)
  
  base.validates_uniqueness_of :path
  base.validates_uniqueness_of :node_path, :scope => :parent_path
  base.validates_presence_of :name, :node_path, :path
  
  base.after_save do |record|
    if record.path_changed? and !record.path_was.nil?
      record.children.each do |child|
        child.update_attributes(:parent_path => record.path)
      end
    end
    record.instance_variable_set(:@children, nil)
  end
  
  base.after_destroy do |record|
    record.children.each do |child|
      child.update_attributes(:parent_path => record.parent_path)
    end
  end
end

Instance Method Details

#ancestorsObject

Get all ancestors of this node with the root node first.



218
219
220
221
222
223
224
225
226
# File 'lib/path_tree.rb', line 218

def ancestors
  ancestor_paths = expanded_paths
  ancestor_paths.pop
  if ancestor_paths.empty?
    []
  else
    self.class.base_class.where(path: ancestor_paths).to_a.sort{|a,b| a.path.length <=> b.path.length}
  end
end

#childrenObject

Get all nodes that are direct children of this node.



197
198
199
200
201
202
203
204
# File 'lib/path_tree.rb', line 197

def children
  unless @children
    childrens_path = new_record? ? path : path_was
    @children = self.class.base_class.where(parent_path: childrens_path).to_a
    @children.each{|c| c.parent = self}
  end
  @children
end

#descendantsObject

Get all descendant of this node.



213
214
215
# File 'lib/path_tree.rb', line 213

def descendants
  self.class.base_class.path_like(path)
end

#expanded_pathsObject

Returns an array containing the paths of this node and those of all its ancestors.



229
230
231
# File 'lib/path_tree.rb', line 229

def expanded_paths
  self.class.expanded_paths(path)
end

#full_name(options = {}) ⇒ Object

Get the full name of a node including the names of all it’s parent nodes. Specify the separator string to use between values with :separator (defaults to “ > ”). You can also specify the context for the full name by specifying a path in :context. This will only render the names up to and not including that part of the tree.



141
142
143
144
145
146
147
# File 'lib/path_tree.rb', line 141

def full_name (options = {})
  separator = options[:separator] || " > "
  n = ""
  n << parent.full_name(options) if parent_path and parent_path != options[:context]
  n << separator unless n.blank?
  n << name
end

#name=(value) ⇒ Object



182
183
184
185
186
187
188
# File 'lib/path_tree.rb', line 182

def name= (value)
  unless value == name
    self[:name] = value
    self.node_path = value if node_path.blank?
  end
  value
end

#node_path=(value) ⇒ Object



190
191
192
193
194
# File 'lib/path_tree.rb', line 190

def node_path= (value)
  pathified = self.class.pathify(value)
  self[:node_path] = pathified
  recalculate_path
end

#parentObject

Get the parent node.



154
155
156
157
158
159
160
161
162
163
# File 'lib/path_tree.rb', line 154

def parent
  unless instance_variable_defined?(:@parent)
    if path.index(path_delimiter)
      @parent = self.class.base_class.where(path: parent_path).first
    else
      @parent = nil
    end
  end
  @parent
end

#parent=(node) ⇒ Object

Set the parent node.



166
167
168
169
170
# File 'lib/path_tree.rb', line 166

def parent= (node)
  node_path = node.path if node
  self.parent_path = node_path unless parent_path == node_path
  @parent = node
end

#parent_path=(value) ⇒ Object

Set the parent path



173
174
175
176
177
178
179
180
# File 'lib/path_tree.rb', line 173

def parent_path= (value)
  unless value == parent_path
    self[:parent_path] = value
    recalculate_path
    remove_instance_variable(:@parent) if instance_variable_defined?(:@parent)
  end
  value
end

#path_delimiterObject



149
150
151
# File 'lib/path_tree.rb', line 149

def path_delimiter
  self.class.path_delimiter
end

#siblingsObject

Get all nodes that share the same parent as this node.



207
208
209
210
# File 'lib/path_tree.rb', line 207

def siblings
  # OPTIMIZE if this record has an ID, it may be more efficient to exclude it via query rather than using reject
  self.class.base_class.where(parent_path: parent_path).to_a.reject{|node| node == self }
end