[nuxt] [rails] filterメソッドで多階層のセレクトボックスを非同期で実現する

**filter**メソッドで多階層のセレクトボックスを非同期で実装することがあったので復習がてら記事にします。

## 前提条件
フロント: nuxt
バック: rails
api通信はできている


データ構成は下記記事を参考にする
[Rails * Vue.js * Ajaxで多階層カテゴリを作った話](https://qiita.com/tanutanu/items/7cb3298ec6398106c5aa)

**完成品**
![f6f3716e51204748799c283176cc6b6b.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/524516/dddd26b1-a578-2bf0-ab75-16c9f65192e1.gif)

## rails側の実装

- seed_fuでデータ作成
- ルーティング生成
- コントローラ作成
- jbuilderでデータをjsonで返す

```001_category.rb
Category.seed(:id,
  { id: 100, name: '親1'},
  { id: 110, name: '親1の子1'},
  { id: 111, name: '親1の子1の孫1'}, { id: 112, name: '親1の子1の孫2'}, { id: 113, name: '親1の子1の孫3'},
  { id: 120, name: '親1の子2'},
  { id: 121, name: '親1の子2の孫1'}, { id: 122, name: '親1の子2の孫2'}, { id: 123, name: '親1の子2の孫3'},
  { id: 130, name: '親1の子3'},
  { id: 131, name: '親1の子3の孫1'}, { id: 132, name: '親1の子3の孫2'}, { id: 133, name: '親1の子3の孫3'},

  { id: 200, name: '親2'},
  { id: 210, name: '親2の子1'},
  { id: 211, name: '親2の子1の孫1'}, { id: 212, name: '親2の子1の孫2'}, { id: 213, name: '親2の子1の孫3'},
  { id: 220, name: '親2の子2'},
  { id: 221, name: '親2の子2の孫1'}, { id: 222, name: '親2の子2の孫2'}, { id: 223, name: '親2の子2の孫3'},
  { id: 230, name: '親2の子3'},
  { id: 231, name: '親2の子3の孫1'}, { id: 232, name: '親2の子3の孫2'}, { id: 233, name: '親2の子3の孫3'}
)
```

```routes.rb
Rails.application.routes.draw do
  resources :categories, only: %i[index]
end
```

```categories_controller.rb
class CategoriesController < BaseController
  def index
    @categories = Category.all
  end
end
```

```index.jbuilder
json.categories @categories do |category|
  json.extract! category, :id, :name
end
```

## nuxt側の実装
nuxtの方は[vuestify](https://vuetifyjs.com/ja/)というマテリアルデザインコンポーネントフレームワークを使用

```category.vue
<template>
  <div>
    <v-select
      label="親カテゴリー"
      :items="parentCategory"
      item-text="name"
      item-value="id"
      v-model="parentSelected"
    />

    <v-select
      label="子カテゴリー"
      :items="childCategory"
      item-text="name"
      item-value="id"
      v-model="childrenSelected"
    />

    <v-select
      label="孫カテゴリー"
      :items="grandChildCategory"
      item-text="name"
      item-value="id"
      v-model="grandChildrenSelected"
    />
  </div>
</template>

<script>
export default {
  mounted() {
    this.fetchCategory()
  },

  data: () => {
    return {
      categories: [],
      parentSelected: '',
      childrenSelected: '',
      grandChildrenSelected: '',
    }
  },

  computed: {
    parentCategory() {
      return this.categories.filter((category) => category.id % 100 === 0)
    },

    childCategory() {
      if (this.parentSelected === '') {
        return []
      }

      return this.categories.filter((category) => {
                // 100の位が同じcategory
        return (Math.floor(category.id  / 100) === Math.floor(this.parentSelected / 100) &&
                // 1の位が0のcategory
                category.id % 10 === 0 &&
                // 親カテゴリーと一致しないcategory
                category.id !== this.parentSelected)
        })
    },

    grandChildCategory() {
      if (this.childrenSelected === '') {
        return []
      }

      return this.categories.filter((category) => {
                // 10の位が一致するcategory
        return (Math.floor(category.id  / 10) === Math.floor(this.childrenSelected / 10) &&
                // 1の位が0ではないのcategory
                category.id % 10 !== 0)
      })
    }
  },

  methods: {
    fetchCategory() {
      const url = 'api/categories'
      this.$axios
        .$get(url)
        .then((response) => {
          this.categories = response.categories
        })
        .catch((errors) => {
          console.log(errors)
        })
    }
  }
}
</script>
```
- mountedでfetchCategoryを呼び出し
- セレクトボックスのオプションをcomputedでそれぞれ定義
- それぞれのセレクトボックスにv-modelを設定
- 親のv-modelが変更 -> 子カテゴリーのオプションが変更
- 子のv-modelが変更 -> 孫カテゴリーのオプションが変更

filterはこのように使用します

```
[検証したいデータ].filter((1データ) => 条件式)
```

親カテゴリーの例でみてみます

```.vue
// 親カテゴリー
<v-select
  label="親カテゴリー"
  :items="parentCategory"
  item-text="name"
  item-value="id"
  v-model="parentSelected"
/>

// オプション定義
parentCategory() {
  return this.categories.filter((category) => category.id % 100 === 0)
  // 100 % 100 = 0, 110 % 100 = 10, 111 % 100 = 11, 200 % 100 = 0
},
```
`:items="オプションのデータ"`なのでcomputed内にある`parentCategory`を参照します
this.categoriesにはrailsで定義した`Category.all`のデータが入っています。
categoryには`{ id: 100, name: '親1' }`のように一つのデータが入っています。
そのデータのidを100で割った時のあまりが0と一致するデータを返すという処理になっているので
この場合idが100, 200, 300...のレコードが返ります。

以上です。

filterの条件式をもう少しうまく記述できそうな気がしますが。。。
他にいい方法があればコメントで教えてください！