My name is Kusano. This post will be an output after reviewing what I learned at the programming school. The text is poor because it is a memo for myself, but I hope it will help beginners as much as possible. As the title suggests, the content is about the implementation of the category function of the flea market apps by team development conducted at the school. I think there are many immature points. Please point out any deficiencies. I will improve it from time to time.
When the parent category is selected, the event will fire and the child, grandchild and select boxes will be displayed.
** Category registration screen when listing products **
** Category information call screen for detailed product information **
** 1. Creating a model in DB / Association definition **
--Installing gem ancestry --Creating a category model --category Creation of migration file --Association definition with item model --Adding records to DB by reading CSV file
** 2. Category registration function **
--Routing settings for JSON of child and grandchild categories --Items controller instance variable definition of parent category --items Controller method definition for JSON of child and grandchild categories --Conversion description to JSON data to json.jbuilder file --Set behavior when selecting parent, child, grandchild by JavaScript --Call description to html.haml file
** 3. Calling category information **
--Define parent, child, and grandchild instance variables in the show method of the items controller --items Described in a haml file using the instance variable defined in the controller and called to the view
Use the Ruby on Rails “ancestry” gem to add category functionality.
Gemfile
gem 'ancestry'
Create a category model with the rails g model category command in the terminal. Describe has_ancestory.
app/models/category.rb
class Category < ApplicationRecord
has_ancestry
has_many :items
end
Describe as follows in the categories migration file and execute the rails db: migrate command in the terminal
db/category.rb
class CreateCategories < ActiveRecord::Migration[5.2]
def change
create_table :categories do |t|
t.string :name, null: false
t.string :ancestry
t.timestamps
end
add_index :categories, :ancestry
end
end
Describe all categories on 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). I wrote all the categories in a CSV file, but there were 1368 lines, so I had a hard time inputting. (I remember the clerical work I was doing in my previous job.) By the way, there is a category without grandchildren, so be careful not to shift the ancestry value. You can save the data by following the procedure of File → Download → Comma-separated values (.csv current sheet). Put the downloaded CSV file directly into the db file.
Write the following in the seeds.rb file and execute the rails db: seed command in the terminal to read the CSV file and automatically generate a DB record. Although it is an explanation about the description content, specify the file you want to read after foreach. For the description below it, 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)
db/seeds.rb
require "csv"
CSV.foreach('db/category.csv') do |row|
Category.create(:id => row[0], :name => row[1], :ancestry => row[2])
end
Routing settings for JSON in child and grandchild categories The top two in the collection do are the routes for JSON created this time. By writing defaults: {fomat:'json'}, it will be dedicated to JSON.
config/routes.rb
resources :items do
resources :comments, only: [:create, :destroy]
resources :favorites, only: [:create, :destroy]
collection do
get 'get_category_children', defaults: { fomat: 'json'}
get 'get_category_grandchildren', defaults: { fomat: 'json'}
get 'search'
get 'post_done'
get 'delete_done'
get 'detail_search'
get 'update_done'
end
end
Instance variable definition of parent category to items controller Define the following description in the new method. (As the implementation progresses, it will be used for other actions as well, so it will be redefined as private later and refactored with before_action.)
app/controllers/items_controller.rb
@category_parent_array = Category.where(ancestry: nil)
Items Controller method definition for JSON of child and grandchild categories The description in parentheses of params [] describes: parent_id and: child_id sent by ajax in the JavaScript file explained later.
app/controllers/items_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 describe the conversion to JSON data
rb:app/views/items/get_category_children.json.jbuilder
json.array! @category_children do |child|
json.id child.id
json.name child.name
end
rb:app/views/items/get_category_grandchildren.json.jbuilder
json.array! @category_grandchildren do |grandchild|
json.id grandchild.id
json.name grandchild.name
end
Set the behavior when selecting parent, child, grandchild by JavaScript
app/assets/javascripts/category.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="item[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="item[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: '/items/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);
})
.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 != "" && childId != 46 && childId != 74 && childId != 134 && childId != 142 && childId != 147 && childId != 150 && childId != 158){
$.ajax({
url: '/items/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);
})
.fail(function(){
alert('Failed to get the category');
})
}else{
$('#grandchildren_wrapper').remove();
}
})
});
I will explain the above description. The event is set to fire when a category is selected in the parent select box that is displayed first in the following description in the middle row. The description on the second line gets the id of the selected category and defines the variables.
①
$('#item_category_id').on('change',function(){
var parentId = document.getElementById('item_category_id').value;
Next, put the id of the category acquired in ① by ajax into parent_id, pass the parent_id to the controller through the route for JSON routed earlier, and acquire the record of the child category.
②
$.ajax({
url: '/items/get_category_children/',
type: 'GET',
data: { parent_id: parentId },
dataType: 'json'
})
If the ajax communication is successful, the record of the child category acquired in ② is expanded by the forEach method on the 5th line. The remove method on the 2nd and 3rd lines is written to remove the child and grandchild select boxes when another category is selected again in the parent select box.
③
.done(function(children){
$('#children_wrapper').remove();
$('#grandchildren_wrapper').remove();
var insertHTML = '';
children.forEach(function(child){
insertHTML += appendOption(child);
});
appendChildrenBox(insertHTML);
})
The appendOption described in the upper row passes the child record obtained earlier with the argument of appendOption (child) on the 6th line of ③, and embeds id and name (category name) in the option tag, respectively.
④
function appendOption(category){
var html = `<option value="${category.id}">${category.name}</option>`;
return html;
}
For appendChildrenBox, put the contents of ④ in the insertHTML described in the 6th line of ③, and pass the option tag of ④ in the argument of appendChildrenBox (insertHTML) described in the 8th line of ③. Embed the option tag with the description of \ $ {insertHTML} Finally, the description of $ ('.append__category'). Append (childSelectHtml); causes the browser to display the child select box asynchronously. The flow of the grandchild select box is the same.
⑤
function appendChildrenBox(insertHTML){
var childSelectHtml = "";
childSelectHtml = `<div class="category__child" id="children_wrapper">
<select id="child__category" name="item[category_id]" class="serect_field">
<option value="">---</option>
${insertHTML}
</select>
</div>`;
$('.append__category').append(childSelectHtml);
}
If ajax communication fails, an alert will be displayed.
⑥
.fail(function(){
alert('Failed to get the category');
})
For the if statement described above ajax, the initial value is set to nil using the include_blank option of collection_select in the haml file, and when this selects the option with id from nil, ajax communication starts. On the contrary, if you return to the initial value of "Please select", it becomes else and the grandchild select box is deleted.
⑦
if (parentId != ""){
#The description in the middle is omitted
}else{
$('#grandchildren_wrapper').remove();
Regarding the difference between the description of the child and the description of the grandchild, in the description of the grandchild, if the one added by javaScript is targeted for event firing, it is necessary to describe as follows. \ $ (Investigation scope) .on (event name, place where the event occurs, function () { In addition, conditional branching is set so that ajax communication starts when the category is not a grandchildless category in the if statement.
⑧
$('.append__category').on('change','#child__category',function(){
var childId = document.getElementById('child__category').value;
if(childId != "" && childId != 46 && childId != 74 && childId != 134 && childId != 142 && childId != 147 && childId != 150 && childId != 158){
There is one caveat when displaying the select box asynchronously on the browser. It is about the part where "item [category_id]" is set in the select tag for both children and grandchildren with the name option. The name option specifies which category_id of parent, child, or grandchild should be stored in the items table.
** Add name option only to children and enter grandchild category ** → In DB, child id is saved ** Add name option only to grandchildren, enter category without grandchildren ** → In the DB, the parent category_id is saved ** Add name option for both children and grandchildren, and enter up to grandchild category ** → In the DB, the select tag with the name option displayed asynchronously at the end has priority and is saved.
The reason why it is necessary to save the grandchild category_id is that the parent and child records can be called based on the grandchild category_id when calling the category information.
⑨
function appendChildrenBox(insertHTML){
var childSelectHtml = "";
childSelectHtml = `<div class="category__child" id="children_wrapper">
<select id="child__category" name="item[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="item[category_id]" class="serect_field">
<option value="">---</option>
${insertHTML}
</select>
</div>`;
$('.append__category').append(grandchildSelectHtml);
}
Display the parent select box in the haml file.
rb:app/views/items/_form.html.haml
.append__category
.category
.form__label
.lavel__name
Category
.lavel__Required
[Mandatory]
=f.collection_select :category_id, @category_parent_array, :id, :name,{ include_blank: "Please select"},class:"serect_field"
Define parent, child, and grandchild instance variables in the show method of the items controller
app/controllers/items_controller.rb
@category_id = @item.category_id
@category_parent = Category.find(@category_id).parent.parent
@category_child = Category.find(@category_id).parent
@category_grandchild = Category.find(@category_id)
Call the category registered using the instance variable defined earlier in the items controller to the view with a haml file. The if statement conditionally branches the display with no grandchildren or with grandchildren. (Since the path is not specified for the link this time, it is set as #.)
rb:app/views/items/_main_show.html.haml
%table
%tr
%th seller
%td= @user.nickname
%tr
%th category
- if [46, 74, 134, 142, 147, 150, 158].include?(@category_id)
%td
= link_to "#{@category_child.name}","#"
%br= link_to "#{@category_grandchild.name}","#"
-else
%td
= link_to "#{@category_parent.name}","#"
%br= link_to "#{@category_child.name}","#"
= link_to "#{@category_grandchild.name}","#"
%tr
%th brand
%td= @item.brand_name
%tr
%th product size
%td
%tr
%th Product status
%td= @item.item_status
%tr
%th Shipping charges
%td= @item.delivery_fee
%tr
%th Shipment area
%td= link_to "#{@item.shipping_origin}","#"
%tr
%th Estimated shipping date
%td= @item.days_until_shipping
Realize dynamic category select box using multi-layered data by ancestry ~ Ajax ~ [Translation] Gem Ancestry Official Document
Recommended Posts