In the application development, I added a category function using a gem called ancestry, so I summarized it.
Install ancestry.
gemfile
gem 'ancestry'
Next, create a category model.
rails g model category
Describe has_ancestry.
app/models/category.rb
class Category < ApplicationRecord
has_many :posts
has_ancestry
end
Describe it in the migration file as follows. About index here
db/migrate/20XXXXXXXXXXXX_create_categories.rb
class CreateCategories < ActiveRecord::Migration[6.0]
def change
create_table :categories do |t|
t.string :name, index: true, null: false
t.string :ancestry, index: true
t.timestamps
end
end
end
I will describe the category in the google spreadsheet. The A column is id, the B column is name (category name), and the C column is ancestry (numerical value that distinguishes parents and grandchildren). You can save the data by following the procedure of File → Download → Comma-separated values (.csv current sheet).
Place the downloaded csv file in the db folder.
Describe as follows in the seeds.rb file.
db/seeds.rb
require "csv"
CSV.foreach('db/category.csv') do |row|
Category.create(:id => row[0], :name => row[1], :ancestry => row[2])
end
When you execute the rails db: seed command in the terminal, the csv file is read and the DB record is automatically generated. Specify the file you want to read after foreach. The description below it will be model name.create (column name => column you want to load). row [0] → column A is id row [1] → B column is name (category name) row [2] → C column is ancestry (a number that distinguishes parents and grandchildren)
Set the routing of child and grandchild categories in json format.
config/routes.rb
Rails.application.routes.draw do
~Abbreviation~
resources :posts do
collection do
get 'top'
get 'get_category_children', defaults: { format: 'json' }
get 'get_category_grandchildren', defaults: { format: 'json' }
get 'name_search'
end
~Abbreviation~
end
Define a parent category for the posts controller. Since it is used in multiple places, it is defined using before_action.
app/controllers/posts_controller.rb
def set_parents
@parents = Category.where(ancestry: nil)
end
Define methods for child and grandchild categories in the posts controller.
app/controllers/posts_controller.rb
def get_category_children
@category_children = Category.find("#{params[:parent_id]}").children
end
def get_category_grandchildren
@category_grandchildren = Category.find("#{params[:child_id]}").children
end
Create a json.jbuilder file and convert it to json data.
ruby:app/views/posts/get_category_children.json.jbuilder
json.array! @category_children do |child|
json.id child.id
json.name child.name
end
ruby:app/views/posts/get_category_grandchildren.json.jbuilder
json.array! @category_grandchildren do |grandchild|
json.id grandchild.id
json.name grandchild.name
end
Set the operation when selecting a category with javascript.
:app/javascript/category_post.js
$(function(){
function appendOption(category){
var html = `<option value="${category.id}">${category.name}</option>`;
return html;
}
function appendChildrenBox(insertHTML){
var childSelectHtml = "";
childSelectHtml = `<div class="category__child" id="children_wrapper">
<select id="child__category" name="post[category_id]" class="serect_field">
<option value="">---</option>
${insertHTML}
</select>
</div>`;
$('.append__category').append(childSelectHtml);
}
function appendGrandchildrenBox(insertHTML){
var grandchildSelectHtml = "";
grandchildSelectHtml = `<div class="category__child" id="grandchildren_wrapper">
<select id="grandchild__category" name="post[category_id]" class="serect_field">
<option value="">---</option>
${insertHTML}
</select>
</div>`;
$('.append__category').append(grandchildSelectHtml);
}
$('#item_category_id').on('change',function(){
var parentId = document.getElementById('item_category_id').value;
if (parentId != ""){
$.ajax({
url: '/posts/get_category_children/',
type: 'GET',
data: { parent_id: parentId },
dataType: 'json'
})
.done(function(children){
$('#children_wrapper').remove();
$('#grandchildren_wrapper').remove();
var insertHTML = '';
children.forEach(function(child){
insertHTML += appendOption(child);
});
appendChildrenBox(insertHTML);
if (insertHTML == "") {
$('#children_wrapper').remove();
}
})
.fail(function(){
alert('Failed to get the category');
})
}else{
$('#children_wrapper').remove();
$('#grandchildren_wrapper').remove();
}
});
$('.append__category').on('change','#child__category',function(){
var childId = document.getElementById('child__category').value;
if(childId != ""){
$.ajax({
url: '/posts/get_category_grandchildren',
type: 'GET',
data: { child_id: childId },
dataType: 'json'
})
.done(function(grandchildren){
$('#grandchildren_wrapper').remove();
var insertHTML = '';
grandchildren.forEach(function(grandchild){
insertHTML += appendOption(grandchild);
});
appendGrandchildrenBox(insertHTML);
if (insertHTML == "") {
$('#grandchildren_wrapper').remove();
}
})
.fail(function(){
alert('Failed to get the category');
})
}else{
$('#grandchildren_wrapper').remove();
}
})
});
Display the category select box on the new post page.
ruby:app/views/posts/new.html.erb
<div class="append__category">
<div class="category">
<div class="form__label">
<div class="weight-bold-text lavel__name ">
Category
</div>
<div class="lavel__Required">
<%= f.collection_select :category_id, @parents, :id, :name,{ include_blank: "Please select"},class:"serect_field", id:"item_category_id" %>
</div>
</div>
</div>
</div>
app/controllers/posts_controller.rb
def top
respond_to do |format|
format.html
format.json do
if params[:parent_id]
@childrens = Category.find(params[:parent_id]).children
elsif params[:children_id]
@grandChilds = Category.find(params[:children_id]).children
elsif params[:gcchildren_id]
@parents = Category.where(id: params[:gcchildren_id])
end
end
end
end
In javascript, I get the child category and grandchild category that belong to which parent category the mouse is on.
:app/javascript/category.js
$(document).ready(function () {
//Show parent category
$('#categoBtn').hover(function (e) {
e.preventDefault();
e.stopPropagation();
$('#tree_menu').show();
$('.categoryTree').show();
}, function () {
//I dare not write anything
});
//Asynchronously display header categories
function childBuild(children) {
let child_category = `
<li class="category_child">
<a href="/posts/${children.id}/search"><input class="child_btn" type="button" value="${children.name}" name= "${children.id}">
</a>
</li>
`
return child_category;
}
function gcBuild(children) {
let gc_category = `
<li class="category_grandchild">
<a href="/posts/${children.id}/search"><input class="gc_btn" type="button" value="${children.name}" name= "${children.id}">
</a>
</li>
`
return gc_category;
}
//Show parent category
$('#categoBtn').hover(function (e) {
e.preventDefault();
e.stopPropagation();
timeOut = setTimeout(function () {
$('#tree_menu').show();
$('.categoryTree').show();
}, 500)
}, function () {
clearTimeout(timeOut)
});
//Show child categories
$('.parent_btn').hover(function () {
$('.parent_btn').css('color', '');
$('.parent_btn').css('background-color', '');
let categoryParent = $(this).attr('name');
timeParent = setTimeout(function () {
$.ajax({
url: '/posts/top',
type: 'GET',
data: {
parent_id: categoryParent
},
dataType: 'json'
})
.done(function (data) {
$(".categoryTree-grandchild").hide();
$(".category_child").remove();
$(".category_grandchild").remove();
$('.categoryTree-child').show();
data.forEach(function (child) {
let child_html = childBuild(child)
$(".categoryTree-child").append(child_html);
});
$('#tree_menu').css('max-height', '490px');
})
.fail(function () {
alert("Please select a category");
});
}, 400)
}, function () {
clearTimeout(timeParent);
});
//Show grandchild categories
$(document).on({
mouseenter: function () {
$('.child_btn').css('color', '');
$('.child_btn').css('background-color', '');
let categoryChild = $(this).attr('name');
timeChild = setTimeout(function () {
$.ajax({
url: '/posts/top',
type: 'GET',
data: {
children_id: categoryChild
},
dataType: 'json'
})
.done(function (gc_data) {
$(".category_grandchild").remove();
$('.categoryTree-grandchild').show();
gc_data.forEach(function (gc) {
let gc_html = gcBuild(gc)
$(".categoryTree-grandchild").append(gc_html);
let parcol = $('.categoryTree').find(`input[name="${gc.root}"]`);
$(parcol).css('color', 'white');
$(parcol).css('background-color', '#b1e9eb');
});
$('#tree_menu').css('max-height', '490px');
})
.fail(function () {
alert("Please select a category");
});
}, 400)
},
mouseleave: function () {
clearTimeout(timeChild);
}
}, '.child_btn');
//When selecting a grandchild category
$(document).on({
mouseenter: function () {
let categoryGc = $(this).attr('name');
timeGc = setTimeout(function () {
$.ajax({
url: '/posts/top',
type: 'GET',
data: {
gcchildren_id: categoryGc
},
dataType: 'json'
})
.done(function (gc_result) {
let childcol = $('.categoryTree-child').find(`input[name="${gc_result[0].parent}"]`);
$(childcol).css('color', 'white');
$(childcol).css('background-color', '#b1e9eb');
$('#tree_menu').css('max-height', '490px');
})
.fail(function () {
alert("Please select a category");
});
}, 400)
},
mouseleave: function () {
clearTimeout(timeGc);
}
}, '.gc_btn');
//Buttons on the category list page
$('#all_btn').hover(function (e) {
e.preventDefault();
e.stopPropagation();
$(".categoryTree-grandchild").hide();
$(".categoryTree-child").hide();
$(".category_grandchild").remove();
$(".category_child").remove();
}, function () {
//By not writing anything, only the action when it deviates from the parent element is propagated.
});
//Hide categories(0 from category menu.It disappears when the cursor is removed for 8 seconds or more)
$(document).on({
mouseleave: function (e) {
e.stopPropagation();
e.preventDefault();
timeChosed = setTimeout(function () {
$(".categoryTree-grandchild").hide();
$(".categoryTree-child").hide();
$(".categoryTree").hide();
$(this).hide();
$('.parent_btn').css('color', '');
$('.parent_btn').css('background-color', '');
$(".category_child").remove();
$(".category_grandchild").remove();
}, 800);
},
mouseenter: function () {
timeChosed = setTimeout(function () {
$(".categoryTree-grandchild").hide();
$(".categoryTree-child").hide();
$(".categoryTree").hide();
$(this).hide();
$('.parent_btn').css('color', '');
$('.parent_btn').css('background-color', '');
$(".category_child").remove();
$(".category_grandchild").remove();
}, 800);
clearTimeout(timeChosed);
}
}, '#tree_menu');
//Category button processing
$(document).on({
mouseenter: function (e) {
e.stopPropagation();
e.preventDefault();
timeOpened = setTimeout(function () {
$('#tree_menu').show();
$('.categoryTree').show();
}, 500);
},
mouseleave: function (e) {
e.stopPropagation();
e.preventDefault();
clearTimeout(timeOpened);
$(".categoryTree-grandchild").hide();
$(".categoryTree-child").hide();
$(".categoryTree").hide();
$("#tree_menu").hide();
$(".category_child").remove();
$(".category_grandchild").remove();
}
}, '.header__headerInner__nav__listsLeft__item');
});
Set the category selection window on the top screen.
ruby:app/views/posts/top.html.erb
<div class="item-categories">
<h2>
Category list
</h2>
<%= link_to posts_path, class: "category-button", id: 'categoBtn' do %>
Search by category
<% end %>
<div id="tree_menu">
<ul class="categoryTree">
<% @parents.each do |parent| %>
<li class="category_parent">
<%= link_to search_post_path(parent) do %>
<input type="button" value="<%= parent.name %>" name="<%= parent.id %>" class="parent_btn">
<% end %>
</li>
<% end %>
</ul>
<ul class="categoryTree-child">
</ul>
<ul class="categoryTree-grandchild">
</ul>
</div>
</div>
In order to distinguish the categories by id, the search action is defined using member.
config/routes.rb
resources :posts do
~Abbreviation~
member do
get 'search'
end
~Abbreviation~
end
The category you clicked is conditional depending on whether it is a parent category, a child category, or a grandchild category.
app/controllers/posts_controller.rb
def search
@category = Category.find_by(id: params[:id])
if @category.ancestry == nil
category = Category.find_by(id: params[:id]).indirect_ids
if category.empty?
@posts = Post.where(category_id: @category.id).order(created_at: :desc)
else
@posts = []
find_item(category)
end
elsif @category.ancestry.include?("/")
@posts = Post.where(category_id: params[:id]).order(created_at: :desc)
else
category = Category.find_by(id: params[:id]).child_ids
@posts = []
find_item(category)
end
end
def find_item(category)
category.each do |id|
post_array = Post.where(category_id: id).order(created_at: :desc)
if post_array.present?
post_array.each do |post|
if post.present?
@posts.push(post)
end
end
end
end
end
ruby:app/views/posts/search.html.erb
<div class="item-categories">
<h2>
Category list
</h2>
<%= link_to posts_path, class: "category-button", id: 'categoBtn' do %>
Search by category
<% end %>
<div id="tree_menu">
<ul class="categoryTree">
<% @parents.each do |parent| %>
<li class="category_parent">
<%= link_to search_post_path(parent) do %>
<input type="button" value="<%= parent.name %>" name="<%= parent.id %>" class="parent_btn">
<% end %>
</li>
<% end %>
</ul>
<ul class="categoryTree-child">
</ul>
<ul class="categoryTree-grandchild">
</ul>
</div>
</div>
https://qiita.com/k_suke_ja/items/aee192b5174402b6e8ca https://qiita.com/Sobue-Yuki/items/9c1b05a66ce6020ff8c1 https://qiita.com/dr_tensyo/items/88e8ddf0f5ce37040dc8 https://qiita.com/ATORA1992/items/bd824f5097caeee09678 https://qiita.com/misioro_missie/items/175af1f1678e76e59dea https://qiita.com/Rubyist_SOTA/items/49383aa7f60c42141871
Recommended Posts