Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions lib/closure_tree/hierarchy_maintenance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ def rebuild!(called_by_rebuild = false)
unless root?
_ct.connection.execute <<-SQL.squish
INSERT INTO #{_ct.quoted_hierarchy_table_name}
(ancestor_id, descendant_id, generations)
SELECT x.ancestor_id, #{_ct.quote(_ct_id)}, x.generations + 1
(ancestor_id, ancestor_type, descendant_id, descendant_type, generations)
SELECT #{_ct.quote(parent.id)}, #{_ct.quote(parent.class.to_s)}, #{_ct.quote(_ct_id)}, x.descendant_type, x.generations + 1
FROM #{_ct.quoted_hierarchy_table_name} x
WHERE x.descendant_id = #{_ct.quote(_ct_parent_id)}
WHERE x.descendant_id = #{_ct.quote(_ct_parent_id)} AND x.descendant_type = #{_ct.quote(self.class.to_s)}
SQL
end

Expand All @@ -79,7 +79,7 @@ def rebuild!(called_by_rebuild = false)
_ct_reorder_siblings if !called_by_rebuild
end

children.find_each { |c| c.rebuild!(true) }
children.each { |c| c.rebuild!(true) }

_ct_reorder_children if _ct.order_is_numeric? && children.present?
end
Expand All @@ -97,8 +97,8 @@ def delete_hierarchy_references
SELECT DISTINCT descendant_id
FROM (SELECT descendant_id
FROM #{_ct.quoted_hierarchy_table_name}
WHERE ancestor_id = #{_ct.quote(id)}
OR descendant_id = #{_ct.quote(id)}
WHERE (ancestor_id = #{_ct.quote(id)} AND ancestor_type = #{_ct.quote(self.class.to_s)})
OR (descendant_id = #{_ct.quote(id)} AND descendant_type = #{_ct.quote(self.class.to_s)})
) #{ _ct.t_alias_keyword } x )
SQL
end
Expand All @@ -119,13 +119,15 @@ def cleanup!
hierarchy_table = hierarchy_class.arel_table

[:descendant_id, :ancestor_id].each do |foreign_key|
alias_name = foreign_key.to_s.split('_').first + "s"
key_prefix = foreign_key.to_s.split('_')
key_as_type = "#{key_prefix}_type".to_sym
alias_name = key_prefix.first + "s"
alias_table = Arel::Table.new(table_name).alias(alias_name)
arel_join = hierarchy_table.join(alias_table, Arel::Nodes::OuterJoin)
.on(alias_table[primary_key].eq(hierarchy_table[foreign_key]))
.join_sources

lonely_childs = hierarchy_class.joins(arel_join).where(alias_table[primary_key].eq(nil))
lonely_childs = hierarchy_class.joins(arel_join).where(alias_table[primary_key].eq(nil)).where(key_as_type => self.class.to_s)
ids = lonely_childs.pluck(foreign_key)

hierarchy_class.where(hierarchy_table[foreign_key].in(ids)).delete_all
Expand Down
28 changes: 22 additions & 6 deletions lib/closure_tree/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ module Model
extend ActiveSupport::Concern

included do

belongs_to :parent, nil,
class_name: _ct.model_class.to_s,
foreign_key: _ct.parent_column_name,
inverse_of: :children,
touch: _ct.options[:touch],
optional: true
optional: true,
polymorphic: true

where_for_ancestors = { ancestor_type: _ct.model_class.to_s }
where_for_descendants = { descendant_type: _ct.model_class.to_s }
order_by_generations = -> { Arel.sql("#{_ct.quoted_hierarchy_table_name}.generations ASC") }

has_many :children, *_ct.has_many_order_with_option, **{
has_many :children, *_ct.has_many_order_with_option_and_where({ parent_type: _ct.model_class.to_s }), **{
class_name: _ct.model_class.to_s,
foreign_key: _ct.parent_column_name,
dependent: _ct.options[:dependent],
Expand All @@ -28,21 +30,35 @@ def hash_tree(options = {})
end
end

has_many :ancestor_hierarchies, *_ct.has_many_order_without_option(order_by_generations),
has_many :ancestor_hierarchies, *_ct.has_many_order_without_option_and_where(order_by_generations, where_for_ancestors),
class_name: _ct.hierarchy_class_name,
foreign_key: 'descendant_id'

has_many :self_and_ancestors, *_ct.has_many_order_without_option(order_by_generations),
through: :ancestor_hierarchies,
source: :ancestor

has_many :descendant_hierarchies, *_ct.has_many_order_without_option(order_by_generations),
has_many :descendant_hierarchies, *_ct.has_many_order_without_option_and_where(order_by_generations, where_for_descendants),
class_name: _ct.hierarchy_class_name,
foreign_key: 'ancestor_id'

has_many :self_and_descendants, *_ct.has_many_order_with_option(order_by_generations),
through: :descendant_hierarchies,
source: :descendant
source: :descendant, source_type: _ct.model_class.to_s
end

def poly_children
pp hierarchy_class.all.map { |h| h.slice(:ancestor_type, :ancestor_id, :descendant_type, :descendant_id, :generations) }

foo = hierarchy_class
.where(ancestor_id: id, ancestor_type: _ct.model_class.to_s, generations: 1)
.map { |h_data| { type: h_data.descendant_type, id: h_data.descendant_id } }
.group_by { |h_data| h_data[:type] }
.map { |type, h_data_array| type.constantize.where(id: h_data_array.map { |h_data| h_data[:id] }) }
.flatten

p foo
foo
end

# Delegate to the Support instance on the class:
Expand Down
20 changes: 15 additions & 5 deletions lib/closure_tree/support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,10 @@ def hierarchy_class_for_model
hierarchy_class = parent_class.const_set(short_hierarchy_class_name, Class.new(model_class.superclass))
use_attr_accessible = use_attr_accessible?
include_forbidden_attributes_protection = include_forbidden_attributes_protection?
model_class_name = model_class.to_s
hierarchy_class.class_eval do
include ActiveModel::ForbiddenAttributesProtection if include_forbidden_attributes_protection
belongs_to :ancestor, class_name: model_class_name
belongs_to :descendant, class_name: model_class_name
belongs_to :ancestor, polymorphic: true
belongs_to :descendant, polymorphic: true
attr_accessible :ancestor, :descendant, :generations if use_attr_accessible
def ==(other)
self.class == other.class && ancestor_id == other.ancestor_id && descendant_id == other.descendant_id
Expand All @@ -58,8 +57,7 @@ def hierarchy_table_name
# We need to use the table_name, not something like ct_class.to_s.demodulize + "_hierarchies",
# because they may have overridden the table name, which is what we want to be consistent with
# in order for the schema to make sense.
tablename = options[:hierarchy_table_name] ||
remove_prefix_and_suffix(table_name).singularize + "_hierarchies"
tablename = "hierarchies"

ActiveRecord::Base.table_name_prefix + tablename + ActiveRecord::Base.table_name_suffix
end
Expand All @@ -84,6 +82,10 @@ def has_many_order_without_option(order_by_opt)
[lambda { order(order_by_opt.call) }]
end

def has_many_order_without_option_and_where(order_by_opt, where_clause)
[lambda { where(where_clause).order(order_by_opt.call) }]
end

def has_many_order_with_option(order_by_opt=nil)
order_options = [order_by_opt, order_by].compact
[lambda {
Expand All @@ -92,6 +94,14 @@ def has_many_order_with_option(order_by_opt=nil)
}]
end

def has_many_order_with_option_and_where(order_by_opt=nil, where_clause)
order_options = [order_by_opt, order_by].compact
[lambda {
order_options = order_options.map { |o| o.is_a?(Proc) ? o.call : o }
where(where_clause).order(order_options)
}]
end

def ids_from(scope)
scope.pluck(model_class.primary_key)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/closure_tree/support_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def quoted_value(value)
end

def hierarchy_class_name
options[:hierarchy_class_name] || model_class.to_s + "Hierarchy"
"Hierarchy"
end

def primary_key_column
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
def change
create_table :<%= migration_name %>, id: false do |t|
create_table :hierarchies, id: false do |t|
t.<%= primary_key_type %> :ancestor_id, null: false
t.string :ancestor_type, null: false
t.<%= primary_key_type %> :descendant_id, null: false
t.string :descendant_type, null: false
t.integer :generations, null: false
end

add_index :<%= migration_name %>, [:ancestor_id, :descendant_id, :generations],
add_index :hierarchies, [:ancestor_id, :ancestor_type, :descendant_id, :descendant_type, :generations],
unique: true,
name: "<%= file_name %>_anc_desc_idx"
name: "anc_desc_idx"

add_index :<%= migration_name -%>, [:descendant_id],
name: "<%= file_name %>_desc_idx"
add_index :hierarchies, [:descendant_id],
name: "desc_idx"
end
end