Module: Ancestry::MaterializedPath

Included in:
MaterializedPath2
Defined in:
lib/ancestry/materialized_path.rb

Overview

store ancestry as grandparent_id/parent_id root a=nil,id=1 children=id,id/% == 1, 1/% 3: a=1/2,id=3 children=a/id,a/id/% == 1/2/3, 1/2/3/%

Defined Under Namespace

Modules: InstanceMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.construct_depth_sql(table_name, ancestry_column, ancestry_delimiter) ⇒ Object



136
137
138
139
140
# File 'lib/ancestry/materialized_path.rb', line 136

def self.construct_depth_sql(table_name, ancestry_column, ancestry_delimiter)
  tmp = %{(LENGTH(#{table_name}.#{ancestry_column}) - LENGTH(REPLACE(#{table_name}.#{ancestry_column},'#{ancestry_delimiter}','')))}
  tmp += "/#{ancestry_delimiter.size}" if ancestry_delimiter.size > 1
  "(CASE WHEN #{table_name}.#{ancestry_column} IS NULL THEN 0 ELSE 1 + #{tmp} END)"
end

.extended(base) ⇒ Object



8
9
10
# File 'lib/ancestry/materialized_path.rb', line 8

def self.extended(base)
  base.send(:include, InstanceMethods)
end

Instance Method Details

#ancestors_of(object) ⇒ Object



20
21
22
23
24
# File 'lib/ancestry/materialized_path.rb', line 20

def ancestors_of(object)
  t = arel_table
  node = to_node(object)
  where(t[primary_key].in(node.ancestor_ids))
end

#ancestry_depth_change(old_value, new_value) ⇒ Object



124
125
126
# File 'lib/ancestry/materialized_path.rb', line 124

def ancestry_depth_change(old_value, new_value)
  parse_ancestry_column(new_value).size - parse_ancestry_column(old_value).size
end

#ancestry_depth_sqlObject



105
106
107
# File 'lib/ancestry/materialized_path.rb', line 105

def ancestry_depth_sql
  @ancestry_depth_sql ||= MaterializedPath.construct_depth_sql(table_name, ancestry_column, ancestry_delimiter)
end

#ancestry_rootObject



93
94
95
# File 'lib/ancestry/materialized_path.rb', line 93

def ancestry_root
  nil
end

#child_ancestry_sqlObject



97
98
99
100
101
102
103
# File 'lib/ancestry/materialized_path.rb', line 97

def child_ancestry_sql
  %{
    CASE WHEN #{table_name}.#{ancestry_column} IS NULL THEN #{concat("#{table_name}.#{primary_key}")}
    ELSE      #{concat("#{table_name}.#{ancestry_column}", "'#{ancestry_delimiter}'", "#{table_name}.#{primary_key}")}
    END
  }
end

#children_of(object) ⇒ Object



32
33
34
35
36
# File 'lib/ancestry/materialized_path.rb', line 32

def children_of(object)
  t = arel_table
  node = to_node(object)
  where(t[ancestry_column].eq(node.child_ancestry))
end

#concat(*args) ⇒ Object



128
129
130
131
132
133
134
# File 'lib/ancestry/materialized_path.rb', line 128

def concat(*args)
  if %w(sqlite sqlite3).include?(connection.adapter_name.downcase)
    args.join('||')
  else
    %{CONCAT(#{args.join(', ')})}
  end
end

#descendant_before_last_save_conditions(object) ⇒ Object



59
60
61
62
# File 'lib/ancestry/materialized_path.rb', line 59

def descendant_before_last_save_conditions(object)
  node = to_node(object)
  descendants_by_ancestry(node.child_ancestry_before_last_save)
end

#descendant_conditions(object) ⇒ Object



54
55
56
57
# File 'lib/ancestry/materialized_path.rb', line 54

def descendant_conditions(object)
  node = to_node(object)
  descendants_by_ancestry(node.child_ancestry)
end

#descendants_by_ancestry(ancestry) ⇒ Object



49
50
51
52
# File 'lib/ancestry/materialized_path.rb', line 49

def descendants_by_ancestry(ancestry)
  t = arel_table
  t[ancestry_column].matches("#{ancestry}#{ancestry_delimiter}%", nil, true).or(t[ancestry_column].eq(ancestry))
end

#descendants_of(object) ⇒ Object



45
46
47
# File 'lib/ancestry/materialized_path.rb', line 45

def descendants_of(object)
  where(descendant_conditions(object))
end

#generate_ancestry(ancestor_ids) ⇒ Object



109
110
111
112
113
114
115
# File 'lib/ancestry/materialized_path.rb', line 109

def generate_ancestry(ancestor_ids)
  if ancestor_ids.present? && ancestor_ids.any?
    ancestor_ids.join(ancestry_delimiter)
  else
    ancestry_root
  end
end

#indirects_of(object) ⇒ Object

indirect = anyone who is a descendant, but not a child



39
40
41
42
43
# File 'lib/ancestry/materialized_path.rb', line 39

def indirects_of(object)
  t = arel_table
  node = to_node(object)
  where(t[ancestry_column].matches("#{node.child_ancestry}#{ancestry_delimiter}%", nil, true))
end

#inpath_of(object) ⇒ Object



26
27
28
29
30
# File 'lib/ancestry/materialized_path.rb', line 26

def inpath_of(object)
  t = arel_table
  node = to_node(object)
  where(t[primary_key].in(node.path_ids))
end

#ordered_by_ancestry(order = nil) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/ancestry/materialized_path.rb', line 76

def ordered_by_ancestry(order = nil)
  if %w(mysql mysql2 sqlite sqlite3).include?(connection.adapter_name.downcase)
    reorder(arel_table[ancestry_column], order)
  elsif %w(postgresql oracleenhanced).include?(connection.adapter_name.downcase) && ActiveRecord::VERSION::STRING >= "6.1"
    reorder(Arel::Nodes::Ascending.new(arel_table[ancestry_column]).nulls_first, order)
  else
    reorder(
      Arel::Nodes::Ascending.new(Arel::Nodes::NamedFunction.new('COALESCE', [arel_table[ancestry_column], Arel.sql("''")])),
      order
    )
  end
end

#ordered_by_ancestry_and(order) ⇒ Object



89
90
91
# File 'lib/ancestry/materialized_path.rb', line 89

def ordered_by_ancestry_and(order)
  ordered_by_ancestry(order)
end

#parse_ancestry_column(obj) ⇒ Object



117
118
119
120
121
122
# File 'lib/ancestry/materialized_path.rb', line 117

def parse_ancestry_column(obj)
  return [] if obj.nil? || obj == ancestry_root

  obj_ids = obj.split(ancestry_delimiter).delete_if(&:blank?)
  primary_key_is_an_integer? ? obj_ids.map!(&:to_i) : obj_ids
end

#path_of(object) ⇒ Object



12
13
14
# File 'lib/ancestry/materialized_path.rb', line 12

def path_of(object)
  to_node(object).path
end

#rootsObject



16
17
18
# File 'lib/ancestry/materialized_path.rb', line 16

def roots
  where(arel_table[ancestry_column].eq(ancestry_root))
end

#siblings_of(object) ⇒ Object



70
71
72
73
74
# File 'lib/ancestry/materialized_path.rb', line 70

def siblings_of(object)
  t = arel_table
  node = to_node(object)
  where(t[ancestry_column].eq(node[ancestry_column].presence))
end

#subtree_of(object) ⇒ Object



64
65
66
67
68
# File 'lib/ancestry/materialized_path.rb', line 64

def subtree_of(object)
  t = arel_table
  node = to_node(object)
  descendants_of(node).or(where(t[primary_key].eq(node.id)))
end