安装和配置
默认已经有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)