Module: WithRecursiveTree::InstanceMethods

Defined in:
lib/with_recursive_tree.rb,
lib/with_recursive_tree/backport.rb

Instance Method Summary collapse

Instance Method Details

#ancestorsObject



76
77
78
# File 'lib/with_recursive_tree.rb', line 76

def ancestors
  self_and_ancestors.excluding self
end

#depthObject Also known as: level



88
89
90
# File 'lib/with_recursive_tree.rb', line 88

def depth
  attributes["depth"] || ancestors.count
end

#descendantsObject



80
81
82
# File 'lib/with_recursive_tree.rb', line 80

def descendants
  self_and_descendants.excluding self
end

#leaf?Boolean

Returns:

  • (Boolean)


84
85
86
# File 'lib/with_recursive_tree.rb', line 84

def leaf?
  children.none?
end

#rootObject



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/with_recursive_tree.rb', line 93

def root
  return self if root?

  if self.class.with_recursive_tree_foreign_key_type.present?
    # For foreign_key_type, find the first ancestor that satisfies root conditions
    self_and_ancestors.where(
      self.class.with_recursive_tree_foreign_key => nil,
      self.class.with_recursive_tree_foreign_key_type => [nil, self.class.name]
    ).or(
      self_and_ancestors.where.not(self.class.with_recursive_tree_foreign_key => nil)
        .where.not(self.class.with_recursive_tree_foreign_key_type => self.class.name)
    ).first
  else
    self_and_ancestors.find_by self.class.with_recursive_tree_foreign_key => nil
  end
end

#root?Boolean

Returns:

  • (Boolean)


110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/with_recursive_tree.rb', line 110

def root?
  foreign_key_value = send(self.class.with_recursive_tree_foreign_key)

  if self.class.with_recursive_tree_foreign_key_type.present?
    foreign_key_type_value = send(self.class.with_recursive_tree_foreign_key_type)

    # Root conditions with foreign_key_type:
    # 1. foreign_key is nil AND foreign_key_type is nil
    # 2. foreign_key is nil AND foreign_key_type matches the model's class name
    # 3. foreign_key is not nil AND foreign_key_type is different from model's class name
    (foreign_key_value.nil? && [nil, self.class.name].include?(foreign_key_type_value)) ||
      (foreign_key_value.present? && foreign_key_type_value != self.class.name)
  else
    foreign_key_value.nil?
  end
end

#self_and_ancestorsObject



127
128
129
130
131
132
133
134
135
136
# File 'lib/with_recursive_tree.rb', line 127

def self_and_ancestors
  scope_condition = self.class.with_recursive_tree_foreign_key_type.present? ? {"tree.#{self.class.with_recursive_tree_foreign_key_type}" => self.class.name} : nil

  self.class.with_recursive(
    tree: [
      self.class.where(self.class.with_recursive_tree_primary_key => send(self.class.with_recursive_tree_primary_key)),
      self.class.joins("JOIN tree ON #{self.class.table_name}.#{self.class.with_recursive_tree_primary_key} = tree.#{self.class.with_recursive_tree_foreign_key}").where(scope_condition)
    ]
  ).select("*").from("tree AS #{self.class.table_name}")
end

#self_and_descendantsObject



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/with_recursive_tree.rb', line 138

def self_and_descendants
  anchor_path = if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
    "ARRAY[#{self.class.with_recursive_tree_order_column}]::text[]"
  elsif defined?(ActiveRecord::ConnectionAdapters::MySQL)
    "CAST(CONCAT('/', #{self.class.with_recursive_tree_primary_key}, '/') AS CHAR(512))"
  elsif defined?(ActiveRecord::ConnectionAdapters::SQLite3Adapter)
    "'/' || #{self.class.with_recursive_tree_primary_key} || '/'"
  end

  recursive_path = if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
    "tree.path || #{self.class.table_name}.#{self.class.with_recursive_tree_order_column}::text"
  elsif defined?(ActiveRecord::ConnectionAdapters::MySQL)
    "CONCAT(tree.path, #{self.class.table_name}.#{self.class.with_recursive_tree_primary_key}, '/')"
  elsif defined?(ActiveRecord::ConnectionAdapters::SQLite3)
    "tree.path || #{self.class.table_name}.#{self.class.with_recursive_tree_primary_key} || '/'"
  end

  scope_condition = self.class.with_recursive_tree_foreign_key_type.present? ? {"#{self.class.table_name}.#{self.class.with_recursive_tree_foreign_key_type}" => self.class.name} : nil

  recursive_query = self.class.joins("JOIN tree ON #{self.class.table_name}.#{self.class.with_recursive_tree_foreign_key} = tree.#{self.class.with_recursive_tree_primary_key}").select("#{self.class.table_name}.*, #{recursive_path} AS path, depth + 1 AS depth").where scope_condition

  unless defined?(ActiveRecord::ConnectionAdapters::MySQL)
    recursive_query = recursive_query.order(self.class.with_recursive_tree_order)
  end

  self.class.with_recursive(
    tree: [
      self.class.where(self.class.with_recursive_tree_primary_key => send(self.class.with_recursive_tree_primary_key)).select("*, #{anchor_path} AS path, 0 AS depth"),
      Arel.sql(recursive_query.to_sql)
    ]
  ).select("*").from("tree AS #{self.class.table_name}")
end

#self_and_siblingsObject



171
172
173
# File 'lib/with_recursive_tree.rb', line 171

def self_and_siblings
  root? ? self.class.roots : parent.children
end

#siblingsObject



175
176
177
# File 'lib/with_recursive_tree.rb', line 175

def siblings
  self_and_siblings.excluding self
end