安装和配置
默认已经有category这个模型,ancestry需要和category结合使用

#category的创建
rails g model category name:string
#安装
gem 'ancestry'

#终端执行代码
rails g migration add_ancestry_to_[table] ancestry:string:index #这里是category

#migration文件
class AddAncestryToCategory < ActiveRecord::Migration[5.1]
  def change
    add_column :categories, :ancestry, :string
    add_index :categories, :ancestry
  end
end

#category.rb文件中方法执行
class Category < ApplicationRecord
  has_ancestry orphan_strategy: :destroy
end

使用

#建立一个父category
one = Category.create(name: "one")

#在父节点的基础上建立子节点
two = one.children.create(name: "two")

#获得该节点的子节点对象
one.children

#获得该节点的父节点对象
two.parent


#获得模型的根节点,即是第一层的节点
Category.roots

#判断是否为父节点
Category.first.root?

#获得父节点对象的id值
Category.find(10).ancestry

在前端中创建ancestry
说明:一般在category这model中会建立title字段,按照以上说述,会增加ancestry这个字段,实质上,title字段和ancestry字段没有任何关系,ancestry这个字段的值是这个category对象的父节点的id值,如果ancestry的值为null,则说明这个节点是root节点。
按照说明中提到的原理,进行前端界面的设计和创建。参考商城的代码
categories_controller.rb

class Admin::CategoriesController < Admin::BaseController

  before_action :find_root_categories, only: [:new, :create, :edit, :update]
  before_action :find_category, only: [:edit, :update, :destroy]

  def index
    if params[:id].blank?
      @categories = Category.roots
    else
      @category = Category.find(params[:id])
      @categories = @category.children
    end

    @categories = @categories.page(params[:page] || 1).per_page(params[:per_page] || 10)
      .order(id: "desc")
  end

  def new
    @category = Category.new
  end

  def create
    @category = Category.new(params.require(:category).permit!)

    if @category.save
      flash[:notice] = "保存成功"
      redirect_to admin_categories_path
    else
      render action: :new
    end
  end

  def edit
    render action: :new
  end

  def update
    @category.attributes = params.require(:category).permit!

    if @category.save
      flash[:notice] = "修改成功"
      redirect_to admin_categories_path
    else
      render action: :new
    end
  end

  def destroy
    if @category.destroy
      flash[:notice] = "删除成功"
      redirect_to admin_categories_path
    else
      flash[:notice] = "删除失败"
      redirect_to :back
    end
  end

  private
    def find_root_categories
      @root_categories = Category.roots.order(id: "desc")
    end

    def find_category
      @category = Category.find(params[:id])
    end
end

views/admin/categories/new.html.erb和index.html.erb

#new.html.erb
<div>
  <h1><%= @category.new_record? ? "新建分类" : "修改分类 ##{params[:id]}" %></h1>
</div>

<div class="form-body">
  <%= form_for @category, 
    url: (@category.new_record? ? admin_categories_path : admin_category_path(@category)), 
    method: (@category.new_record? ? 'post' : 'put'), 
    html: { class: 'form-horizontal' } do |f| %>

    <% unless @category.errors.blank? %>
      <div class="alert alert-danger">
        <ul class="list-unstyled">
          <% @category.errors.messages.values.flatten.each do |error| %>
            <li><i class="fa fa-exclamation-circle"></i> <%= error %></li>
          <% end -%>
        </ul>
      </div>
    <% end -%>

    <div class="form-group">
      <label for="ancestry" class="col-sm-2 control-label">所属分类:</label>
      <div class="col-sm-5">
        <select name="category[ancestry]">
          <option></option>
          <% @root_categories.each do |category| %>
            <% next if category == @category %>
            <option value="<%= category.id %>" <%= @category.ancestry == category.id.to_s ? 'selected' : '' %>>
            <%= category.title %></option>
          <% end -%>
        </select>
        为空为一级分类
      </div>
    </div>
    <div class="form-group">
      <label for="title" class="col-sm-2 control-label">名称:*</label>
      <div class="col-sm-5">
        <%= f.text_field :title, class: "form-control" %>
      </div>
    </div>
    <div class="form-group">
      <label for="weight" class="col-sm-2 control-label">权重:</label>
      <div class="col-sm-5">
        <%= f.text_field :weight, class: "form-control" %> 数值越大越靠前
      </div>
    </div>
    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-8">
        <%= f.submit (@category.new_record? ? "新建分类" : "编辑分类"), class: "btn btn-default" %>
      </div>
    </div>
  <% end -%>
</div>
#index.html.erb
<div>
  <div class="pull-right">
    <%= link_to "新建分类", new_admin_category_path, class: "btn btn-primary" %>
  </div>

  <h1>
    <% if @category %>
      分类:<%= @category.title %>(<%= @categories.total_entries %>)
    <% else %>
      分类(<%= @categories.total_entries %>)
    <% end -%>
  </h1>
</div>

<div>
  <table class="table table-striped">
    <tr>
      <th>ID</th>
      <th>名称</th>
      <th>Weight</th>
      <th></th>
    </tr>

    <% @categories.each do |category| %>
      <tr>
        <td><%= category.id %></td>
        <td><%= category.title %></td>
        <td><%= category.weight %></td>
        <td align="right">
          <%= link_to "编辑", edit_admin_category_path(category) %> 
          <%= link_to "删除", admin_category_path(category), method: :delete, 'data-confirm': "确认删除吗?" %>
          <% if category.root? %>
            <%= link_to "查看子分类", admin_categories_path(id: category) %>
          <% end -%>
        </td>
      </tr>
    <% end -%>
  </table>

  <%= will_paginate @categories %>
</div>

前端显示一二级标签

#category.rb中利用grouped_data生成一二级标签数组
class Category < ApplicationRecord

  def self.grouped_data
    self.roots.order("weight desc").inject([]) do |result, parent|
      row = []
      row << parent
      row << parent.children.order("weight desc")
      result << row
    end
  end
end

#app/controllers/categories_controller.rb
class CategoriesController < ApplicationController
  def fetch_home_data
    @categories = Category.grouped_data
  end
end
#app/views/shared/_categories.html.erb
<ul class="list-group">
  <% @categories.each do |group| %>
    <li class="list-group-item"><%= group.first.title %></li>
    <% group.last.each do |sub_category| %>
      <li class="list-group-item"><a href="<%= category_path(sub_category) %>"><%= sub_category.title %></a></li>
    <% end -%>
  <% end -%>
</ul>

注意事项
对于如上成的空值,即是ancestry字段为空值,但是这里会报错,解决方式是在model中添加如下代码:

#category.rb
class Category < ApplicationRecord
  has_ancestry orphan_strategy: :destroy

  before_validation :correct_ancestry

  private
    def correct_ancestry
      self.ancestry = nil if self.ancestry.blank?
    end
end

解决N+1问题

#改变字段类型,从string变为integer
rails g migration add_ancestry_to_[table] ancestry:integer:index

#进行自关联
class Category < ApplicationRecord
  has_many :subordinates, class_name: "Category", foreign_key: "ancestry"
end

#不使用children,而使用includes
Category.includes(:subordinates)

results matching ""

    No results matching ""