Rails 通过父类找出所有子类的问题,在一张表中,谢谢!

d4rkl0rd · 2014年02月25日 · 最后由 d4rkl0rd 回复于 2014年02月26日 · 4769 次阅读

求教,通过父类找出所有子类的问题。

有张类别表 types,字段如下: id name type_id 内容: 1 汽车 0 2 红色汽车 1

就这样汽车是红色汽车的父类,我建立模型 type: class Type < ActiveRecord::Base has_many :subordinates, class_name: "Type", foreign_key: "type_id"

belongs_to :type, class_name: "Type" end 测试: a = Type.find 1 a.type 这样无法找出红色汽车

但是 a = Type.find 2 a.type 这样能找出红色汽车父类

想问下,该如何配置呢?我有父类 ID,想查找出所有子类,谢谢。

@Peter 用了 awesome_nested_set

但是执行很多方法报: NoMethodError: undefined method `quoted_table_name' for ActiveRecord::AttributeMethods::Serialization::Type:Class

我用的 rails4 对 rails4 支持不好?

目前版本的 awesome_nested_set 确实对 rails4 支持不好。 戳这里: http://ruby-china.org/topics/15943

a = Type.find 1 a.type 这样无法找出红色汽车

改成:a.subordinates 即可

是不是树形结构,oracle 还是直接用 start with...connect by... 写 sql 好了

可以用 预排序遍历树算法 :)

自己实现的,有些地方不怎么样,轻喷

class Category < ActiveRecord::Base
  include Concerns::RecursionTree

  # 先序遍历, 拿到 [depth, name]
  # 注意:由于 Rails 将不存在的列名过滤,此处只能使用 lft 来存储深度!!
  scope :get_sorted_array, -> do
    Category
    .joins('AS node, categories AS parent')
    .select('node.id, node.name, node.slug, node.model_id, (COUNT(parent.slug)) - 1 AS lft')
    .where('node.lft BETWEEN parent.lft AND parent.rgt')
    .group('node.parent_id, node.slug')
    .order('node.lft')
  end
end
#
# 预排序遍历树算法(modified preorder tree traversal algorithm)
# 来存储无限分级的树结构,以改进查询的时空复杂度。render 需要实现 get_sorted_array
#
module Concerns
  module RecursionTree
    extend ActiveSupport::Concern

    included do
      attr_accessor :children, :depth
    end

    # 同级插入
    def place_with(id)
      klass = self.class
      if new_record?
        klass.transaction do
          brother = klass.select(:lft, :rgt, :parent_id).find(id)
          raise AlertError.new('不能用虚拟的根节点作为兄弟节点') if brother.lft == 0
          parent, right = brother.parent_id, brother.rgt
          klass.where('rgt > ?', right).update_all('rgt = rgt + 2')
          klass.where('lft > ?', right).update_all('lft = lft + 2')
          self.parent_id = parent
          self.lft = right + 1
          self.rgt = right + 2
          self.save!
        end
      end
    end


    # 节点追加
    def append_to(id)
      klass = self.class
      if new_record?
        klass.transaction do
          left = klass.select(:lft).find(id).lft
          klass.where('rgt > ?', left).update_all('rgt = rgt + 2')
          klass.where('lft > ?', left).update_all('lft = lft + 2')
          self.parent_id = id
          self.lft = left + 1
          self.rgt = left + 2
          self.save!
        end
      end
    end

    # 删除节点
    def remove
      klass = self.class
      klass.transaction do
        left, right = self.lft, self.rgt
        distance = (right - left - 1) / 2
        klass.where('lft BETWEEN ? AND ?', left, right).delete_all
        klass.where('rgt > ?', right).update_all(['rgt = rgt - ?', distance])
        klass.where('lft > ?', right).update_all(['lft = lft - ?', distance])
      end
    end

    module ClassMethods
      # 采用迭代和栈替代递归,避免递归带来的内存问题
      def render
        @nodes = get_sorted_array
        root = @nodes.shift # 出队无意义的根节点
        current_depth = -1
        item_path = [root]
        @nodes.each do |item|
          depth = item.depth = item.lft # 留意 get_sorted_array 方法的注释
          if depth > current_depth
            (item_path[-1].children ||= []) << item
            item_path << item
          elsif depth == current_depth
            item_path[-1] = item
            item_path[-2].children << item
          else
            until item_path.pop.lft == depth; end
            item_path[-1].children << item
            item_path << item
          end
          current_depth = depth
        end
        root
      end
    end
  end
end

感谢各位,已解决。

Peter Rails 树形结构分组查询问题 提及了此话题。 04月09日 17:03
需要 登录 后方可回复, 如果你还没有账号请 注册新账号