8
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Rails】ancestryを使って動的なセレクトボックスを作る方法

Last updated at Posted at 2019-11-03

ancestryというGemを使うと階層カテゴリを簡単に作ることができます。今回はancestryを使いつつ、動的なセレクトボックスを作る方法をご紹介します。

完成イメージは以下の通りです。
dynamic_select.gif

今回使用したコードはGithubからも見れるようにしておきました。
ancestry-sandbox

前提

  • ruby-version: 2.6.3
  • rails-version: 6.0.0

準備

ancestryをinstall

まずはGemのinstallから行います。テストデータを作成するためのseed-fuのinstallもここで行います。

# Gemfile
gem 'ancestry'
gem 'seed-fu'
$ bundle install

migrationを実行

例えばcategoryモデルで階層化を行いたい場合は、以下を実行します。

$ rails g migration add_ancestry_to_category ancestry:string:index
$ rake db:migrate
app/models/cateogry.rb
class Category < ActiveRecord::Base
   has_ancestry
end

テストデータを挿入

動作確認をするためのテストデータを作ります。(ここは冗長なコードになってしまっている気がするので、改善方法ご教授いただきたいです🙇‍♂️)

db/fixtures/01_category.rb
parents = %w[ 家電 ファッション 本 ]
electronics_child = %w[ 洗濯機 炊飯器 ]
fashion_child = %w[ アウター トップス ]
book_child = %w[ 自己啓発 技術書 ]

parents.each do |parent|
  Category.seed do |c|
    c.name = parent
  end
end
electronics_child.each do |child|
  Category.find_by!(name: '家電').children.create(name: child)
end
fashion_child.each do |child|
  Category.find_by!(name: 'ファッション').children.create(name: child)
end
book_child.each do |child|
  Category.find_by!(name: '本').children.create(name: child)
end

上記で使っているchildrenがancestryをinstallしたことで使えるようになったメソッドで、上記の例では家電>洗濯機, ファッション>アウターなどのカテゴリが生成されています。

jQueryをinstall

Ajaxで動的セレクトボックスを作るときにjQueryを使用するので、セットアップを行います。

$ yarn add jquery
config/webpack/environment.js
const { environment } = require('@rails/webpacker')
const webpack = require('webpack');

environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)
module.exports = environment

動的セレクトボックスを作る

ここからが本題で動的セレクトボックスを作っていきます。親のカテゴリを選択すると、それに紐づく子供のカテゴリを表示するということです。

セレクトボックスを表示

親カテゴリとデフォルトで表示させる子カテゴリを変数代入し、最初のセレクトボックスで表示させます。

app/controllers/products_controller.rb
before_action :set_categories, only: %w[edit new]
private
def set_categories
 @parent_categories = Category.roots
 @default_child_categories = @parent_categories.first.children
end
app/views/products/_form.html.erb
<div class="actions">
  <select id="parent_category">
    <% @parent_categories.each do |c| %>
      <option value="<%= c.id %>"><%= c.name %></option>
    <% end %>
  </select>
  <select id="product_category_ids" name="product[category_ids]">
    <% @default_child_categories.each do |c| %>
      <option><%= c.name %></option>
    <% end %>
  </select>
</div>

ここまでで一旦セレクトボックスを2つ並べるところまでは完了しました。続いて、親カテゴリが変更されたときにAjaxを利用して子カテゴリが動的に切り替わる実装をしていきます。

ルーティングの設定

まずはルーティングにAjax用のパスを指定します。

routes.rb
get :dynamic_select_category, to: 'products#dynamic_select_category'

Ajax処理を記述

次にAjax処理を追記します。下記で、dynamic_select_category_pathにセレクトボックスで選択されたvalueをデータとして持たせてAjax送信を行っています。

app/views/products/_form.html.erb
<script type="text/javascript">
$(function() {
  $('#parent_category').change(function() {
    $.ajax({
      url: "<%= dynamic_select_category_path %>",
      type: 'GET',
      data: { category_id: $('#parent_category').has('option:selected').val() }
    });
  });
});
</script>

子カテゴリを動的に表示

app/controllers/products_controller.rb
def dynamic_select_category
 @category = Category.find(params[:category_id])
end
app/views/produts/dynamic_select_category.js.erb
$('#product_category_ids').html('<%= j(options_for_select(@category.children.pluck(:name, :id))) %>');

コントローラ側では受け取ったcategory_idからカテゴリを特定し、js.erbの方では#product_category_idsの書き換えを行なっています。

ここでも上述したchildrenメソッドで子カテゴリを表示するようにしています。

参考

8
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?