Relationships

Bob のコード生成では、データベースのリレーションシップを自動的に検出し、対応するコードを生成します。

自動リレーションシップ検出

Bob は外部キー制約に基づいて自動的にリレーションシップを検出します。

基本的な外部キー

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100)
);

CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    title VARCHAR(200),
    content TEXT
);

この場合、Bob は以下のリレーションシップを生成します:

// User モデル
type User struct {
    ID   int    `db:"id"`
    Name string `db:"name"`
}

// Post モデル
type Post struct {
    ID      int    `db:"id"`
    UserID  int    `db:"user_id"`
    Title   string `db:"title"`
    Content string `db:"content"`
}

// リレーションシップメソッド
func (u *User) Posts() *PostQuery {
    return Posts.Query().Where(PostColumns.UserID.EQ(psql.Arg(u.ID)))
}

func (p *Post) User() *UserQuery {
    return Users.Query().Where(UserColumns.ID.EQ(psql.Arg(p.UserID)))
}

One-to-One リレーションシップ

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100)
);

CREATE TABLE user_profiles (
    id SERIAL PRIMARY KEY,
    user_id INTEGER UNIQUE REFERENCES users(id),
    bio TEXT,
    avatar_url VARCHAR(255)
);

生成されるコード:

// User から UserProfile へのリレーション
func (u *User) Profile() *UserProfileQuery {
    return UserProfiles.Query().Where(UserProfileColumns.UserID.EQ(psql.Arg(u.ID)))
}

// UserProfile から User へのリレーション
func (p *UserProfile) User() *UserQuery {
    return Users.Query().Where(UserColumns.ID.EQ(psql.Arg(p.UserID)))
}

One-to-Many リレーションシップ

CREATE TABLE categories (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100)
);

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    category_id INTEGER REFERENCES categories(id),
    name VARCHAR(100),
    price DECIMAL(10,2)
);

生成されるコード:

// Category から Products へのリレーション
func (c *Category) Products() *ProductQuery {
    return Products.Query().Where(ProductColumns.CategoryID.EQ(psql.Arg(c.ID)))
}

// Product から Category へのリレーション
func (p *Product) Category() *CategoryQuery {
    return Categories.Query().Where(CategoryColumns.ID.EQ(psql.Arg(p.CategoryID)))
}

Many-to-Many リレーションシップ

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100)
);

CREATE TABLE roles (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100)
);

CREATE TABLE user_roles (
    user_id INTEGER REFERENCES users(id),
    role_id INTEGER REFERENCES roles(id),
    PRIMARY KEY (user_id, role_id)
);

生成されるコード:

// User から Roles へのリレーション(中間テーブル経由)
func (u *User) Roles() *RoleQuery {
    return Roles.Query().
        Join("user_roles", RoleColumns.ID.EQ(psql.Quote("user_roles", "role_id"))).
        Where(psql.Quote("user_roles", "user_id").EQ(psql.Arg(u.ID)))
}

// Role から Users へのリレーション(中間テーブル経由)
func (r *Role) Users() *UserQuery {
    return Users.Query().
        Join("user_roles", UserColumns.ID.EQ(psql.Quote("user_roles", "user_id"))).
        Where(psql.Quote("user_roles", "role_id").EQ(psql.Arg(r.ID)))
}

カスタムリレーションシップ

設定ファイルでカスタムリレーションシップを定義できます:

relationships:
  - name: "recent_posts"
    local_table: "users"
    foreign_table: "posts"
    local_columns: ["id"]
    foreign_columns: ["user_id"]
    type: "has_many"
    where: "created_at > NOW() - INTERVAL '30 days'"
    
  - name: "active_users"
    local_table: "posts"
    foreign_table: "users"
    local_columns: ["user_id"]
    foreign_columns: ["id"]
    type: "belongs_to"
    where: "active = true"

生成されるコード:

// カスタムリレーション
func (u *User) RecentPosts() *PostQuery {
    return Posts.Query().
        Where(PostColumns.UserID.EQ(psql.Arg(u.ID))).
        Where(psql.Raw("created_at > NOW() - INTERVAL '30 days'"))
}

func (p *Post) ActiveUser() *UserQuery {
    return Users.Query().
        Where(UserColumns.ID.EQ(psql.Arg(p.UserID))).
        Where(psql.Raw("active = true"))
}

Eager Loading

生成されたリレーションシップは Eager Loading をサポートします:

// ユーザーと関連する投稿を一度に取得
users, err := Users.Query().
    Preload("Posts").
    All(ctx, db)

// 投稿と関連するユーザーを取得
posts, err := Posts.Query().
    Preload("User").
    All(ctx, db)

// 複数のリレーションを同時に読み込み
users, err := Users.Query().
    Preload("Posts", "Profile").
    All(ctx, db)

複合外部キー

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_id INTEGER,
    branch_id INTEGER
);

CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id INTEGER,
    customer_id INTEGER,
    branch_id INTEGER,
    product_id INTEGER,
    FOREIGN KEY (order_id, customer_id, branch_id) 
        REFERENCES orders(id, customer_id, branch_id)
);

生成されるコード:

func (o *Order) Items() *OrderItemQuery {
    return OrderItems.Query().
        Where(
            OrderItemColumns.OrderID.EQ(psql.Arg(o.ID)),
            OrderItemColumns.CustomerID.EQ(psql.Arg(o.CustomerID)),
            OrderItemColumns.BranchID.EQ(psql.Arg(o.BranchID)),
        )
}

設定オプション

relationships:
  enabled: true
  auto_detect: true
  eager_loading: true
  naming:
    singular: true
    plural: true
  include:
    - "users"
    - "posts"
  exclude:
    - "migrations"
    - "sessions"

これらの機能により、Bob は複雑なデータベースリレーションシップを自動的に検出し、使いやすい Go コードを生成します。