Module: Models::Lineage::ClassMethods
- Defined in:
- app/concerns/models/lineage.rb
Overview
ActiveSupport::Concern mixin: class methods.
Belongs to collapse
-
#parent ⇒ ApplicationRecord, ...
The parent node of this record.
Has many collapse
-
#children ⇒ ActiveRecord::Relation, ActiveRecord::Relation<Child>
The direct child nodes of this record.
Instance Method Summary collapse
-
#acts_as_lineage(options = {}) ⇒ void
Sets up a self-referential parent/children tree (
belongs_to :parent,has_many :children) plus lineage scopes and class helpers. -
#ancestors_ids(*ids) ⇒ Array<Integer>
Returns the ids of all ancestors of the given records, at any depth.
-
#descendants_ids(*ids) ⇒ Array<Integer>
Returns the ids of all descendants of the given records, at any depth.
-
#generate_order_by(*ids) ⇒ Arel::Nodes::SqlLiteral
Builds an
ORDER BYfragment that sorts records in the order of the given ids. -
#root_ids(*ids) ⇒ Array<Integer>
Returns the ids of the root records of the trees containing the given records.
-
#self_ancestors_and_descendants_ids(*ids) ⇒ Array<Integer>
Returns the given ids plus the ids of all their ancestors and descendants.
-
#self_and_ancestors_ids(*ids) ⇒ Array<Integer>
Returns the given ids plus the ids of all their ancestors.
-
#self_and_descendants_ids(*ids) ⇒ Array<Integer>
Returns the given ids plus the ids of all their descendants.
Instance Method Details
#acts_as_lineage(options = {}) ⇒ void
This method returns an undefined value.
Sets up a self-referential parent/children tree (belongs_to :parent,
has_many :children) plus lineage scopes and class helpers.
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'app/concerns/models/lineage.rb', line 25 def acts_as_lineage( = {}) configuration = { foreign_key: :parent_id, order: nil, counter_cache: nil, dependent: :destroy } configuration.update() if .is_a?(Hash) # The parent node of this record. # @return [ApplicationRecord, nil] the parent node belongs_to :parent, class_name: name, foreign_key: configuration[:foreign_key], counter_cache: configuration[:counter_cache], inverse_of: :children, optional: true # The direct child nodes of this record. # @return [ActiveRecord::Relation] the child nodes has_many :children, -> { order(configuration[:order]) }, class_name: name, foreign_key: configuration[:foreign_key], dependent: configuration[:dependent], inverse_of: :parent # NOTE: scopes moved to class_eval to use configured foreign_key class_eval %{ def self.roots order_option = "#{configuration.dig(:order)}" r = where(#{configuration[:foreign_key]}: nil) r = r.order(order_option) if order_option.present? r end def self.root self.roots.first end scope :parents_only, -> { where(#{configuration[:foreign_key]}: nil) } scope :children_only, -> { where.not(#{configuration[:foreign_key]}: nil) } scope :descendants_lin, ->(pid) { where(id: self_and_descendants_ids(pid) ) } scope :ancestors_lin, ->(pid) { where(id: self_and_ancestors_ids(pid)) } scope :descendants_only, ->(pid) { where(id: descendants_ids(pid)) } }, __FILE__, __LINE__ - 18 end |
#ancestors_ids(*ids) ⇒ Array<Integer>
Returns the ids of all ancestors of the given records, at any depth.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'app/concerns/models/lineage.rb', line 106 def ancestors_ids(*ids) ids = [ids].flatten.filter_map(&:presence).uniq return [] if ids.blank? foreign_key = reflect_on_all_associations.find { |a| a.name == :children }.foreign_key sql = Arel.sql("with recursive t(level,#{foreign_key},id) as (select 0,#{foreign_key},id from #{table_name} where id IN (#{ids.join(',')}) union select t.level+1,c.#{foreign_key},c.id from #{table_name} c join t on c.id = t.#{foreign_key}) select id from t where id not in (#{ids.join(',')});") res = ActiveRecord::Base.lease_connection.execute(sql) begin res.map { |r| r['id'].to_i }.filter_map(&:presence).uniq rescue StandardError [] end end |
#children ⇒ ActiveRecord::Relation, ActiveRecord::Relation<Child>
The direct child nodes of this record.
46 47 48 49 50 51 |
# File 'app/concerns/models/lineage.rb', line 46 has_many :children, -> { order(configuration[:order]) }, class_name: name, foreign_key: configuration[:foreign_key], dependent: configuration[:dependent], inverse_of: :parent |
#descendants_ids(*ids) ⇒ Array<Integer>
Returns the ids of all descendants of the given records, at any depth.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'app/concerns/models/lineage.rb', line 79 def descendants_ids(*ids) ids = [ids].flatten.filter_map(&:presence).uniq return [] if ids.blank? foreign_key = reflect_on_all_associations.find { |a| a.name == :children }.foreign_key sql = Arel.sql("with recursive t(level,#{foreign_key},id) as (select 0,#{foreign_key},id from #{table_name} where #{foreign_key} IN (#{ids.join(',')}) union select t.level+1,c.#{foreign_key},c.id from #{table_name} c join t on c.#{foreign_key} = t.id) select distinct id from t order by id;") res = ActiveRecord::Base.lease_connection.execute(sql) begin res.map { |r| r['id'].to_i }.filter_map(&:presence).uniq rescue StandardError [] end end |
#generate_order_by(*ids) ⇒ Arel::Nodes::SqlLiteral
Builds an ORDER BY fragment that sorts records in the order of the given ids.
160 161 162 |
# File 'app/concerns/models/lineage.rb', line 160 def generate_order_by(*ids) Arel.sql([ids].flatten.map(&:presence).uniq.compact.map { |i| "#{table_name}.id = #{i} desc" }.join(',')) end |
#parent ⇒ ApplicationRecord, ...
The parent node of this record.
37 38 39 40 41 42 |
# File 'app/concerns/models/lineage.rb', line 37 belongs_to :parent, class_name: name, foreign_key: configuration[:foreign_key], counter_cache: configuration[:counter_cache], inverse_of: :children, optional: true |
#root_ids(*ids) ⇒ Array<Integer>
Returns the ids of the root records of the trees containing the given records.
125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'app/concerns/models/lineage.rb', line 125 def root_ids(*ids) ids = ids.flatten.filter_map(&:presence).uniq return [] if ids.blank? foreign_key = reflect_on_all_associations.find { |a| a.name == :children }.foreign_key sql = Arel.sql("with recursive t(level,parent_id,id) as (select 0,#{foreign_key},id from #{table_name} where id IN (#{ids.join(',')}) union select t.level + 1,c.#{foreign_key},c.id from #{table_name} c join t on c.id = t.parent_id) select id from t where t.parent_id IS NULL;") res = ActiveRecord::Base.lease_connection.execute(sql) begin res.map { |r| r['id'].to_i }.filter_map(&:presence).uniq rescue StandardError [] end end |
#self_ancestors_and_descendants_ids(*ids) ⇒ Array<Integer>
Returns the given ids plus the ids of all their ancestors and descendants.
152 153 154 |
# File 'app/concerns/models/lineage.rb', line 152 def self_ancestors_and_descendants_ids(*ids) (ancestors_ids(ids) + ids + descendants_ids(ids)).flatten.filter_map(&:presence).uniq end |
#self_and_ancestors_ids(*ids) ⇒ Array<Integer>
Returns the given ids plus the ids of all their ancestors.
144 145 146 |
# File 'app/concerns/models/lineage.rb', line 144 def self_and_ancestors_ids(*ids) (ancestors_ids(ids) + ids).flatten.filter_map(&:presence).uniq end |
#self_and_descendants_ids(*ids) ⇒ Array<Integer>
Returns the given ids plus the ids of all their descendants.
98 99 100 |
# File 'app/concerns/models/lineage.rb', line 98 def self_and_descendants_ids(*ids) (ids + descendants_ids(ids)).flatten.filter_map(&:presence).uniq end |