diff --git a/lib/closure_tree/hierarchy_maintenance.rb b/lib/closure_tree/hierarchy_maintenance.rb index f7c5492d..2278b5a9 100644 --- a/lib/closure_tree/hierarchy_maintenance.rb +++ b/lib/closure_tree/hierarchy_maintenance.rb @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/lib/closure_tree/model.rb b/lib/closure_tree/model.rb index cc64ae4d..3dd1c76e 100644 --- a/lib/closure_tree/model.rb +++ b/lib/closure_tree/model.rb @@ -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], @@ -28,7 +30,7 @@ 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' @@ -36,13 +38,27 @@ def hash_tree(options = {}) 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: diff --git a/lib/closure_tree/support.rb b/lib/closure_tree/support.rb index e887090e..f4c409ae 100644 --- a/lib/closure_tree/support.rb +++ b/lib/closure_tree/support.rb @@ -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 @@ -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 @@ -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 { @@ -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 diff --git a/lib/closure_tree/support_attributes.rb b/lib/closure_tree/support_attributes.rb index 38879ade..162518fa 100644 --- a/lib/closure_tree/support_attributes.rb +++ b/lib/closure_tree/support_attributes.rb @@ -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 diff --git a/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb b/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb index 18d7671c..56f69173 100644 --- a/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb +++ b/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb @@ -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