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
- .child_ancestry_sql(table_name, ancestry_column, primary_key, delimiter, adapter) ⇒ Object
- .child_ancestry_value(ancestry_value, id, delimiter) ⇒ Object
- .concat(adapter, *args) ⇒ Object
- .construct_depth_sql(table_name, ancestry_column, ancestry_delimiter) ⇒ Object
-
.descendants_condition(attr, child_ancestry, delimiter) ⇒ Object
Arel condition: descendants have ancestry matching child_ancestry or starting with child_ancestry/.
- .extended(base) ⇒ Object
- .generate(ancestor_ids, delimiter, root) ⇒ Object
-
.indirects_condition(attr, child_ancestry, delimiter) ⇒ Object
Arel condition: indirects have ancestry matching child_ancestry/*/.
- .parse(obj, root, delimiter, integer_pk) ⇒ Object
- .validation_options(primary_key_format, delimiter) ⇒ Object
Instance Method Summary collapse
- #ancestors_of(object) ⇒ Object
- #ancestry_depth_change(old_value, new_value) ⇒ Object
- #ancestry_depth_sql ⇒ Object
- #ancestry_root ⇒ Object
- #child_ancestry_sql ⇒ Object
- #children_of(object) ⇒ Object
- #concat(*args) ⇒ Object
- #descendant_before_last_save_conditions(object) ⇒ Object
- #descendant_conditions(object) ⇒ Object
- #descendants_by_ancestry(ancestry) ⇒ Object
- #descendants_of(object) ⇒ Object
- #generate_ancestry(ancestor_ids) ⇒ Object
- #indirects_of(object) ⇒ Object
- #inpath_of(object) ⇒ Object
- #ordered_by_ancestry(order = nil) ⇒ Object
- #ordered_by_ancestry_and(order) ⇒ Object
- #parse_ancestry_column(obj) ⇒ Object
- #path_of(object) ⇒ Object
- #roots ⇒ Object
- #siblings_of(object) ⇒ Object
- #subtree_of(object) ⇒ Object
Class Method Details
.child_ancestry_sql(table_name, ancestry_column, primary_key, delimiter, adapter) ⇒ Object
152 153 154 155 156 157 158 159 160 |
# File 'lib/ancestry/materialized_path.rb', line 152 def self.child_ancestry_sql(table_name, ancestry_column, primary_key, delimiter, adapter) pk_sql = concat(adapter, "#{table_name}.#{primary_key}") full_sql = concat(adapter, "#{table_name}.#{ancestry_column}", "'#{delimiter}'", "#{table_name}.#{primary_key}") %{ CASE WHEN #{table_name}.#{ancestry_column} IS NULL THEN #{pk_sql} ELSE #{full_sql} END } end |
.child_ancestry_value(ancestry_value, id, delimiter) ⇒ Object
126 127 128 |
# File 'lib/ancestry/materialized_path.rb', line 126 def self.child_ancestry_value(ancestry_value, id, delimiter) [ancestry_value, id].compact.join(delimiter) end |
.concat(adapter, *args) ⇒ Object
144 145 146 147 148 149 150 |
# File 'lib/ancestry/materialized_path.rb', line 144 def self.concat(adapter, *args) if %w(sqlite sqlite3).include?(adapter) args.join('||') else %{CONCAT(#{args.join(', ')})} end end |
.construct_depth_sql(table_name, ancestry_column, ancestry_delimiter) ⇒ Object
162 163 164 165 166 |
# File 'lib/ancestry/materialized_path.rb', line 162 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 |
.descendants_condition(attr, child_ancestry, delimiter) ⇒ Object
Arel condition: descendants have ancestry matching child_ancestry or starting with child_ancestry/
131 132 133 |
# File 'lib/ancestry/materialized_path.rb', line 131 def self.descendants_condition(attr, child_ancestry, delimiter) attr.matches("#{child_ancestry}#{delimiter}%", nil, true).or(attr.eq(child_ancestry)) end |
.extended(base) ⇒ Object
8 9 10 |
# File 'lib/ancestry/materialized_path.rb', line 8 def self.extended(base) base.send(:include, InstanceMethods) end |
.generate(ancestor_ids, delimiter, root) ⇒ Object
111 112 113 114 115 116 117 |
# File 'lib/ancestry/materialized_path.rb', line 111 def self.generate(ancestor_ids, delimiter, root) if ancestor_ids.present? && ancestor_ids.any? ancestor_ids.join(delimiter) else root end end |
.indirects_condition(attr, child_ancestry, delimiter) ⇒ Object
Arel condition: indirects have ancestry matching child_ancestry/*/
136 137 138 |
# File 'lib/ancestry/materialized_path.rb', line 136 def self.indirects_condition(attr, child_ancestry, delimiter) attr.matches("#{child_ancestry}#{delimiter}%", nil, true) end |
.parse(obj, root, delimiter, integer_pk) ⇒ Object
119 120 121 122 123 124 |
# File 'lib/ancestry/materialized_path.rb', line 119 def self.parse(obj, root, delimiter, integer_pk) return [] if obj.nil? || obj == root obj_ids = obj.split(delimiter).delete_if(&:blank?) integer_pk ? obj_ids.map!(&:to_i) : obj_ids end |
.validation_options(primary_key_format, delimiter) ⇒ Object
168 169 170 171 172 173 |
# File 'lib/ancestry/materialized_path.rb', line 168 def self.(primary_key_format, delimiter) { format: {with: /\A#{primary_key_format}(#{Regexp.escape(delimiter)}#{primary_key_format})*\z/.freeze}, allow_nil: true } 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
107 108 109 |
# File 'lib/ancestry/materialized_path.rb', line 107 def ancestry_depth_change(old_value, new_value) parse_ancestry_column(new_value).size - parse_ancestry_column(old_value).size end |
#ancestry_depth_sql ⇒ Object
95 96 97 |
# File 'lib/ancestry/materialized_path.rb', line 95 def ancestry_depth_sql @ancestry_depth_sql ||= MaterializedPath.construct_depth_sql(table_name, ancestry_column, ancestry_delimiter) end |
#ancestry_root ⇒ Object
87 88 89 |
# File 'lib/ancestry/materialized_path.rb', line 87 def ancestry_root nil end |
#child_ancestry_sql ⇒ Object
91 92 93 |
# File 'lib/ancestry/materialized_path.rb', line 91 def child_ancestry_sql MaterializedPath.child_ancestry_sql(table_name, ancestry_column, primary_key, ancestry_delimiter, connection.adapter_name.downcase) end |
#children_of(object) ⇒ Object
32 33 34 35 |
# File 'lib/ancestry/materialized_path.rb', line 32 def children_of(object) node = to_node(object) where(arel_table[ancestry_column].eq(node.child_ancestry)) end |
#concat(*args) ⇒ Object
140 141 142 |
# File 'lib/ancestry/materialized_path.rb', line 140 def concat(*args) MaterializedPath.concat(connection.adapter_name.downcase, *args) end |
#descendant_before_last_save_conditions(object) ⇒ Object
55 56 57 58 |
# File 'lib/ancestry/materialized_path.rb', line 55 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
50 51 52 53 |
# File 'lib/ancestry/materialized_path.rb', line 50 def descendant_conditions(object) node = to_node(object) descendants_by_ancestry(node.child_ancestry) end |
#descendants_by_ancestry(ancestry) ⇒ Object
46 47 48 |
# File 'lib/ancestry/materialized_path.rb', line 46 def descendants_by_ancestry(ancestry) MaterializedPath.descendants_condition(arel_table[ancestry_column], ancestry, ancestry_delimiter) end |
#descendants_of(object) ⇒ Object
42 43 44 |
# File 'lib/ancestry/materialized_path.rb', line 42 def descendants_of(object) where(descendant_conditions(object)) end |
#generate_ancestry(ancestor_ids) ⇒ Object
99 100 101 |
# File 'lib/ancestry/materialized_path.rb', line 99 def generate_ancestry(ancestor_ids) MaterializedPath.generate(ancestor_ids, ancestry_delimiter, ancestry_root) end |
#indirects_of(object) ⇒ Object
37 38 39 40 |
# File 'lib/ancestry/materialized_path.rb', line 37 def indirects_of(object) node = to_node(object) where(MaterializedPath.indirects_condition(arel_table[ancestry_column], node.child_ancestry, ancestry_delimiter)) 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
70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/ancestry/materialized_path.rb', line 70 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
83 84 85 |
# File 'lib/ancestry/materialized_path.rb', line 83 def ordered_by_ancestry_and(order) ordered_by_ancestry(order) end |
#parse_ancestry_column(obj) ⇒ Object
103 104 105 |
# File 'lib/ancestry/materialized_path.rb', line 103 def parse_ancestry_column(obj) MaterializedPath.parse(obj, ancestry_root, ancestry_delimiter, primary_key_is_an_integer?) 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 |
#roots ⇒ Object
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
65 66 67 68 |
# File 'lib/ancestry/materialized_path.rb', line 65 def siblings_of(object) node = to_node(object) where(arel_table[ancestry_column].eq(node[ancestry_column].presence)) end |
#subtree_of(object) ⇒ Object
60 61 62 63 |
# File 'lib/ancestry/materialized_path.rb', line 60 def subtree_of(object) node = to_node(object) descendants_of(node).or(where(arel_table[primary_key].eq(node.id))) end |