🐸

Vuex ORM の基本的な使い方 - Setup, Relationships

2023/01/08に公開約11,100字

Vuex ORM は Vue の状態管理ライブラリである Vuex のプラグインです。
これを使うことでオブジェクトにアクセスするようにVuexのステートにアクセスすることが可能になります。

チュートリアル動画や公式ドキュメントがあるので詳細はそちらをご覧ください。
今回は Vuex ORM を使った事がない方向けにチュートリアルの最初の部分にそって解説をしていきます。

Setup

まずはプロジェクト作成します。作成時はVuexにチェックを入れます。

% vue create vuex-orm-tutorial             

Vue CLI v5.0.8
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Vuex, Linter
? Choose a version of Vue.js that you want to start the project with 2.x
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

Vuex ORM インストールします。

cd vuex-orm-tutorial
yarn add @vuex-orm/core

src/classes/Item.js を作成してモデルを定義します。

import { Model } from "@vuex-orm/core";

export default class Item extends Model {
  static entity = "items";

  static fields() {
    return {
      id: this.attr(null),
      body: this.attr(""),
    };
  }
}

src/store.js を作成して VuexORM の設定をします。公式ドキュメントにあるやつです。

import Vue from "vue";
import Vuex from "vuex";
import VuexORM from "@vuex-orm/core";
import Item from "./classes/Item";

Vue.use(Vuex);

const database = new VuexORM.Database();
database.register(Item);

export default new Vuex.Store({
  plugins: [VuexORM.install(database)],
});

src/App.vue を編集してコンポーネント側で作成したモデルを使ってみます。

<template>
  <div id="app">
    <input v-model="form.body" />
    <button @click="addItem">Add Item</button>
    <li v-for="item in items" :key="item.id">{{ item.body }}</li>
  </div>
</template>

<script>
import Item from "./classes/Item";

export default {
  name: "App",
  data() {
    return {
      form: {
        body: "",
      },
    };
  },
  computed: {
    items() {
      return Item.all();
    },
  },
  methods: {
    addItem() {
      Item.insert({ data: this.form });
    },
  },
};
</script>

insert() を使ってモデルに対して新しいレコードを追加(引数のkeyにはdataを指定)できます。
all() を使ってモデル内の全レコードを取得できます。

ローカルサーバー起動してブラウザでアクセスします。

yarn run serve

テキストボックスに入力してボタンを押すとリストに追加されるはずです。
Chrome拡張機能の Vue.js devtool を使うと Vuex のストアの値を見ることができるので便利です。

One To One Relationships

UserモデルとProfileモデルが1対1で紐づくデータの場合。

hasOne(), belongsTo() を使って対象のモデルとフィールドを指定します。

src/classes/User.js

import { Model } from "@vuex-orm/core";
import Profile from "./Profile";

export default class User extends Model {
  static entity = "users";

  static fields() {
    return {
      id: this.attr(null),
      name: this.attr(""),
      email: this.attr(""),
      profile: this.hasOne(Profile, "user_id"),
    };
  }
}

src/classes/Profile.js

import { Model } from "@vuex-orm/core";
import User from "./User";

export default class Profile extends Model {
  static entity = "profiles";

  static fields() {
    return {
      id: this.attr(null),
      bio: this.attr(""),
      life_goal: this.attr(""),
      user_id: this.attr(null),
      user: this.belongsTo(User, "user_id"),
    };
  }
}

それぞれもモデルを登録する必要があるので store.js に以下を追記します。

database.register(User);
database.register(Profile);

コンポーネント側のコード。サンプルとして User モデルに2つのデータを insert() で追加し query(), with(), get() を使ってデータを取得できます。

<div v-for="profile in profiles" :key="profile.id">
  <h1>{{ profile.user.name }}</h1>
  <p>{{ profile.bio }}</p>
</div>
beforeMount() {
  User.insert({
    data: [
      {
        id: 28,
        name: "Luke",
        email: "luke@gmail.com",
        profile: {
          id: 55,
          bio: "Luke is a web developer",
          life_goal: "create products!",
        },
      },
      {
        id: 27,
        name: "Shannen",
        email: "shannen@gmail.com",
        profile: {
          id: 65,
          bio: "Shannen is awesome",
          life_goal: "change the world!",
        },
      },
    ],
  });
},
computed: {
  profiles() {
    return Profile.query().with("user").get();
  },
}

ブラウザでの表示確認するとProfileモデルと紐づくUserモデルの内容が表示できています。

Vue.js devtool では Vuex のステートを見ることができますが紐づく値は残念ながら null で表示されてしまいます。今回の例だと profiles に user は紐づいていますが null になっています。

One To Many Relationships

User:List = 1 : N で紐づいている場合。

それぞれのモデルは以下のように hasMany(), belogsTo() を使って定義します。

Userモデル

export default class User extends Model {
  static entity = "users";

  static fields() {
    return {
      id: this.attr(null),
      name: this.attr(""),
      email: this.attr(""),
      profile: this.hasOne(Profile, "user_id"),
      lists: this.hasMany(List, "user_id"),
    };
  }
}

Listモデル

export default class List extends Model {
  static entity = "lists";

  static fields() {
    return {
      id: this.attr(null),
      title: this.attr(""),
      user_id: this.attr(null),
      user: this.belongsTo(User, "user_id"),
      items: this.hasMany(Item, "list_id"),
    };
  }
}

Itemモデル

export default class Item extends Model {
  static entity = "items";

  static fields() {
    return {
      id: this.attr(null),
      body: this.attr(""),
      list_id: this.attr(null),
      list: this.belongsTo(List, "list_id"),
    };
  }
}

コンポーネント側でデータの追加と取得をやってみる。

<div v-for="list in user.lists" :key="list.id">
  {{ list.title }}
  <ul>
    <li v-for="item in list.items" v-text="item.body" :key="item.id" />
  </ul>
</div>
beforeMount() {
    User.insert({
      data: [
        {
          id: 28,
          name: "Luke",
          email: "luke@gmail.com",
          lists: [
            {
              id: 623,
              title: "shopping",
              items: [
                { id: 62, body: "banana" },
                { id: 63, body: "strawberries" },
              ],
            },
            {
              id: 72,
              title: "life goals",
              items: [
                { id: 77, body: "create develop team" },
                { id: 78, body: "finish the vuex-orm course" },
              ],
            },
          ],
        },
      ],
    });
  },
  computed: {
    user() {
      return User.query().with("lists.items").find(28);
    },
  },

ブラウザで確認。

Has Many By

User:List = 1 : N で紐づいている場合にUserモデル側にListモデルの id を list_ids として配列で指定することも可能です。

Userモデル

static fields() {
  return {
    id: this.attr(null),
    name: this.attr(""),
    email: this.attr(""),
    list_ids: this.attr([]),
    lists: this.hasManyBy(List, "list_ids"),
  };
}

Listモデル

static fields() {
  return {
    id: this.attr(null),
    title: this.attr(""),
    user_id: this.attr(null),
    user: this.belongsTo(User, "user_id"),
  };
}

コンポーネント側でデータの追加と取得。

<h1>{{ user.name }}</h1>
<div v-for="list in user.lists" :key="list.id">
  {{ list.title }}
</div>
beforeMount() {
  User.insert({
    data: [
      {
        id: 28,
        name: "Luke",
        email: "luke@gmail.com",
        list_ids: [62, 56, 92],
      },
    ],
  });

  List.insert({
    data: [
      { id: 62, title: "shopping" },
      { id: 56, title: "favorite things" },
      { id: 92, title: "todo" },
    ],
  });
},
computed: {
  user() {
    return User.query().with("lists").find(28);
  },
},

ブラウザで確認。

Has Many Through

以下のような紐づきの状態はになっている場合に User モデルから Item を直接取得することも可能です。

User モデルで hasManyThrough を使ってフィールドを定義します。

static fields() {
  return {
    id: this.attr(null),
    name: this.attr(""),
    email: this.attr(""),
    list_ids: this.attr([]),
    lists: this.hasManyBy(List, "list_ids"),
		items: this.hasManyThrough(Item, List, "user_id", "list_id"),
  };
}

コンポーネント側でデータの追加と取得。

<div style="float: right">
  <ul>
    <li v-for="item in user.items" :key="item.id">{{ item.body }}</li>
  </ul>
</div>
beforeMount() {
  User.insert({
    data: [
      {
        id: 28,
        name: "Luke",
        email: "luke@gmail.com",
				lists: [
            {
              id: 11,
              title: "shopping",
              items: [{ body: "banana" }],
            },
            {
              id: 12,
              title: "favorite",
              items: [{ body: "computer" }],
            },
          ],
      },
    ],
  });
},
computed: {
  user() {
    return User.query().with("items").find(28);
  },
},

ブラウザで確認すると banana, computer が表示されるはずです。

User → List → Item 経由でアクセスする場合は with("lists.items") と書いていましたが with("items") でアクセスできるようになりました。

Many To Many

User, Role が N : N で紐づいている場合。

このような場合は中間モデルを作成する必要があります。

belogsToMany を使ってそれぞれの関係を定義するのでモデルのフィールドは以下のようになります。

User.js

static fields() {
  return {
    id: this.attr(null),
    name: this.attr(""),
    email: this.attr(""),
    roles: this.belongsToMany(Role, RoleUser, "user_id", "role_id"),
  };
}

RoleUser.js

static fields() {
  return {
    id: this.attr(null),
    user_id: this.attr(),
    role_id: this.attr(),
  };
}

Role.js

static fields() {
  return {
    id: this.attr(null),
    title: this.attr(),
    users: this.belongsToMany(User, RoleUser, "role_id", "user_id"),
  };
}

コンポーネント側でデータの追加と取得。

<ul v-for="role in roles" :key="role.id">
  <h1>{{ role.title }}</h1>
  <li v-for="user in role.users" :key="user.id" v-text="user.name"></li>
</ul>
mounted() {
  User.insert({
    data: [
      {
        id: 28,
        name: "Luke",
        email: "luke@gmail.com",
        roles: [
          {
            id: 43,
            title: "admin",
          },
          {
            id: 47,
            title: "designer",
          },
        ],
      },
      {
        id: 29,
        name: "Aaron",
        email: "aaron@gmail.com",
        roles: [
          {
            id: 47,
            title: "designer",
          },
        ],
      },
    ],
  });
},
computed: {
  roles() {
    return Role.query().with("users").get();
  },
},

ブラウザで確認すると role ごとに user を表示できるはずです。

まとめ

Vuex ORM の基本的な使い方についてコードを交えて解説しました。
where, with などを使うとデータの絞り込みや取得が簡潔に書けるのでSPAの開発などで役立ちそうですね。
今回の内容が Vuex ORM を使う際の参考になれば幸いです。

参考

Discussion

ログインするとコメントできます