[Rails] Implementation of multi-layer category function using ancestry "Creation form"

Target

ezgif.com-video-to-gif.gif

Development environment

・ Ruby: 2.5.7 Rails: 5.2.4 ・ Vagrant: 2.2.7 -VirtualBox: 6.1 ・ OS: macOS Catalina

Premise

The following has been implemented.

Slim introductionIntroduction of Bootstrap3Introduction of Font Awesome -Login function implementationImplementation of posting function -Many-to-many category function implementationMulti-layer category function implementation (preparation)Multi-layer category function implementation (seed)

Implementation

1. Create a method in category.rb

category.rb


def self.category_parent_array_create
  category_parent_array = ['---']
  Category.where(ancestry: nil).each do |parent|
    category_parent_array << [parent.name, parent.id]
  end
  return category_parent_array
end

[Explanation]

(1) Prepare an array to create a select box for the parent category and store the initial values.

category_parent_array = ['---']

(2) The value of ancestry is nil, that is, all the parent categories are extracted, and the category name and ID are stored in the array created in (1).

Category.where(ancestry: nil).each do |parent|
  category_parent_array << [parent.name, parent.id]
end

** * Important points ** [parent.name, parent.id] ➡︎ When displaying the parent category in the select box in the view The first argument (parent.name) becomes the value displayed in the browser, The second argument (parent.id) is the value to be sent as a parameter.

③ Returns the array created by as a return value.

return category_parent_array

2. Create a method in book_category.rb

book_category.rb


  def self.maltilevel_category_create(book, parent_id, children_id, grandchildren_id)
    if parent_id.present? && parent_id != '---'
      category = Category.find(parent_id)
      BookCategory.create(book_id: book.id, category_id: category.id)
    end

    if children_id.present? && children_id != '---'
      category = Category.find(children_id)
      BookCategory.create(book_id: book.id, category_id: category.id)
    end

    if grandchildren_id.present? && grandchildren_id != '---'
      category = Category.find(grandchildren_id)
      BookCategory.create(book_id: book.id, category_id: category.id)
    end
  end

[Explanation]

① Receive 4 arguments from the controller.

(book, parent_id, children_id, grandchildren_id)

book: Data of the book to be created parent_id: ID of the parent category children_id: ID of the child category grandchildren_id: ID of grandchild category

(2) If the ID of the parent category is received as an argument and it is not the initial value, the process is executed.

if parent_id.present? && parent_id != '---'

(3) Extract the record corresponding to the parent category ID received as an argument from the Category model and assign it to the variable.

category = Category.find(parent_id)

④ Create a record in BookCategory (intermediate table). ** **

BookCategory.create(book_id: book.id, category_id: category.id)

3. Edit controller

books_controller.rb


def create
  @book = Book.new(book_params)
  @book.user_id = current_user.id
  if @book.save
    BookCategory.maltilevel_category_create(
      @book,
      params[:parent_id],
      params[:children_id],
      params[:grandchildren_id]
    )
    redirect_to books_path
  else
    @books = Book.all
    @category_parent_array = Category.category_parent_array_create
    render 'index'
  end
end

def index
  @book = Book.new
  @books = Book.all
  @category_parent_array = Category.category_parent_array_create
end

def get_category_children
  @category_children = Category.find(params[:parent_id]).children
end

def get_category_grandchildren
  @category_grandchildren = Category.find(params[:children_id]).children
end

[Explanation]

(1) Pass four arguments to the maltilevel_category_create method and execute it.

BookCategory.maltilevel_category_create(
  @book,
  params[:parent_id],
  params[:children_id],
  params[:grandchildren_id]
)

** ◎ Receive the parameters sent by Ajax communication. ** **

params[:parent_id],
params[:children_id],
params[:grandchildren_id]

(2) Assign the array received as the return value of the category_parent_array_create method to the instance variable.

@category_parent_array = Category.category_parent_array_create

③ Extract the child categories associated with the selected parent category.

def get_category_children
  @category_children = Category.find(params[:parent_id]).children
end

④ Extract the grandchild category associated with the selected child category.

def get_category_grandchildren
  @category_grandchildren = Category.find(params[:children_id]).children
end

4. Create / edit json.jbuilder file

Terminal


$ touch app/views/books/get_category_children.json.jbuilder

ruby:books/get_category_children.json.jbuilder


json.array! @category_children do |children|
  json.id children.id
  json.name children.name
end

Terminal


$ touch app/views/books/get_category_grandchildren.json.jbuilder

ruby:books/get_category_grandchildren.json.jbuilder


json.array! @category_grandchildren do |grandchildren|
  json.id grandchildren.id
  json.name grandchildren.name
end

[Explanation]

(1) Repeat the records extracted by the get_category_children action to create an array.

json.array! @category_children do |children|

(2) Store each ID and name in the array created by .

json.id children.id
json.name children.name

** ◎ Return value when parent category (business) is selected **

[
  {
    "id": 2, 
    "name": "Finance"
  },
  {
    "id": 6, 
    "name": "Economy"
  },
  {
    "id": 9, 
    "name": "management"
  },
  {
    "id": 13, 
    "name": "marketing"
  },
]

** ◎ When child category (finance) is selected **

[
  {
    "id": 3, 
    "name": "stock"
  },
  {
    "id": 4, 
    "name": "exchange"
  },
  {
    "id": 5, 
    "name": "tax"
  },
]

5. Add routing

routes.rb


#Postscript
get 'get_category/children', to: 'books#get_category_children', defaults: { format: 'json' }
get 'get_category/grandchildren', to: 'books#get_category_grandchildren', defaults: { format: 'json' }

6. Edit the view

slim:books/index.html.slim


.category-form
  = label_tag 'Genre'
  = select_tag 'parent_id', options_for_select(@category_parent_array), id: 'parent-category', class: 'form-control'
  i.fas.fa-chevron-down
br

[Explanation]

(1) Display the data in the instance variable defined by the controller in the select box, and set the property (parameter name) to parent_id.

= select_tag 'parent_id', options_for_select(@category_parent_array), id: 'parent-category', class: 'form-control'

7. Create / edit JavaScript file

Terminal


$ touch app/assets/javascripts/category_form.js

category_form.js


$(function() {
  function appendOption(category) {
    let html = `<option value='${category.id}' data-category='${category.id}'>${category.name}</option>`;
    return html;
  }

  function appendChidrenBox(insertHTML) {
    let childrenSelectHtml = '';
    childrenSelectHtml = `
      <div id='children-wrapper'>
        <select id='children-category' class='form-control' name='[children_id]'>
          <option value='---' data-category='---'>---</option>
          ${insertHTML}
        </select>
        <i class='fas fa-chevron-down'></i>
      </div>
    `;
    $('.category-form').append(childrenSelectHtml);
  }

  function appendGrandchidrenBox(insertHTML) {
    let grandchildrenSelectHtml = '';
    grandchildrenSelectHtml = `
      <div id='grandchildren-wrapper'>
        <select id='grandchildren-category' class='form-control' name='[grandchildren_id]'>
          <option value='---' data-category='---'>---</option>
          ${insertHTML}
        </select>
        <i class='fas fa-chevron-down'></i>
      </div>
    `;
    $('.category-form').append(grandchildrenSelectHtml);
  }

  $('#parent-category').on('change', function() {
    let parentId = document.getElementById('parent-category').value;
    if (parentId != '---') {
      $.ajax({
        url: '/get_category/children',
        type: 'GET',
        data: {
          parent_id: parentId,
        },
        dataType: 'json',
      })
        .done(function(children) {
          $('#children-wrapper').remove();
          $('#grandchildren-wrapper').remove();
          let insertHTML = '';
          children.forEach(function(children) {
            insertHTML += appendOption(children);
          });
          appendChidrenBox(insertHTML);
        })
        .fail(function() {
          alert('Failed to get the genre');
        });
    } else {
      $('#children-wrapper').remove();
      $('#grandchildren-wrapper').remove();
    }
  });

  $('.category-form').on('change', '#children-category', function() {
    let childrenId = $('#children-category option:selected').data('category');
    if (childrenId != '---') {
      $.ajax({
        url: '/get_category/grandchildren',
        type: 'GET',
        data: {
          children_id: childrenId,
        },
        dataType: 'json',
      })
        .done(function(grandchildren) {
          if (grandchildren.length != 0) {
            $('#grandchildren-wrapper').remove();
            let insertHTML = '';
            grandchildren.forEach(function(grandchildren) {
              insertHTML += appendOption(grandchildren);
            });
            appendGrandchidrenBox(insertHTML);
          }
        })
        .fail(function() {
          alert('Failed to get the genre');
        });
    } else {
      $('#grandchildren-wrapper').remove();
    }
  });
});

[Explanation]

(1) Set the options of the select box.

$(function() {
  function appendOption(category) {
    let html = `<option value='${category.id}' data-category='${category.id}'>${category.name}</option>`;
    return html;
  }

** ◎ Set the value to be sent as a parameter. ** **

<option value='${category.id}' data-category='${category.id}'>

** * Important points ** It corresponds to the parameter received by of` 3. Edit controller``.

** ◎ Set the value to be displayed in the select box. ** **

${category.name}

(2) Create a select box for the child genre.

function appendChidrenBox(insertHTML) {
  let childrenSelectHtml = '';
  childrenSelectHtml = `
    <div id='children-wrapper'>
      <select id='children-category' class='form-control' name='[children_id]'>
        <option value='---' data-category='---'>---</option>
        ${insertHTML}
      </select>
      <i class='fas fa-chevron-down'></i>
    </div>
  `;
  $('.category-form').append(childrenSelectHtml);
}

** ◎ Set the parameter name of the parameter created in. ** **

name='[children_id]

** ◎ Create a child category select box based on the options set in . ** **

${insertHTML}

** ◎ Display the select box of the child category. ** **

$('.category-form').append(childrenSelectHtml);

③ Create a select box for the grandchild genre. (Since it is almost the same as , the explanation is omitted)

function appendGrandchidrenBox(insertHTML) {
  let grandchildrenSelectHtml = '';
  grandchildrenSelectHtml = `
    <div id='grandchildren-wrapper'>
      <select id='grandchildren-category' class='form-control' name='[grandchildren_id]'>
        <option value='---' data-category='---'>---</option>
        ${insertHTML}
      </select>
      <i class='fas fa-chevron-down'></i>
    </div>
  `;
  $('.category-form').append(grandchildrenSelectHtml);
}

④ Create an event that fires when the parent category is selected.

$('#parent-category').on('change', function() {
  let parentId = document.getElementById('parent-category').value;
  if (parentId != '---') {
    $.ajax({
      url: '/get_category/children',
      type: 'GET',
      data: {
        parent_id: parentId,
      },
      dataType: 'json',
    })
      .done(function(children) {
        $('#children-wrapper').remove();
        $('#grandchildren-wrapper').remove();
        let insertHTML = '';
        children.forEach(function(children) {
          insertHTML += appendOption(children);
        });
        appendChidrenBox(insertHTML);
      })
      .fail(function() {
        alert('Failed to get the genre');
      });
  } else {
    $('#children-wrapper').remove();
    $('#grandchildren-wrapper').remove();
  }
});

** ◎ Fires when the parent category is selected. ** **

$('#parent-category').on('change', function() {});

** ◎ Get the ID of the selected parent category and assign it to a variable. ** **

let parentId = document.getElementById('parent-category').value;

** ◎ If the parent category is not the default value Set the ID of the parent category obtained earlier in the parameter (parent_id), Execute the get_category_children action asynchronously. ** **

if (parentId != '---') {
  $.ajax({
    url: '/get_category/children',
    type: 'GET',
    data: {
      parent_id: parentId,
    },
    dataType: 'json',
  })

** ◎ If Ajax communication is successful, create a select box for the child category. Also, if the parent category is changed while the select boxes below the child category are already displayed, Delete the select boxes under the child category and recreate the select boxes for the child categories. ** **

.done(function(children) {
  $('#children-wrapper').remove();
  $('#grandchildren-wrapper').remove();
  let insertHTML = '';
  children.forEach(function(children) {
    insertHTML += appendOption(children);
  });
  appendChidrenBox(insertHTML);
})

** ◎ If asynchronous communication fails, an alert is displayed. ** **

.fail(function() {
  alert('Failed to get the genre');
});

** ◎ If the parent category is the initial value, delete the child category and below. ** **

} else {
  $('#children-wrapper').remove();
  $('#grandchildren-wrapper').remove();
}

④ Create an event that fires when a child category is selected. (Almost the same as )

$('.category-form').on('change', '#children-category', function() {
  let childrenId = $('#children-category option:selected').data('category');
  if (childrenId != '---') {
    $.ajax({
      url: '/get_category/grandchildren',
      type: 'GET',
      data: {
        children_id: childrenId,
      },
      dataType: 'json',
    })
      .done(function(grandchildren) {
        if (grandchildren.length != 0) {
          $('#grandchildren-wrapper').remove();
          let insertHTML = '';
          grandchildren.forEach(function(grandchildren) {
            insertHTML += appendOption(grandchildren);
          });
          appendGrandchidrenBox(insertHTML);
        }
      })
      .fail(function() {
        alert('Failed to get the genre');
      });
  } else {
    $('#grandchildren-wrapper').remove();
  }
});

** ◎ Some categories do not have grandchildren categories, so conditions are given. (Add conditions to child categories as needed) **

if (grandchildren.length != 0)

Caution

If you do not disable turbolinks, the select box will not work asynchronously, so be sure to disable it.

How to disable turbolinks

Sequel

Multi-layer category function implementation (editing form)

Recommended Posts

[Rails] Implementation of multi-layer category function using ancestry "Creation form"
[Rails] Implementation of multi-layer category function using ancestry "Editing form"
[Rails] Implementation of multi-layer category function using ancestry "Preparation"
[Rails] Implementation of multi-layer category function using ancestry "seed"
[Rails] Implementation of category function
[Rails] gem ancestry category function implementation
[Rails] Implementation of multi-layer category function using ancestry "I tried to make a window with Bootstrap 3"
[Rails] Implementation of search function using gem's ransack
[Rails 6] Implementation of inquiry function using Action Mailer
[Rails] Implementation of image enlargement function using lightbox2
Implementation of category selection form using JS in flea market application creation
[Rails 6] Implementation of search function
Implementation of category pull-down function
[Rails] Implementation of tutorial function
[Rails] Implementation of like function
[Rails] Implementation of CSV import function
[Rails] Asynchronous implementation of like function
[Rails] Implementation of image preview function
[Rails] About implementation of like function
[Rails] Implementation of user withdrawal function
[Rails] Implementation of CSV export function
[Rails] Implementation of tagging function using intermediate table (without Gem)
[Rails] Implementation of many-to-many category functions
[Rails] Implementation of new registration function in wizard format using devise
[Rails] Implementation of coupon function (with automatic deletion function using batch processing)
[Rails] Implementation of tag function using acts-as-taggable-on and tag input completion function using tag-it
Implementation of user authentication function using devise (2)
Implementation of user authentication function using devise (1)
Rails [For beginners] Implementation of comment function
[Rails 6] Implementation of SNS (Twitter) sharing function
Implementation of user authentication function using devise (3)
[Vue.js] Implementation of menu function Implementation version rails6
[Ruby on rails] Implementation of like function
[Vue.js] Implementation of menu function Vue.js introduction rails6
[Rails] Category function
[Rails] I will explain the implementation procedure of the follow function using form_with.
Implement the product category function using ancestry ① (Preparation)
[Rails] Implementation of retweet function in SNS application
[Rails] Implementation of batch processing using whenever (gem)
Implementation of search function Learning memo (portfolio creation)
[Rails] Implementation of PV number ranking using impressionist
[Rails] Implementation of image slide show using Bootstrap 3
[Note] Summary of rails login function using devise ①
[Rails] Set validation for the search function using Rakuten API (from the implementation of Rakuten API)
Implementation of search function
Rails search function implementation
Implementation of pagination function
Ruby on Rails <2021> Implementation of simple login function (form_with)
[Rails] Implementation of drag and drop function (with effect)
[Rails] Test of star evaluation function using Raty [Rspec]
Implementation of Ruby on Rails login function (devise edition)
[Ruby on Rails] Implementation of tagging function/tag filtering function
[Rails] Implementation of SNS authentication (Twitter, Facebook, Google) function
Rails implementation of ajax removal
Rails fuzzy search function implementation
[Rails] Implementation of automatic address input using jpostal and jp_prefecture
Implementation of sequential search function
Implementation of like function (Ajax)
Implementation of image preview function
Rails sorting function implementation (displayed in order of number of like)
Implement category function using ancestory