Relation

テーブルを関連付けて使う方法です。

環境は Postgres のバージョン 11 を使います。またその環境は Docker を用いて以下のコマンドで建てたサーバーを使います。

docker run \
  --name postgres \
  --detach \
  --publish 5432:5432 \
  --env POSTGRES_HOST_AUTH_METHOD=trust \
  postgres:11

準備

テーブルを関連付けたいのでuserspostsという 2 つのテーブルを作ります。ユーザー 1 人辺り0-nの投稿を持つようなものとして作ります。

ユーザーテーブル

ユーザーから作ります。

diesel migration generate create-users

up.sqlは以下のようにします。foobarという名前のユーザー名も一緒に作ります。

CREATE TABLE IF NOT EXISTS users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(30) NOT NULL
);

INSERT INTO users (name) VALUES
  ('foo'),
  ('bar');

投稿テーブル

同じように投稿テーブルも作ります。

diesel migration generate create-posts

up.sqlはこのようにしました。本来はスネークケースが多いと思いますが、キャメルケースに対応した例の為にuserIdと名付けています。

CREATE TABLE IF NOT EXISTS posts (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    "userId" INTEGER NOT NULL REFERENCES users(id)
);

INSERT INTO posts (title, "userId") VALUES
    ('hoge', 1),
    ('fuga', 2);

流します。

diesel migration run \
  --database-url postgres://postgres:@localhost:5432

うまく処理が終わればsrc/schema.rsが以下のように生成されます。

table! {
    posts (id) {
        id -> Int4,
        title -> Varchar,
        userId -> Int4,
    }
}

table! {
    users (id) {
        id -> Int4,
        name -> Varchar,
    }
}

joinable!(posts -> users (userId));

allow_tables_to_appear_in_same_query!(
    posts,
    users,
);

また、こんな感じで投稿が取得できるようになります。

psql postgres://postgres:@localhost:5432 \
  --tuples-only \
  --command \
    '
      select json_agg(posts) from posts 
        where "userId" = (
          select id from users where id = 1
        )
    ' \
  | jq .

この結果は JSON でこんな感じです。

[
  {
    "id": 1,
    "title": "hoge",
    "userId": 1
  }
]

最後にsrc/lib.rsschemaを取り込めるようにしておきます。

#[macro_use]
extern crate diesel;

pub mod schema;

構造体作成

src/lib.rsの最後から以下の 2 つの構造体を追記します。

use schema::{posts, users};

#[derive(Debug, Queryable, Identifiable)]
pub struct User {
  pub id: i32,
  pub name: String,
}

#[derive(Debug, Queryable, Associations, Identifiable)]
#[belongs_to(User, foreign_key = "userId")]
pub struct Post {
  pub id: i32,
  pub title: String,
  #[column_name = "userId"]
  pub user_id: i32,
}

気にする点は 4 つです。

  1. Queryableトレイトを継承
  2. Identifiableトレイトを継承
  3. 関連付けを行う方のテーブルはAssociationsトレイトを継承
  4. belongs_toで属す先の構造体名を指定

またカラム名にキャメルケースを使っているような場合にはさらに 2 つあります。

  1. 構造体のプロパティはスネークケースで定義し、column_nameで実際の名前を指定
  2. belongs_toでさらにforeigh_keyとしてcolumn_nameを指定

これは Diesel がbelongs_to(User)のような時の場合、user_idが参照キーだろうと見てしまうので、これを訂正しなければなりません。

ユーザーから投稿を取得する

src/main.rsを書きます。

紐付けには対象の構造体から::belonging_to関数を呼び出します。その際引数には属す先のインスタンスを指定します。

その後.load.get_resultsなどのメソッドを呼ぶことで、うまく行けば関連する投稿一覧が取得できます。

use diesel::pg::PgConnection;
use diesel::prelude::*;
use get_started_diesel_relation::{Post, User};

const DATABASE_URL: &'static str = "postgres://postgres:@localhost:5432";

fn main() {
  let connection =
    PgConnection::establish(DATABASE_URL).expect(&format!("Error connecting to {}", DATABASE_URL));

  // 検索して取ってきた体
  let user = User {
    id: 1,
    name: "foo".into(),
  };

  let post = Post::belonging_to(&user)
    .load::<Post>(&connection)
    .expect("Error in getting posts");
  println!("{:#?}", post);
}

サンプルコード

動作確認できるコードは nju33-com/get-started-diesel-relation に置かれています。